[Pharo] Personal fitness tracker web app.
Implemented cards, graphs, dialogs, the 'about' page... Very productive today!
Changed files
- src/MyFitnessTracker/MFTAboutPage.class.st
- src/MyFitnessTracker/MFTAnalyticsPage.class.st
- src/MyFitnessTracker/MFTCard.class.st
- src/MyFitnessTracker/MFTDialog.class.st
- src/MyFitnessTracker/MFTFooterComponent.class.st
- src/MyFitnessTracker/MFTGraph.class.st
- src/MyFitnessTracker/MFTHeaderComponent.class.st
- src/MyFitnessTracker/MFTLearnPage.class.st
- src/MyFitnessTracker/MFTMainNavigationBarComponent.class.st
- src/MyFitnessTracker/MFTMainPublicCard.class.st
- src/MyFitnessTracker/MFTMainUserCard.class.st
- src/MyFitnessTracker/MFTMusclesArticle.class.st
- src/MyFitnessTracker/MFTPage.class.st
- src/MyFitnessTracker/MFTPageManager.class.st
- src/MyFitnessTracker/MFTProfilePage.class.st
- src/MyFitnessTracker/MFTPromisesCard.class.st
- src/MyFitnessTracker/MFTPublicLandingPage.class.st
- src/MyFitnessTracker/MFTRegisterProspectEmailHeader.class.st
- src/MyFitnessTracker/MFTRepsVsTimeGraph.class.st
- src/MyFitnessTracker/MFTRootComponent.class.st
- src/MyFitnessTracker/MFTScreenComponent.class.st
- src/MyFitnessTracker/MFTUserLandingPage.class.st
- src/MyFitnessTracker/MFTWeightDialog.class.st
- src/MyFitnessTracker/MFTWeightVsTimeGraph.class.st
- src/MyFitnessTracker/MFTWeightsCard.class.st
- src/MyFitnessTracker/MFTWeightsListPage.class.st
- src/MyFitnessTracker/MFTWidget.class.st
- src/MyFitnessTracker/MFTWorkoutDialog.class.st
- src/MyFitnessTracker/MFTWorkoutsCard.class.st
- src/MyFitnessTracker/MFTWorkoutsListPage.class.st
src/MyFitnessTracker/MFTAboutPage.class.st
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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 }