Implemented cards, graphs, dialogs, the 'about' page... Very productive today!

Commit
297d793044066a9dc1d1e5f08f2ee06060b47c5d
Author
Marius Peter <marius.peter@tutanota.com>
Author date
Committer
Marius Peter <marius.peter@tutanota.com>
Committer date
Changed files
src/MyFitnessTracker/MFTAboutPage.class.st
index 00000000..a7f9ff1b 000000..100644
@@ -0,0 +1,83 @@
1 Added: Class {
2 Added: #name : #MFTAboutPage,
3 Added: #superclass : #MFTPage,
4 Added: #category : #'MyFitnessTracker-Components'
5 Added: }
6 Added:
7 Added: { #category : #initialization }
8 Added: MFTAboutPage >> initialize [
9 Added:
10 Added: super initialize.
11 Added: title := 'about'
12 Added: ]
13 Added:
14 Added: { #category : #rendering }
15 Added: MFTAboutPage >> renderBackgroundOn: html [
16 Added:
17 Added: html heading
18 Added: level: 1;
19 Added: with: 'Background'.
20 Added: html heading
21 Added: level: 2;
22 Added: with: 'Why I built this web app'.
23 Added: html
24 Added: paragraph:
25 Added: 'I wanted a convenient way to record, perform data analysis, and create visualizations for my workouts.';
26 Added: paragraph:
27 Added: 'The following features were, and still are, essential for me:';
28 Added: unorderedList: [
29 Added: html
30 Added: listItem: 'Free access to all app features,';
31 Added: listItem: 'Easy import and export of my training data,';
32 Added: listItem:
33 Added: 'An extensible interface to suit a wide range of training routines.' ];
34 Added: paragraph:
35 Added: 'What better way than to build my own Personal Fitness Tracker?!'.
36 Added: html heading level2 with: 'Who I am'.
37 Added: html paragraph:
38 Added: 'I am an engineer working in Europe, with a deep passion for health and fitness. Publishing My Fitness Tracker is a way for me to give thanks to everyone whose path I have crossed.'
39 Added: ]
40 Added:
41 Added: { #category : #rendering }
42 Added: MFTAboutPage >> renderCatchOn: html [
43 Added:
44 Added: html heading
45 Added: dangerText;
46 Added: with: 'The catch'.
47 Added: html
48 Added: paragraph:
49 Added: 'I''ve made some debatable compromises when it comes to My Fitness Tracker:';
50 Added: unorderedList: [
51 Added: html
52 Added: listItem: 'No external advertisements,';
53 Added: listItem:
54 Added: 'No cookies or personally identifying information stored,';
55 Added: listItem:
56 Added: 'No paid feature, except for the storage of your training data beyond ten workouts.' ];
57 Added: paragraph:
58 Added: 'I know these design choices might frustrate and maybe even anger certain users; thankfully, all previously existing fitness tracking services do not abide by these design principles.';
59 Added: paragraph:
60 Added: 'I am developing My Fitness Tracker for my own personal use case, first and foremost; secondly, for the benefit of as many fitness enthusiasts as possible; thirdly, because I''ve found to be a fun experience. I''m already rich of the joy this project has brought me, and the paid '
61 Added: ]
62 Added:
63 Added: { #category : #rendering }
64 Added: MFTAboutPage >> renderContentOn: html [
65 Added:
66 Added: self renderPageOn: html with: [
67 Added: self renderVisionOn: html.
68 Added: self renderBackgroundOn: html.
69 Added: self renderCatchOn: html]
70 Added: ]
71 Added:
72 Added: { #category : #rendering }
73 Added: MFTAboutPage >> renderVisionOn: html [
74 Added:
75 Added: html heading level1 with: 'The Vision'.
76 Added: html paragraph: [
77 Added: html text: 'Free, and eventually '.
78 Added: html anchor
79 Added: url: 'https://fsfe.org/index.en.html';
80 Added: with: 'libre'.
81 Added: html text:
82 Added: ' fitness tracking for all to enjoy. No user data tracking and reselling; no features blocked for any reason.' ]
83 Added: ]
src/MyFitnessTracker/MFTAnalyticsPage.class.st
index 00000000..3b8be9c6 000000..100644
@@ -0,0 +1,58 @@
1 Added: Class {
2 Added: #name : #MFTAnalyticsPage,
3 Added: #superclass : #MFTPage,
4 Added: #instVars : [
5 Added: 'type'
6 Added: ],
7 Added: #category : #'MyFitnessTracker-Components'
8 Added: }
9 Added:
10 Added: { #category : #'instance creation' }
11 Added: MFTAnalyticsPage class >> for: aSymbol [
12 Added:
13 Added: ^ self new
14 Added: type: aSymbol;
15 Added: yourself
16 Added: ]
17 Added:
18 Added: { #category : #accessing }
19 Added: MFTAnalyticsPage >> analytics [
20 Added:
21 Added: ^ Dictionary newFrom: {
22 Added: (#workouts -> { MFTRepsVsTimeGraph new }).
23 Added: (#weights -> { MFTWeightVsTimeGraph new }) }
24 Added: ]
25 Added:
26 Added: { #category : #rendering }
27 Added: MFTAnalyticsPage >> renderActionsButtonGroupOn: html [
28 Added:
29 Added: html form: [
30 Added: html buttonGroup: [
31 Added: html formButton
32 Added: bePrimary;
33 Added: callback: [ self answer ];
34 Added: with: 'Return' ] ]
35 Added: ]
36 Added:
37 Added: { #category : #rendering }
38 Added: MFTAnalyticsPage >> renderContentOn: html [
39 Added:
40 Added: | components |
41 Added: components := self analytics at: type.
42 Added:
43 Added: self renderPageOn: html with: [
44 Added: self renderActionsButtonGroupOn: html.
45 Added: components do: [ :each | html render: each ] ]
46 Added: ]
47 Added:
48 Added: { #category : #accessing }
49 Added: MFTAnalyticsPage >> type [
50 Added:
51 Added: ^ type
52 Added: ]
53 Added:
54 Added: { #category : #accessing }
55 Added: MFTAnalyticsPage >> type: anObject [
56 Added:
57 Added: type := anObject
58 Added: ]
src/MyFitnessTracker/MFTCard.class.st
index 427646c2..977bc944 100644..100644
@@ -4,138 +4,18 @@
4 4 #category : #'MyFitnessTracker-Components'
5 5 }
6 6
7 Removed: { #category : #'accessing - structure variables' }
8 Removed: MFTCard class >> type: aType title: aString [
9 Removed:
10 Removed: ^ self new
11 Removed: type: aType;
12 Removed: title: aString;
13 Removed: yourself
14 Removed: ]
15 Removed:
16 7 { #category : #rendering }
17 Removed: MFTCard >> renderContentOn: html [
8 Added: MFTCard >> renderCardOn: html with: aBlock [
18 9
19 Removed: self
20 Removed: perform: ('render' , type asCamelCase , 'CardOn:') asSymbol
21 Removed: with: html
22 Removed: ]
23 Removed:
24 Removed: { #category : #rendering }
25 Removed: MFTCard >> renderDefaultCardOn: html [
26 Removed:
27 Removed: html cardBody: [ html paragraph: self defaultDescription ]
28 Removed: ]
29 Removed:
30 Removed: { #category : #rendering }
31 Removed: MFTCard >> renderMainPublicCardOn: html [
32 Removed:
33 10 html card
34 Removed: marginTopAndBottom: 3;
35 Removed: with: [
36 Removed: html jumbotron
37 Removed: class: 'p-3 bg-light';
38 Removed: with: [
39 Removed: html displayHeading
40 Removed: level: 1;
41 Removed: with: 'Welcome to My Fitness Tracker!'.
42 Removed: html big textMuted with: 'Free Software never looked so fit.'.
43 Removed: html form: [
44 Removed: html formButton
45 Removed: bePrimary;
46 Removed: beLarge;
47 Removed: class: 'mt-3';
48 Removed: callback: [
49 Removed: self session pageManager activePage:
50 Removed: (self session pageManager mainPages at: 2) ];
51 Removed: with: 'Log a workout' ] ] ]
11 Added: style: '';
12 Added: with: aBlock
52 13 ]
53 14
54 15 { #category : #rendering }
55 Removed: MFTCard >> renderMainUserCardOn: html [
16 Added: MFTCard >> renderContentOn: html [
56 17
57 Removed: | lastLoginDate subtitle |
58 Removed: lastLoginDate := DateAndTime fromString:
59 Removed: self session user lastLoginDateAndTime.
60 Removed: subtitle := {
61 Removed: 'Last logged in'.
62 Removed: (lastLoginDate dayOfWeekName , ',').
63 Removed: lastLoginDate asDate asString.
64 Removed: 'at'.
65 Removed: lastLoginDate asTimeUTC asString.
66 Removed: 'UTC' } joinUsing: ' '.
67 Removed:
68 Removed:
69 Removed: html card
70 Removed: marginTopAndBottom: 3;
71 Removed: with: [
72 Removed: html jumbotron
73 Removed: class: 'p-3 bg-light';
74 Removed: with: [
75 Removed: html displayHeading
76 Removed: level: 1;
77 Removed: with: [
78 Removed: html
79 Removed: text: 'Welcome, ';
80 Removed: code: self session user username;
81 Removed: text: '!' ].
82 Removed: html big textMuted with: subtitle ] ]
83 Removed: ]
84 Removed:
85 Removed: { #category : #rendering }
86 Removed: MFTCard >> renderPromisesCardOn: html [
87 Removed:
88 Removed: html card: [
89 Removed: html cardHeader: self title.
90 Removed: html cardBody: [
91 Removed: html unorderedList: [
92 Removed: html listItem:
93 Removed: 'Every single feature shall always be free of charge.'.
94 Removed: html listItem:
95 Removed: 'No personal user data shall ever be sold, under any circumstances.'.
96 Removed: html listItem:
97 Removed: 'No external advertisements shall ever be displayed.' ] ] ]
98 Removed: ]
99 Removed:
100 Removed: { #category : #rendering }
101 Removed: MFTCard >> renderWeightsCardOn: html [
102 Removed:
103 Removed: html card: [
104 Removed: html cardHeader: self title.
105 Removed: html cardBody: [
106 Removed: html paragraph: 'Great job logging your weight '
107 Removed: , self session user weights size asString , ' times!'.
108 Removed: html form
109 Removed: class: 'my-3';
110 Removed: with: [
111 Removed: html formButton
112 Removed: bePrimary;
113 Removed: beSmall;
114 Removed: callback: [
115 Removed: self session pageManager activePage:
116 Removed: (self session pageManager mainPages at: 3) ];
117 Removed: with: 'Log a weight' ].
118 Removed: html render: (MFTGraph type: #weightVersusTime) ] ]
119 Removed: ]
120 Removed:
121 Removed: { #category : #rendering }
122 Removed: MFTCard >> renderWorkoutsCardOn: html [
123 Removed:
124 Removed: html card: [
125 Removed: html cardHeader: self title.
126 Removed: html cardBody: [
127 Removed: html paragraph:
128 Removed: 'Great job logging ' , self session user workouts size asString
129 Removed: , ' workouts!'.
130 Removed: html form
131 Removed: class: 'my-3';
132 Removed: with: [
133 Removed: html formButton
134 Removed: bePrimary;
135 Removed: beSmall;
136 Removed: callback: [
137 Removed: self session pageManager activePage:
138 Removed: (self session pageManager mainPages at: 2) ];
139 Removed: with: 'Log a workout' ].
140 Removed: html render: (MFTGraph type: #repsVersusExercises) ] ]
18 Added: self
19 Added: renderCardOn: html
20 Added: with: [ html cardBody: [ html paragraph: self defaultDescription ] ]
141 21 ]
src/MyFitnessTracker/MFTDialog.class.st
index 00000000..3a13fb47 000000..100644
@@ -0,0 +1,60 @@
1 Added: Class {
2 Added: #name : #MFTDialog,
3 Added: #superclass : #SBSComponent,
4 Added: #instVars : [
5 Added: 'addOrEdit'
6 Added: ],
7 Added: #category : #'MyFitnessTracker-Dialogs'
8 Added: }
9 Added:
10 Added: { #category : #actions }
11 Added: MFTDialog class >> edit: aMFTWorkout [
12 Added:
13 Added: ^ self new
14 Added: workout: aMFTWorkout;
15 Added: exercises: aMFTWorkout exercises;
16 Added: addOrEdit: #edit;
17 Added: yourself
18 Added: ]
19 Added:
20 Added: { #category : #accessing }
21 Added: MFTDialog >> addOrEdit [
22 Added:
23 Added: ^ addOrEdit
24 Added: ]
25 Added:
26 Added: { #category : #accessing }
27 Added: MFTDialog >> addOrEdit: aRole [
28 Added:
29 Added: addOrEdit := aRole
30 Added: ]
31 Added:
32 Added: { #category : #initialization }
33 Added: MFTDialog >> initialize [
34 Added:
35 Added: super initialize.
36 Added: addOrEdit := #add
37 Added: ]
38 Added:
39 Added: { #category : #rendering }
40 Added: MFTDialog >> renderContentOn: html [
41 Added:
42 Added: self renderDialogOn: html with: [
43 Added: html heading: 'Default ' , self class name.
44 Added: html form: [
45 Added: html formGroup: [
46 Added: html label: 'Don''t do anything'.
47 Added: html formButton
48 Added: beSecondary;
49 Added: formControl;
50 Added: callback: [ self answer: nil ];
51 Added: with: 'Cancel' ] ] ]
52 Added: ]
53 Added:
54 Added: { #category : #rendering }
55 Added: MFTDialog >> renderDialogOn: html with: aBlock [
56 Added:
57 Added: html container
58 Added: class: 'w-50';
59 Added: with: aBlock
60 Added: ]
src/MyFitnessTracker/MFTFooterComponent.class.st
index cd991772..a08234dd 100644..100644
@@ -7,10 +7,9 @@
7 7 { #category : #rendering }
8 8 MFTFooterComponent >> renderContentOn: html [
9 9
10 Removed: html container
10 Added: html card
11 11 marginTop: 3;
12 12 with: [
13 Removed: html card: [
14 Removed: html cardHeader:
15 Removed: 'Copyright 2023-' , Date today year asString , ' Marius PETER' ] ]
13 Added: html cardHeader:
14 Added: 'Copyright 2023-' , Date today year asString , ' Marius PETER' ]
16 15 ]
src/MyFitnessTracker/MFTGraph.class.st
index 23992fb5..d79759f4 100644..100644
@@ -1,105 +1,61 @@
1 1 Class {
2 2 #name : #MFTGraph,
3 3 #superclass : #MFTWidget,
4 Added: #instVars : [
5 Added: 'labels',
6 Added: 'data'
7 Added: ],
4 8 #category : #'MyFitnessTracker-Components'
5 9 }
6 10
7 Removed: { #category : #rendering }
8 Removed: MFTGraph >> renderContentOn: html [
11 Added: { #category : #accessing }
12 Added: MFTGraph >> data [
9 13
10 Removed: html div: [
11 Removed: self
12 Removed: perform: ('render' , type asCamelCase , 'GraphOn:') asSymbol
13 Removed: with: html ]
14 Added: ^ data
14 15 ]
15 16
16 Removed: { #category : #rendering }
17 Removed: MFTGraph >> renderDefaultGraphOn: html [
17 Added: { #category : #accessing }
18 Added: MFTGraph >> data: anObject [
18 19
19 Removed: | labels data |
20 Added: data := anObject
21 Added: ]
22 Added:
23 Added: { #category : #initialization }
24 Added: MFTGraph >> initialize [
25 Added:
26 Added: super initialize.
20 27 labels := #( Monday Tuesday Wednesday Thursday Friday ).
21 Removed: data := #( 5 1 6 2 7 ).
22 Removed: html heading level5 with: self defaultDescription.
23 Removed: html canvas id: 'defaultGraph'.
24 Removed: html script:
25 Removed: 'var ctx = document.getElementById("defaultGraph").getContext("2d");
26 Removed: var defaultChart = new Chart(ctx, {
27 Removed: type: "line",
28 Removed: data: {
29 Removed: labels: ' , labels asJavascript , ',
30 Removed: datasets: [{
31 Removed: label: "default data",
32 Removed: data: ' , data asJavascript , ',
33 Removed: borderWidth: 1
34 Removed: }]
35 Removed: }
36 Removed: });'
28 Added: data := #( 5 1 6 2 7 )
37 29 ]
38 30
39 Removed: { #category : #rendering }
40 Removed: MFTGraph >> renderRepsVersusExercisesGraphOn: html [
31 Added: { #category : #accessing }
32 Added: MFTGraph >> labels [
41 33
42 Removed: | exercises labels data repTally |
43 Removed: exercises := self session user workouts flatCollect: #exercises.
44 Removed: repTally := Dictionary new.
45 Removed: exercises do: [ :exercise |
46 Removed: repTally at: exercise englishName ifAbsentPut: [ 0 ].
47 Removed: repTally
48 Removed: at: exercise englishName
49 Removed: put: (repTally at: exercise englishName) + exercise repsBySet sum ].
50 Removed: labels := repTally keys.
51 Removed: data := repTally values.
34 Added: ^ labels
35 Added: ]
52 36
53 Removed: html canvas id: 'workoutChart'.
54 Removed: html script:
55 Removed: 'var ctx = document.getElementById("workoutChart").getContext("2d");
56 Removed: var workoutChart = new Chart(ctx, {
57 Removed: type: "doughnut",
58 Removed: data: {
59 Removed: labels: ' , labels asJavascript , ',
60 Removed: datasets: [{
61 Removed: data: ' , data asJavascript , '
62 Removed: }]
63 Removed: },
64 Removed: options: {
65 Removed: title: {
66 Removed: display: true,
67 Removed: text: "Reps by exercise"
68 Removed: }
69 Removed: }
70 Removed: });'
37 Added: { #category : #accessing }
38 Added: MFTGraph >> labels: anObject [
39 Added:
40 Added: labels := anObject
71 41 ]
72 42
73 43 { #category : #rendering }
74 Removed: MFTGraph >> renderWeightVersusTimeGraphOn: html [
44 Added: MFTGraph >> renderContentOn: html [
75 45
76 Removed: | labels data |
77 Removed: labels := self session user weights collect: [ :weight |
78 Removed: weight date yyyymmdd ].
79 Removed: data := self session user weights collect: #value.
80 Removed: html canvas id: 'weightChart'.
46 Added: html heading level5 with: self defaultDescription.
47 Added: html canvas id: 'defaultGraph'.
81 48 html script:
82 Removed: 'var ctx = document.getElementById("weightChart").getContext("2d");
83 Removed: var weightChart = new Chart(ctx, {
49 Added: 'var ctx = document.getElementById("defaultGraph").getContext("2d");
50 Added: var defaultChart = new Chart(ctx, {
84 51 type: "line",
85 52 data: {
86 53 labels: ' , labels asJavascript , ',
87 54 datasets: [{
88 Removed: label: "weight",
55 Added: label: "default data",
89 56 data: ' , data asJavascript , ',
90 Removed: backgroundColor: "rgba(255, 99, 132, 0.2)",
91 Removed: borderColor: "rgba(255, 99, 132, 1)",
92 57 borderWidth: 1
93 58 }]
94 Removed: },
95 Removed: options: {
96 Removed: scales: {
97 Removed: yAxes: [{
98 Removed: ticks: {
99 Removed: beginAtZero: true
100 Removed: }
101 Removed: }]
102 Removed: }
103 59 }
104 60 });'
105 61 ]
src/MyFitnessTracker/MFTHeaderComponent.class.st
index dd3c2110..a3c860d5 100644..100644
@@ -1,109 +1,5 @@
1 1 Class {
2 2 #name : #MFTHeaderComponent,
3 3 #superclass : #SBSComponent,
4 Removed: #instVars : [
5 Removed: 'email',
6 Removed: 'localPart',
7 Removed: 'domain'
8 Removed: ],
9 4 #category : #'MyFitnessTracker-Components'
10 5 }
11 Removed:
12 Removed: { #category : #adding }
13 Removed: MFTHeaderComponent >> addUserEmail [
14 Removed:
15 Removed: | emailToCheck emailPattern |
16 Removed: emailToCheck := self call: MFTEmailSignupDialog new.
17 Removed: emailPattern := '^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]{3,}\.[a-zA-Z]{2,}$'
18 Removed: asRegex.
19 Removed: (emailToCheck isNil or: [ (emailPattern matches: emailToCheck) not ])
20 Removed: ifTrue: [ ^ nil ].
21 Removed: email := emailToCheck.
22 Removed: MFTSQLite3Database storeProspectEmail: email
23 Removed: ]
24 Removed:
25 Removed: { #category : #rendering }
26 Removed: MFTHeaderComponent >> renderContentOn: html [
27 Removed:
28 Removed: email
29 Removed: ifNil: [
30 Removed: html alert
31 Removed: beWarning;
32 Removed: beDismissible;
33 Removed: marginBottom: 0;
34 Removed: with: [
35 Removed: html closeButton dataDismiss: 'alert'.
36 Removed: html alertHeading: 'This app is still in beta.'.
37 Removed: html paragraph: [
38 Removed: html
39 Removed: text:
40 Removed: 'My Fitness Tracker is still under heavy development, with improvements being made daily.';
41 Removed: space;
42 Removed: text: 'I can''t wait to share the finished product with you!' ].
43 Removed: html form with: [
44 Removed: html label
45 Removed: style: 'margin-right: 1em';
46 Removed: with: [ html strong: 'Want to know when version 1.0 is out?' ].
47 Removed: html formButton
48 Removed: beInfo;
49 Removed: callback: [ self addUserEmail ];
50 Removed: with: 'E-mail me!' ] ] ]
51 Removed: ifNotNil: [
52 Removed: html alert
53 Removed: beSuccess;
54 Removed: beDismissible;
55 Removed: marginBottom: 0;
56 Removed: with: [
57 Removed: html closeButton dataDismiss: 'alert'.
58 Removed: html text: email , ' will be notified when v1 is released!' ] ]
59 Removed: ]
60 Removed:
61 Removed: { #category : #rendering }
62 Removed: MFTHeaderComponent >> renderEmailInputModalButtonOn: html [
63 Removed:
64 Removed: ^ html formButton
65 Removed: bePrimary;
66 Removed: beSmall;
67 Removed: dataToggle: 'modal';
68 Removed: dataTarget: '#emailInputModal';
69 Removed: with: 'E-mail me!'
70 Removed: ]
71 Removed:
72 Removed: { #category : #rendering }
73 Removed: MFTHeaderComponent >> renderEmailInputModalOn: html [
74 Removed:
75 Removed: html modal
76 Removed: id: 'emailInputModal';
77 Removed: fade;
78 Removed: with: [
79 Removed: html modalDialog
80 Removed: beCentered;
81 Removed: with: [
82 Removed: html modalContent: [
83 Removed: html form: [
84 Removed: html modalHeader: [
85 Removed: html modalTitle
86 Removed: level5;
87 Removed: with:
88 Removed: 'You''ll be the first to know when My Fitness Tracker is ready to use.'.
89 Removed: html modalCloseButton ].
90 Removed:
91 Removed: html modalBody: [
92 Removed: html inputGroup: [
93 Removed: html formTextInput
94 Removed: callback: [ :value | localPart := value ];
95 Removed: placeholder: 'my-user'.
96 Removed: html inputGroupAppend: [ html inputGroupText: '@' ].
97 Removed: html formTextInput
98 Removed: callback: [ :value | domain := value ];
99 Removed: placeholder: 'example.com' ] ].
100 Removed:
101 Removed: html modalFooter: [
102 Removed: html paragraph:
103 Removed: 'You will receive a single e-mail when the app is ready to use. Your e-mail address shall not be used in any other way.'.
104 Removed: html formButton
105 Removed: bePrimary;
106 Removed: beSubmit;
107 Removed: callback: [ email := localPart , '@' , domain ];
108 Removed: with: 'Confirm' ] ] ] ] ]
109 Removed: ]
src/MyFitnessTracker/MFTLearnPage.class.st
index e24c25a7..c114fb60 100644..100644
@@ -18,5 +18,7 @@
18 18 { #category : #rendering }
19 19 MFTLearnPage >> renderContentOn: html [
20 20
21 Removed: articles do: [ :each | html render: each ]
21 Added: self
22 Added: renderPageOn: html
23 Added: with: [ articles do: [ :each | html render: each ] ]
22 24 ]
src/MyFitnessTracker/MFTMainNavigationBarComponent.class.st
index 1c15bc80..c38f823f 100644..100644
@@ -39,7 +39,7 @@
39 39 | pm |
40 40 pm := self session pageManager.
41 41 html navigationBarBrand
42 Removed: callback: [ pm activePage: (pm mainPages at: 1) ];
42 Added: callback: [ pm activePage: 1 ];
43 43 with: 'MyFitnessTracker'.
44 44 "The toggler that is only visible when reducing the width of screen"
45 45 html navigationBarToggler
@@ -51,12 +51,12 @@
51 51 id: 'navbarCollapsed';
52 52 with: [
53 53 html navigationBarNavigation: [
54 Removed: pm mainPages do: [ :page |
54 Added: pm mainPages doWithIndex: [ :page :i |
55 55 html navigationItem
56 56 beActiveIf: [ pm activePage = page ];
57 57 with: [
58 58 html navigationLink
59 Removed: callback: [ pm activePage: page ];
59 Added: callback: [ pm activePage: i ];
60 60 with: page title asCapitalizedPhrase ] ] ].
61 61 html div
62 62 class: 'ms-auto';
src/MyFitnessTracker/MFTMainPublicCard.class.st
index 00000000..6322d342 000000..100644
@@ -0,0 +1,31 @@
1 Added: Class {
2 Added: #name : #MFTMainPublicCard,
3 Added: #superclass : #MFTCard,
4 Added: #category : #'MyFitnessTracker-Components'
5 Added: }
6 Added:
7 Added: { #category : #adding }
8 Added: MFTMainPublicCard >> addWorkoutDialog [
9 Added:
10 Added: self session pageManager activePage call: MFTWorkoutDialog new
11 Added: ]
12 Added:
13 Added: { #category : #rendering }
14 Added: MFTMainPublicCard >> renderContentOn: html [
15 Added:
16 Added: self renderCardOn: html with: [
17 Added: html jumbotron
18 Added: class: 'p-3 bg-light h-100';
19 Added: with: [
20 Added: html displayHeading
21 Added: level: 1;
22 Added: with: 'Welcome to My Fitness Tracker!'.
23 Added: html big textMuted with: 'Free Software never looked so fit.'.
24 Added: html form: [
25 Added: html formButton
26 Added: bePrimary;
27 Added: beLarge;
28 Added: class: 'mt-3';
29 Added: callback: [ self addWorkoutDialog ];
30 Added: with: 'Log a workout' ] ] ]
31 Added: ]
src/MyFitnessTracker/MFTMainUserCard.class.st
index 00000000..b2223add 000000..100644
@@ -0,0 +1,41 @@
1 Added: Class {
2 Added: #name : #MFTMainUserCard,
3 Added: #superclass : #MFTCard,
4 Added: #instVars : [
5 Added: 'lastLoginDate',
6 Added: 'subtitle'
7 Added: ],
8 Added: #category : #'MyFitnessTracker-Components'
9 Added: }
10 Added:
11 Added: { #category : #initialization }
12 Added: MFTMainUserCard >> initialize [
13 Added:
14 Added: super initialize.
15 Added: lastLoginDate := DateAndTime fromString:
16 Added: self session user lastLoginDateAndTime.
17 Added: subtitle := {
18 Added: 'Last logged in'.
19 Added: (lastLoginDate dayOfWeekName , ',').
20 Added: lastLoginDate asDate asString.
21 Added: 'at'.
22 Added: lastLoginDate asTimeUTC asString.
23 Added: 'UTC' } joinUsing: ' '
24 Added: ]
25 Added:
26 Added: { #category : #rendering }
27 Added: MFTMainUserCard >> renderContentOn: html [
28 Added:
29 Added: self renderCardOn: html with: [
30 Added: html jumbotron
31 Added: class: 'p-3 bg-light';
32 Added: with: [
33 Added: html displayHeading
34 Added: level: 1;
35 Added: with: [
36 Added: html
37 Added: text: 'Welcome, ';
38 Added: code: self session user username;
39 Added: text: '!' ].
40 Added: html big textMuted with: subtitle ] ]
41 Added: ]
src/MyFitnessTracker/MFTMusclesArticle.class.st
index 4634837f..885ff81e 100644..100644
@@ -23,7 +23,6 @@
23 23 { #category : #rendering }
24 24 MFTMusclesArticle >> renderContentOn: html [
25 25
26 Removed: html container: [
27 Removed: html heading: title.
28 Removed: muscleGroups do: [ :muscleGroup | muscleGroup renderContentOn: html ] ]
26 Added: html heading: title.
27 Added: muscleGroups do: [ :muscleGroup | muscleGroup renderContentOn: html ]
29 28 ]
src/MyFitnessTracker/MFTPage.class.st
index 70ff8967..07330d7d 100644..100644
@@ -7,6 +7,14 @@
7 7 #category : #'MyFitnessTracker-Components'
8 8 }
9 9
10 Added: { #category : #rendering }
11 Added: MFTPage >> renderPageOn: html with: aBlock [
12 Added:
13 Added: html container
14 Added: style: 'max-width: 60vw';
15 Added: with: aBlock
16 Added: ]
17 Added:
10 18 { #category : #accessing }
11 19 MFTPage >> title [
12 20
src/MyFitnessTracker/MFTPageManager.class.st
index cb10f17f..4647489e 100644..100644
@@ -16,9 +16,9 @@
16 16 ]
17 17
18 18 { #category : #accessing }
19 Removed: MFTPageManager >> activePage: aMFTComponent [
19 Added: MFTPageManager >> activePage: anIndex [
20 20
21 Removed: activePage := aMFTComponent
21 Added: activePage := mainPages at: anIndex
22 22 ]
23 23
24 24 { #category : #'as yet unclassified' }
@@ -30,12 +30,13 @@
30 30 { #category : #'as yet unclassified' }
31 31 MFTPageManager >> defaultPublicPages [
32 32
33 Removed: ^ OrderedCollection newFromArray: {
34 Removed: MFTPublicLandingPage new.
35 Removed: MFTWorkoutsListPage placeholder.
36 Removed: MFTWeightsListPage placeholder.
37 Removed: MFTLearnPage new.
38 Removed: MFTProfilePage new }
33 Added: ^ {
34 Added: MFTPublicLandingPage new.
35 Added: MFTWorkoutsListPage placeholder.
36 Added: MFTWeightsListPage placeholder.
37 Added: MFTLearnPage new.
38 Added: MFTAboutPage new.
39 Added: MFTProfilePage new }
39 40 ]
40 41
41 42 { #category : #initialization }
@@ -43,7 +44,7 @@
43 44
44 45 mainNavigationBar := MFTMainNavigationBarComponent new.
45 46 mainPages := self defaultPublicPages.
46 Removed: activePage := mainPages first
47 Added: activePage := mainPages at: 1
47 48 ]
48 49
49 50 { #category : #'as yet unclassified' }
@@ -97,7 +98,7 @@
97 98 ]
98 99
99 100 { #category : #'as yet unclassified' }
100 Removed: MFTPageManager >> registerProspectHeader [
101 Added: MFTPageManager >> registerProspectEmailHeader [
101 102
102 Removed: ^ MFTHeaderComponent new
103 Added: ^ MFTRegisterProspectEmailHeader new
103 104 ]
src/MyFitnessTracker/MFTProfilePage.class.st
index e7cc22a5..2f60928a 100644..100644
@@ -14,5 +14,5 @@
14 14 { #category : #rendering }
15 15 MFTProfilePage >> renderContentOn: html [
16 16
17 Removed: html container: [ html text: 'Welcome!' ]
17 Added: self renderPageOn: html with: [ html text: 'Welcome!' ]
18 18 ]
src/MyFitnessTracker/MFTPromisesCard.class.st
index 00000000..7ebe35c1 000000..100644
@@ -0,0 +1,28 @@
1 Added: Class {
2 Added: #name : #MFTPromisesCard,
3 Added: #superclass : #MFTCard,
4 Added: #category : #'MyFitnessTracker-Components'
5 Added: }
6 Added:
7 Added: { #category : #rendering }
8 Added: MFTPromisesCard >> renderContentOn: html [
9 Added:
10 Added: html card: [
11 Added: html cardHeader: 'My promises to all users:'.
12 Added: html cardBody: [
13 Added: html listGroup: [
14 Added: html
15 Added: listGroupItem:
16 Added: 'Every single feature shall always be free of charge.';
17 Added: listGroupItem:
18 Added: 'No personal user data shall ever be sold, under any circumstances.';
19 Added: listGroupItem:
20 Added: 'No external advertisements shall ever be displayed.' ].
21 Added: html form
22 Added: class: 'mt-3';
23 Added: with: [
24 Added: html outlineButton
25 Added: bePrimary;
26 Added: callback: [ self session pageManager activePage: 5 ];
27 Added: with: 'Why did I create this web app?' ] ] ]
28 Added: ]
src/MyFitnessTracker/MFTPublicLandingPage.class.st
index c4fbbddf..bf765025 100644..100644
@@ -1,27 +1,42 @@
1 1 Class {
2 2 #name : #MFTPublicLandingPage,
3 3 #superclass : #MFTPage,
4 Added: #instVars : [
5 Added: 'cards'
6 Added: ],
4 7 #category : #'MyFitnessTracker-Components'
5 8 }
6 9
10 Added: { #category : #hooks }
11 Added: MFTPublicLandingPage >> children [
12 Added:
13 Added: ^ cards
14 Added: ]
15 Added:
16 Added: { #category : #'as yet unclassified' }
17 Added: MFTPublicLandingPage >> defaultCards [
18 Added:
19 Added: ^ {
20 Added: MFTMainPublicCard new.
21 Added: MFTWorkoutsCard new.
22 Added: MFTWeightsCard new.
23 Added: MFTPromisesCard new }
24 Added: ]
25 Added:
7 26 { #category : #initialization }
8 27 MFTPublicLandingPage >> initialize [
9 28
10 29 super initialize.
11 Removed: title := 'home'
30 Added: title := 'home'.
31 Added: cards := self defaultCards.
12 32 ]
13 33
14 34 { #category : #rendering }
15 35 MFTPublicLandingPage >> renderContentOn: html [
16 36
17 Removed: html container: [
18 Removed: html render: (MFTCard type: #mainPublic).
37 Added: self renderPageOn: html with: [
19 38 html div
20 39 style:
21 Removed: 'display: flex; flex-wrap: wrap; justify-content: space-evenly; gap: 1em';
22 Removed: with: [
23 Removed: html render: (MFTCard type: #workouts title: 'Workouts').
24 Removed: html render: (MFTCard type: #weights title: 'Weights').
25 Removed: html render:
26 Removed: (MFTCard type: #promises title: 'My promises to all users') ] ]
40 Added: 'display: flex; flex-wrap: wrap; justify-content: space-around; gap: 1em';
41 Added: with: [ cards do: [ :card | html render: card ] ] ]
27 42 ]
src/MyFitnessTracker/MFTRegisterProspectEmailHeader.class.st
index 00000000..5dd4278f 000000..100644
@@ -0,0 +1,109 @@
1 Added: Class {
2 Added: #name : #MFTRegisterProspectEmailHeader,
3 Added: #superclass : #MFTHeaderComponent,
4 Added: #instVars : [
5 Added: 'email',
6 Added: 'localPart',
7 Added: 'domain'
8 Added: ],
9 Added: #category : #'MyFitnessTracker-Components'
10 Added: }
11 Added:
12 Added: { #category : #adding }
13 Added: MFTRegisterProspectEmailHeader >> addUserEmail [
14 Added:
15 Added: | emailToCheck emailPattern |
16 Added: emailToCheck := self call: MFTEmailSignupDialog new.
17 Added: emailPattern := '^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]{3,}\.[a-zA-Z]{2,}$'
18 Added: asRegex.
19 Added: (emailToCheck isNil or: [ (emailPattern matches: emailToCheck) not ])
20 Added: ifTrue: [ ^ nil ].
21 Added: email := emailToCheck.
22 Added: MFTSQLite3Database storeProspectEmail: email
23 Added: ]
24 Added:
25 Added: { #category : #rendering }
26 Added: MFTRegisterProspectEmailHeader >> renderContentOn: html [
27 Added:
28 Added: email
29 Added: ifNil: [
30 Added: html alert
31 Added: beWarning;
32 Added: beDismissible;
33 Added: marginBottom: 0;
34 Added: with: [
35 Added: html closeButton dataDismiss: 'alert'.
36 Added: html alertHeading: 'This app is still in beta.'.
37 Added: html paragraph: [
38 Added: html
39 Added: text:
40 Added: 'My Fitness Tracker is still under heavy development, with improvements being made daily.';
41 Added: space;
42 Added: text: 'I can''t wait to share the finished product with you!' ].
43 Added: html form with: [
44 Added: html label
45 Added: style: 'margin-right: 1em';
46 Added: with: [ html strong: 'Want to know when version 1.0 is out?' ].
47 Added: html formButton
48 Added: beInfo;
49 Added: callback: [ self addUserEmail ];
50 Added: with: 'E-mail me!' ] ] ]
51 Added: ifNotNil: [
52 Added: html alert
53 Added: beSuccess;
54 Added: beDismissible;
55 Added: marginBottom: 0;
56 Added: with: [
57 Added: html closeButton dataDismiss: 'alert'.
58 Added: html text: email , ' will be notified when v1 is released!' ] ]
59 Added: ]
60 Added:
61 Added: { #category : #rendering }
62 Added: MFTRegisterProspectEmailHeader >> renderEmailInputModalButtonOn: html [
63 Added:
64 Added: ^ html formButton
65 Added: bePrimary;
66 Added: beSmall;
67 Added: dataToggle: 'modal';
68 Added: dataTarget: '#emailInputModal';
69 Added: with: 'E-mail me!'
70 Added: ]
71 Added:
72 Added: { #category : #rendering }
73 Added: MFTRegisterProspectEmailHeader >> renderEmailInputModalOn: html [
74 Added:
75 Added: html modal
76 Added: id: 'emailInputModal';
77 Added: fade;
78 Added: with: [
79 Added: html modalDialog
80 Added: beCentered;
81 Added: with: [
82 Added: html modalContent: [
83 Added: html form: [
84 Added: html modalHeader: [
85 Added: html modalTitle
86 Added: level5;
87 Added: with:
88 Added: 'You''ll be the first to know when My Fitness Tracker is ready to use.'.
89 Added: html modalCloseButton ].
90 Added:
91 Added: html modalBody: [
92 Added: html inputGroup: [
93 Added: html formTextInput
94 Added: callback: [ :value | localPart := value ];
95 Added: placeholder: 'my-user'.
96 Added: html inputGroupAppend: [ html inputGroupText: '@' ].
97 Added: html formTextInput
98 Added: callback: [ :value | domain := value ];
99 Added: placeholder: 'example.com' ] ].
100 Added:
101 Added: html modalFooter: [
102 Added: html paragraph:
103 Added: 'You will receive a single e-mail when the app is ready to use. Your e-mail address shall not be used in any other way.'.
104 Added: html formButton
105 Added: bePrimary;
106 Added: beSubmit;
107 Added: callback: [ email := localPart , '@' , domain ];
108 Added: with: 'Confirm' ] ] ] ] ]
109 Added: ]
src/MyFitnessTracker/MFTRepsVsTimeGraph.class.st
index 00000000..f6cbded6 000000..100644
@@ -0,0 +1,51 @@
1 Added: Class {
2 Added: #name : #MFTRepsVsTimeGraph,
3 Added: #superclass : #MFTGraph,
4 Added: #category : #'MyFitnessTracker-Components'
5 Added: }
6 Added:
7 Added: { #category : #'as yet unclassified' }
8 Added: MFTRepsVsTimeGraph >> crunchTheNumbers [
9 Added:
10 Added: | exercises repTally |
11 Added: exercises := self session user workouts flatCollect: #exercises.
12 Added: repTally := Dictionary new.
13 Added: exercises do: [ :exercise |
14 Added: repTally at: exercise englishName ifAbsentPut: [ 0 ].
15 Added: repTally
16 Added: at: exercise englishName
17 Added: put: (repTally at: exercise englishName) + exercise repsBySet sum ].
18 Added:
19 Added: labels := repTally keys.
20 Added: data := repTally values
21 Added: ]
22 Added:
23 Added: { #category : #initialization }
24 Added: MFTRepsVsTimeGraph >> initialize [
25 Added:
26 Added: super initialize.
27 Added: self crunchTheNumbers
28 Added: ]
29 Added:
30 Added: { #category : #rendering }
31 Added: MFTRepsVsTimeGraph >> renderContentOn: html [
32 Added:
33 Added: html canvas id: 'workoutChart'.
34 Added: html script:
35 Added: 'var ctx = document.getElementById("workoutChart").getContext("2d");
36 Added: var workoutChart = new Chart(ctx, {
37 Added: type: "doughnut",
38 Added: data: {
39 Added: labels: ' , labels asJavascript , ',
40 Added: datasets: [{
41 Added: data: ' , data asJavascript , '
42 Added: }]
43 Added: },
44 Added: options: {
45 Added: title: {
46 Added: display: true,
47 Added: text: "Reps by exercise"
48 Added: }
49 Added: }
50 Added: });'
51 Added: ]
src/MyFitnessTracker/MFTRootComponent.class.st
index 4a5baa9c..21859a77 100644..100644
@@ -33,7 +33,8 @@
33 33 at: #development put: {
34 34 JQDevelopmentLibrary.
35 35 JQUiDevelopmentLibrary.
36 Removed: SBSDevelopmentLibrary };
36 Added: SBSDevelopmentLibrary.
37 Added: 'https://cdn.jsdelivr.net/npm/chart.js' };
37 38 at: #deployment put: {
38 39 JQGoogleLibrary.
39 40 JQUiGoogleLibrary.
src/MyFitnessTracker/MFTScreenComponent.class.st
index 571693a7..d7d08bdf 100644..100644
@@ -12,7 +12,7 @@
12 12 MFTScreenComponent >> children [
13 13
14 14 ^ {
15 Removed: pm registerProspectHeader.
15 Added: pm registerProspectEmailHeader.
16 16 pm mainNavigationBar.
17 17 pm activePage.
18 18 footer }
@@ -30,7 +30,7 @@
30 30 MFTScreenComponent >> renderContentOn: html [
31 31
32 32 self session user isLoggedIn ifFalse: [
33 Removed: html render: pm registerProspectHeader ].
33 Added: html render: pm registerProspectEmailHeader ].
34 34 html
35 35 render: pm mainNavigationBar;
36 36 render: pm activePage;
src/MyFitnessTracker/MFTUserLandingPage.class.st
index 46d9fab6..7c8b789c 100644..100644
@@ -14,9 +14,9 @@
14 14 { #category : #rendering }
15 15 MFTUserLandingPage >> renderContentOn: html [
16 16
17 Removed: html container: [
18 Removed: html render: (MFTCard type: #mainUser).
19 Removed: html render: (MFTCard type: #workouts).
17 Added: self renderPageOn: html with: [
18 Added: html render: MFTMainUserCard new.
19 Added: html render: MFTWorkoutsCard new.
20 20 html render: MFTCard new ]
21 21 ]
22 22
src/MyFitnessTracker/MFTWeightDialog.class.st
index 53b2daf7..809ad96e 100644..100644
@@ -1,34 +1,12 @@
1 1 Class {
2 2 #name : #MFTWeightDialog,
3 Removed: #superclass : #SBSComponent,
3 Added: #superclass : #MFTDialog,
4 4 #instVars : [
5 Removed: 'weight',
6 Removed: 'addOrEdit'
5 Added: 'weight'
7 6 ],
8 7 #category : #'MyFitnessTracker-Dialogs'
9 8 }
10 9
11 Removed: { #category : #actions }
12 Removed: MFTWeightDialog class >> edit: aMFTWeight [
13 Removed:
14 Removed: ^ self new
15 Removed: weight: aMFTWeight;
16 Removed: addOrEdit: #edit;
17 Removed: yourself
18 Removed: ]
19 Removed:
20 Removed: { #category : #accessing }
21 Removed: MFTWeightDialog >> addOrEdit [
22 Removed:
23 Removed: ^ addOrEdit
24 Removed: ]
25 Removed:
26 Removed: { #category : #accessing }
27 Removed: MFTWeightDialog >> addOrEdit: aRole [
28 Removed:
29 Removed: addOrEdit := aRole
30 Removed: ]
31 Removed:
32 10 { #category : #initialization }
33 11 MFTWeightDialog >> initialize [
34 12
@@ -40,7 +18,7 @@
40 18 { #category : #rendering }
41 19 MFTWeightDialog >> renderContentOn: html [
42 20
43 Removed: html container: [
21 Added: self renderDialogOn: html with: [
44 22 html heading: self addOrEdit asCapitalizedPhrase , ' weight'.
45 23 html form with: [
46 24 self renderDateInputOn: html.
@@ -61,8 +39,9 @@
61 39 { #category : #rendering }
62 40 MFTWeightDialog >> renderSubmitWeightOn: html [
63 41
64 Removed: html formGroup: [
65 Removed: html buttonGroup with: [
42 Added: html buttonGroup
43 Added: class: 'mt-3';
44 Added: with: [
66 45 html formButton
67 46 bePrimary;
68 47 beSubmit;
@@ -71,7 +50,7 @@
71 50 html formButton
72 51 beSecondary;
73 52 callback: [ self answer: nil ];
74 Removed: with: 'Cancel' ] ]
53 Added: with: 'Cancel' ]
75 54 ]
76 55
77 56 { #category : #rendering }
src/MyFitnessTracker/MFTWeightVsTimeGraph.class.st
index 00000000..602ace78 000000..100644
@@ -0,0 +1,47 @@
1 Added: Class {
2 Added: #name : #MFTWeightVsTimeGraph,
3 Added: #superclass : #MFTGraph,
4 Added: #category : #'MyFitnessTracker-Components'
5 Added: }
6 Added:
7 Added: { #category : #initialization }
8 Added: MFTWeightVsTimeGraph >> initialize [
9 Added:
10 Added: super initialize.
11 Added: labels := self session user weights collect: [ :weight |
12 Added: weight date yyyymmdd ].
13 Added: data := self session user weights collect: #value
14 Added: ]
15 Added:
16 Added: { #category : #rendering }
17 Added: MFTWeightVsTimeGraph >> renderContentOn: html [
18 Added:
19 Added: html canvas
20 Added: id: 'weightChart';
21 Added: height: 1;
22 Added: width: 1.
23 Added: html script:
24 Added: 'var ctx = document.getElementById("weightChart").getContext("2d");
25 Added: var weightChart = new Chart(ctx, {
26 Added: type: "line",
27 Added: data: {
28 Added: labels: ' , labels asJavascript , ',
29 Added: datasets: [{
30 Added: label: "weight",
31 Added: data: ' , data asJavascript , ',
32 Added: backgroundColor: "rgba(255, 99, 132, 0.2)",
33 Added: borderColor: "rgba(255, 99, 132, 1)",
34 Added: borderWidth: 1
35 Added: }]
36 Added: },
37 Added: options: {
38 Added: scales: {
39 Added: yAxes: [{
40 Added: ticks: {
41 Added: beginAtZero: true
42 Added: }
43 Added: }]
44 Added: }
45 Added: }
46 Added: });'
47 Added: ]
src/MyFitnessTracker/MFTWeightsCard.class.st
index 00000000..23b871a9 000000..100644
@@ -0,0 +1,46 @@
1 Added: Class {
2 Added: #name : #MFTWeightsCard,
3 Added: #superclass : #MFTCard,
4 Added: #category : #'MyFitnessTracker-Components'
5 Added: }
6 Added:
7 Added: { #category : #adding }
8 Added: MFTWeightsCard >> addWeightDialog [
9 Added:
10 Added: | newWeight |
11 Added: newWeight := self session pageManager activePage call:
12 Added: MFTWeightDialog new.
13 Added: newWeight value ifNotNil: [ self session user weights add: newWeight ]
14 Added: ]
15 Added:
16 Added: { #category : #rendering }
17 Added: MFTWeightsCard >> renderContentOn: html [
18 Added:
19 Added: self renderCardOn: html with: [
20 Added: html cardHeader: 'Weights'.
21 Added: html cardBody: [
22 Added: html paragraph: 'Great job logging your weight '
23 Added: , self session user weights size asString , ' times!'.
24 Added: html form
25 Added: class: 'mb-3';
26 Added: with: [
27 Added: html buttonGroup
28 Added: beSmall;
29 Added: with: [
30 Added: html outlineButton
31 Added: bePrimary;
32 Added: callback: [ self addWeightDialog ];
33 Added: with: 'Log weight'.
34 Added: html outlineButton
35 Added: beSecondary;
36 Added: callback: [ self viewAnalytics ];
37 Added: with: 'View progress' ] ].
38 Added: html render: MFTWeightVsTimeGraph new ] ]
39 Added: ]
40 Added:
41 Added: { #category : #'as yet unclassified' }
42 Added: MFTWeightsCard >> viewAnalytics [
43 Added:
44 Added: self session pageManager activePage call:
45 Added: (MFTAnalyticsPage for: #weights)
46 Added: ]
src/MyFitnessTracker/MFTWeightsListPage.class.st
index 314522b7..d03451d0 100644..100644
@@ -72,10 +72,10 @@
72 72 html formButton
73 73 bePrimary;
74 74 callback: [ self addWeight ];
75 Removed: with: 'Add weight'.
75 Added: with: 'Log weight'.
76 76 html formButton
77 77 beSecondary;
78 Removed: callback: [ self ];
78 Added: callback: [ self viewAnalytics ];
79 79 with: 'View progress' ] ]
80 80 ]
81 81
@@ -83,7 +83,8 @@
83 83 MFTWeightsListPage >> renderContentOn: html [
84 84
85 85 weights sort: [ :a :b | a date > b date ].
86 Removed: html container: [
86 Added:
87 Added: self renderPageOn: html with: [
87 88 weights ifEmpty: [
88 89 html jumbotron: [ html heading: self randomWittyCTA ] ].
89 90 self renderActionsButtonGroupOn: html.
@@ -126,6 +127,12 @@
126 127 self renderWeightActionsButtonGroupOn: html at: i ].
127 128 html tableData: weight date.
128 129 html tableData: weight value ] ] ]
130 Added: ]
131 Added:
132 Added: { #category : #adding }
133 Added: MFTWeightsListPage >> viewAnalytics [
134 Added:
135 Added: self call: (MFTAnalyticsPage for: #weights)
129 136 ]
130 137
131 138 { #category : #accessing }
src/MyFitnessTracker/MFTWidget.class.st
index 7148628b..8886e47b 100644..100644
@@ -2,53 +2,22 @@
2 2 #name : #MFTWidget,
3 3 #superclass : #SBSComponent,
4 4 #instVars : [
5 Removed: 'title',
6 Removed: 'type'
5 Added: 'title'
7 6 ],
8 7 #category : #'MyFitnessTracker-Components'
9 8 }
10 9
11 10 { #category : #accessing }
12 Removed: MFTWidget class >> default [
13 Removed:
14 Removed: ^ self new
15 Removed: title: 'Default ' , self name;
16 Removed: yourself
17 Removed: ]
18 Removed:
19 Removed: { #category : #'accessing - structure variables' }
20 Removed: MFTWidget class >> type: aType [
21 Removed:
22 Removed: ^ self new
23 Removed: type: aType;
24 Removed: yourself
25 Removed: ]
26 Removed:
27 Removed: { #category : #'accessing - structure variables' }
28 Removed: MFTWidget class >> type: aType title: aString [
29 Removed:
30 Removed: ^ self new
31 Removed: type: aType;
32 Removed: title: aString;
33 Removed: yourself
34 Removed: ]
35 Removed:
36 Removed: { #category : #accessing }
37 11 MFTWidget >> defaultDescription [
38 12
39 Removed: ^ {
40 Removed: 'I am a'.
41 Removed: self class name.
42 Removed: 'of type'.
43 Removed: (type , '.') } joinUsing: ' '
13 Added: ^ 'I am a ' , self class name
44 14 ]
45 15
46 16 { #category : #initialization }
47 17 MFTWidget >> initialize [
48 18
49 19 super initialize.
50 Removed: title := String new.
51 Removed: type := #default
20 Added: title := String new
52 21 ]
53 22
54 23 { #category : #rendering }
@@ -59,33 +28,17 @@
59 28 html heading
60 29 level5;
61 30 with: self title ].
62 Removed: html paragraph: ({
63 Removed: 'I am a'.
64 Removed: self class name.
65 Removed: 'of type'.
66 Removed: (type , '.') } joinUsing: ' ') ]
31 Added: html paragraph: self defaultDescription ]
67 32 ]
68 33
69 34 { #category : #accessing }
70 35 MFTWidget >> title [
71 36
72 Removed: ^ title ifEmpty: [ type asCapitalizedPhrase ]
37 Added: ^ title
73 38 ]
74 39
75 40 { #category : #accessing }
76 41 MFTWidget >> title: aString [
77 42
78 43 title := aString
79 Removed: ]
80 Removed:
81 Removed: { #category : #accessing }
82 Removed: MFTWidget >> type [
83 Removed:
84 Removed: ^ type
85 Removed: ]
86 Removed:
87 Removed: { #category : #accessing }
88 Removed: MFTWidget >> type: aTypeSymbol [
89 Removed:
90 Removed: type := aTypeSymbol
91 44 ]
src/MyFitnessTracker/MFTWorkoutDialog.class.st
index c0eab81c..4c81c912 100644..100644
@@ -1,24 +1,13 @@
1 1 Class {
2 2 #name : #MFTWorkoutDialog,
3 Removed: #superclass : #SBSComponent,
3 Added: #superclass : #MFTDialog,
4 4 #instVars : [
5 5 'workout',
6 Removed: 'exercises',
7 Removed: 'addOrEdit'
6 Added: 'exercises'
8 7 ],
9 8 #category : #'MyFitnessTracker-Dialogs'
10 9 }
11 10
12 Removed: { #category : #actions }
13 Removed: MFTWorkoutDialog class >> edit: aMFTWorkout [
14 Removed:
15 Removed: ^ self new
16 Removed: workout: aMFTWorkout;
17 Removed: exercises: aMFTWorkout exercises;
18 Removed: addOrEdit: #edit;
19 Removed: yourself
20 Removed: ]
21 Removed:
22 11 { #category : #adding }
23 12 MFTWorkoutDialog >> addExercise [
24 13
@@ -27,18 +16,6 @@
27 16 exercise ifNotNil: [ exercises add: exercise ]
28 17 ]
29 18
30 Removed: { #category : #accessing }
31 Removed: MFTWorkoutDialog >> addOrEdit [
32 Removed:
33 Removed: ^ addOrEdit
34 Removed: ]
35 Removed:
36 Removed: { #category : #accessing }
37 Removed: MFTWorkoutDialog >> addOrEdit: aRole [
38 Removed:
39 Removed: addOrEdit := aRole
40 Removed: ]
41 Removed:
42 19 { #category : #'as yet unclassified' }
43 20 MFTWorkoutDialog >> editExerciseAt: i [
44 21
@@ -90,9 +67,8 @@
90 67 { #category : #rendering }
91 68 MFTWorkoutDialog >> renderContentOn: html [
92 69
93 Removed: html container: [
94 Removed: html heading: self addOrEdit asCapitalizedPhrase , ' workout for '
95 Removed: , workout date asString.
70 Added: self renderDialogOn: html with: [
71 Added: html heading: self addOrEdit asCapitalizedPhrase , ' workout'.
96 72 html form: [
97 73 self renderDateInputOn: html.
98 74 self renderExercisesTableOn: html.
@@ -181,8 +157,9 @@
181 157 { #category : #rendering }
182 158 MFTWorkoutDialog >> renderSubmitWorkoutOn: html [
183 159
184 Removed: html formGroup: [
185 Removed: html buttonGroup: [
160 Added: html buttonGroup
161 Added: class: 'mt-3';
162 Added: with: [
186 163 html formButton
187 164 bePrimary;
188 165 beSubmit;
@@ -191,7 +168,7 @@
191 168 html formButton
192 169 beSecondary;
193 170 callback: [ self answer: nil ];
194 Removed: with: 'Cancel' ] ]
171 Added: with: 'Cancel' ]
195 172 ]
196 173
197 174 { #category : #rendering }
src/MyFitnessTracker/MFTWorkoutsCard.class.st
index 00000000..c91f83b0 000000..100644
@@ -0,0 +1,44 @@
1 Added: Class {
2 Added: #name : #MFTWorkoutsCard,
3 Added: #superclass : #MFTCard,
4 Added: #category : #'MyFitnessTracker-Components'
5 Added: }
6 Added:
7 Added: { #category : #adding }
8 Added: MFTWorkoutsCard >> addWorkoutDialog [
9 Added:
10 Added: self session pageManager activePage call: MFTWorkoutDialog new
11 Added: ]
12 Added:
13 Added: { #category : #rendering }
14 Added: MFTWorkoutsCard >> renderContentOn: html [
15 Added:
16 Added: self renderCardOn: html with: [
17 Added: html cardHeader: 'Workouts'.
18 Added: html cardBody: [
19 Added: html paragraph:
20 Added: 'Great job logging ' , self session user workouts size asString
21 Added: , ' workouts!'.
22 Added: html form
23 Added: class: 'my-3';
24 Added: with: [
25 Added: html buttonGroup
26 Added: beSmall;
27 Added: with: [
28 Added: html outlineButton
29 Added: bePrimary;
30 Added: callback: [ self addWorkoutDialog ];
31 Added: with: 'Log workout'.
32 Added: html outlineButton
33 Added: beSecondary;
34 Added: callback: [ self viewAnalytics ];
35 Added: with: 'View progress' ] ].
36 Added: html render: MFTRepsVsTimeGraph new ] ]
37 Added: ]
38 Added:
39 Added: { #category : #adding }
40 Added: MFTWorkoutsCard >> viewAnalytics [
41 Added:
42 Added: self session pageManager activePage call:
43 Added: (MFTAnalyticsPage for: #workouts)
44 Added: ]
src/MyFitnessTracker/MFTWorkoutsListPage.class.st
index 0b170819..d750ced5 100644..100644
@@ -37,6 +37,7 @@
37 37 | placeholderWorkouts workout1 workout2 workout3 |
38 38 placeholderWorkouts := OrderedCollection new.
39 39 workout1 := MFTWorkout new
40 Added: date: (Date fromString: '01/03/2023');
40 41 primaryMuscle: MFTMuscleGroup chest;
41 42 exercises: (OrderedCollection
42 43 with: (MFTExercise cableRow sets: {
@@ -53,7 +54,7 @@
53 54 (MFTSet reps: 6 rpe: 8).
54 55 (MFTSet reps: 8 rpe: 10) })).
55 56 workout2 := MFTWorkout new
56 Removed: date: (Date fromString: '04/20/2023');
57 Added: date: (Date fromString: '01/02/2023');
57 58 exercises: (OrderedCollection
58 59 with: (MFTExercise cableRow sets: {
59 60 (MFTSet reps: 8 rpe: 6).
@@ -82,7 +83,7 @@
82 83
83 84 | newWorkout |
84 85 newWorkout := self call: MFTWorkoutDialog new.
85 Removed: newWorkout ifNotNil: [ workouts addFirst: newWorkout ]
86 Added: newWorkout ifNotNil: [ workouts add: newWorkout ]
86 87 ]
87 88
88 89 { #category : #hooks }
@@ -142,14 +143,19 @@
142 143 html formButton
143 144 bePrimary;
144 145 callback: [ self addWorkout ];
145 Removed: with: 'Add workout' ] ]
146 Added: with: 'Log workout'.
147 Added: html formButton
148 Added: beSecondary;
149 Added: callback: [ self viewAnalytics ];
150 Added: with: 'View progress' ] ]
146 151 ]
147 152
148 153 { #category : #rendering }
149 154 MFTWorkoutsListPage >> renderContentOn: html [
150 155
151 156 workouts sort: [ :a :b | a date > b date ].
152 Removed: html container: [
157 Added:
158 Added: self renderPageOn: html with: [
153 159 workouts ifEmpty: [
154 160 html jumbotron: [ html heading: self randomWittyCTA ] ].
155 161 self renderActionsButtonGroupOn: html.
@@ -206,6 +212,12 @@
206 212 MFTWorkoutsListPage >> userWorkouts [
207 213
208 214 self session db selectWorkoutsWhereUser: self session user
215 Added: ]
216 Added:
217 Added: { #category : #adding }
218 Added: MFTWorkoutsListPage >> viewAnalytics [
219 Added:
220 Added: self call: (MFTAnalyticsPage for: #workouts)
209 221 ]
210 222
211 223 { #category : #accessing }