[Pharo] Personal fitness tracker web app.
Checkpoint
Changed files
- src/MyFitnessTracker/MFTHeaderProspectComponent.class.st
- src/MyFitnessTracker/MFTLoginRegisterLogoutComponent.class.st
- src/MyFitnessTracker/MFTMainNavigationBarComponent.class.st
- src/MyFitnessTracker/MFTMuscle.class.st
- src/MyFitnessTracker/MFTPageManager.class.st
- src/MyFitnessTracker/MFTPublicLandingPage.class.st
- src/MyFitnessTracker/MFTRegisterProspectHaederComponent.class.st
- src/MyFitnessTracker/MFTSQLite3Database.class.st
- src/MyFitnessTracker/MFTScreenComponent.class.st
- src/MyFitnessTracker/MFTSession.class.st
- src/MyFitnessTracker/MFTUser.class.st
- src/MyFitnessTracker/MFTUserLandingPage.class.st
- src/MyFitnessTracker/MFTWeight.class.st
- src/MyFitnessTracker/MFTWeightDialog.class.st
- src/MyFitnessTracker/MFTWeightsListPage.class.st
- src/MyFitnessTracker/MFTWorkout.class.st
- src/MyFitnessTracker/MFTWorkoutDialog.class.st
- src/MyFitnessTracker/MFTWorkoutsListPage.class.st
src/MyFitnessTracker/MFTHeaderProspectComponent.class.st
@@ -1,111 +0,0 @@
1
Removed:
Class {
2
Removed:
#name : #MFTHeaderProspectComponent,
3
Removed:
#superclass : #SBSComponent,
4
Removed:
#instVars : [
5
Removed:
'email',
6
Removed:
'localPart',
7
Removed:
'domain'
8
Removed:
],
9
Removed:
#category : #'MyFitnessTracker-Components'
10
Removed:
}
11
Removed:
12
Removed:
{ #category : #adding }
13
Removed:
MFTHeaderProspectComponent >> 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:
MFTHeaderProspectComponent >> renderContentOn: html [
27
Removed:
28
Removed:
self session user isLoggedIn ifTrue: [ "If a user is logged in, the CTA banner is not displayed."
29
Removed:
^ nil ].
30
Removed:
email
31
Removed:
ifNil: [
32
Removed:
html alert
33
Removed:
beWarning;
34
Removed:
beDismissible;
35
Removed:
marginBottom: 0;
36
Removed:
with: [
37
Removed:
html closeButton dataDismiss: 'alert'.
38
Removed:
html alertHeading: 'This app is still in beta.'.
39
Removed:
html paragraph: [
40
Removed:
html
41
Removed:
text:
42
Removed:
'My Fitness Tracker is still under heavy development, with improvements being made daily.';
43
Removed:
space;
44
Removed:
text: 'I can''t wait to share the finished product with you!' ].
45
Removed:
html form with: [
46
Removed:
html label
47
Removed:
style: 'margin-right: 1em';
48
Removed:
with: [ html strong: 'Want to know when version 1.0 is out?' ].
49
Removed:
html formButton
50
Removed:
beInfo;
51
Removed:
callback: [ self addUserEmail ];
52
Removed:
with: 'E-mail me!' ] ] ]
53
Removed:
ifNotNil: [
54
Removed:
html alert
55
Removed:
beSuccess;
56
Removed:
beDismissible;
57
Removed:
marginBottom: 0;
58
Removed:
with: [
59
Removed:
html closeButton dataDismiss: 'alert'.
60
Removed:
html text: email , ' will be notified when v1 is released!' ] ]
61
Removed:
]
62
Removed:
63
Removed:
{ #category : #rendering }
64
Removed:
MFTHeaderProspectComponent >> renderEmailInputModalButtonOn: html [
65
Removed:
66
Removed:
^ html formButton
67
Removed:
bePrimary;
68
Removed:
beSmall;
69
Removed:
dataToggle: 'modal';
70
Removed:
dataTarget: '#emailInputModal';
71
Removed:
with: 'E-mail me!'
72
Removed:
]
73
Removed:
74
Removed:
{ #category : #rendering }
75
Removed:
MFTHeaderProspectComponent >> renderEmailInputModalOn: html [
76
Removed:
77
Removed:
html modal
78
Removed:
id: 'emailInputModal';
79
Removed:
fade;
80
Removed:
with: [
81
Removed:
html modalDialog
82
Removed:
beCentered;
83
Removed:
with: [
84
Removed:
html modalContent: [
85
Removed:
html form: [
86
Removed:
html modalHeader: [
87
Removed:
html modalTitle
88
Removed:
level5;
89
Removed:
with:
90
Removed:
'You''ll be the first to know when My Fitness Tracker is ready to use.'.
91
Removed:
html modalCloseButton ].
92
Removed:
93
Removed:
html modalBody: [
94
Removed:
html inputGroup: [
95
Removed:
html formTextInput
96
Removed:
callback: [ :value | localPart := value ];
97
Removed:
placeholder: 'my-user'.
98
Removed:
html inputGroupAppend: [ html inputGroupText: '@' ].
99
Removed:
html formTextInput
100
Removed:
callback: [ :value | domain := value ];
101
Removed:
placeholder: 'example.com' ] ].
102
Removed:
103
Removed:
html modalFooter: [
104
Removed:
html paragraph:
105
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.'.
106
Removed:
html formButton
107
Removed:
bePrimary;
108
Removed:
beSubmit;
109
Removed:
callback: [ email := localPart , '@' , domain ];
110
Removed:
with: 'Confirm' ] ] ] ] ]
111
Removed:
]
src/MyFitnessTracker/MFTLoginRegisterLogoutComponent.class.st
@@ -25,11 +25,10 @@
25
25
passwordHash := existingUser
26
26
at: #password_hash
27
27
ifAbsent: [ String empty ].
28
Removed:
29
28
aMFTUser passwordHash = passwordHash
30
Removed:
ifTrue: [ self login: (MFTUser newFromDictionary: existingUser) ]
31
Removed:
ifFalse: [ "TODO: user should be prompted to retry logging in."
32
Removed:
^ nil ]
29
Added:
ifTrue: [
30
Added:
self session login: (MFTUser newFromDictionary: existingUser) ]
31
Added:
ifFalse: [ self inform: 'Login failed' ]
33
32
]
34
33
35
34
{ #category : #initialization }
@@ -37,19 +36,6 @@
37
36
38
37
super initialize.
39
38
user := MFTUser new
40
Removed:
]
41
Removed:
42
Removed:
{ #category : #login }
43
Removed:
MFTLoginRegisterLogoutComponent >> login: aMFTUser [
44
Removed:
45
Removed:
| db |
46
Removed:
db := MFTSQLite3Database newFromDefault.
47
Removed:
db connection open.
48
Removed:
self session db: db.
49
Removed:
self session db updateLoginDateAndTime: aMFTUser.
50
Removed:
self session user: aMFTUser.
51
Removed:
self session user isLoggedIn: true.
52
Removed:
self session mainPages at: 1 put: MFTUserLandingPage new
53
39
]
54
40
55
41
{ #category : #'as yet unclassified' }
src/MyFitnessTracker/MFTMainNavigationBarComponent.class.st
@@ -36,7 +36,11 @@
36
36
{ #category : #rendering }
37
37
MFTMainNavigationBarComponent >> renderNavigationBarItemsOn: html [
38
38
39
Removed:
html navigationBarBrand: 'MyFitnessTracker'.
39
Added:
| pm |
40
Added:
pm := self session pageManager.
41
Added:
html navigationBarBrand
42
Added:
callback: [ pm activePage: (pm mainPages at: 1) ];
43
Added:
with: 'MyFitnessTracker'.
40
44
"The toggler that is only visible when reducing the width of screen"
41
45
html navigationBarToggler
42
46
beButtonType;
@@ -47,23 +51,13 @@
47
51
id: 'navbarCollapsed';
48
52
with: [
49
53
html navigationBarNavigation: [
50
Removed:
self session mainPages do: [ :page |
54
Added:
pm mainPages do: [ :page |
51
55
html navigationItem
52
Removed:
beActiveIf: [ self session activePage = page ];
56
Added:
beActiveIf: [ pm activePage = page ];
53
57
with: [
54
58
html navigationLink
55
Removed:
callback: [ self session activePage: page ];
56
Removed:
with: page title asCapitalizedPhrase ] ].
57
Removed:
html navigationItem dropdown with: [
58
Removed:
html navigationLink
59
Removed:
dropdownToggle;
60
Removed:
dataToggle: 'dropdown';
61
Removed:
with: 'Learn'.
62
Removed:
html dropdownMenu
63
Removed:
attributeAt: 'aria-labelledby' put: 'navbarDropdown';
64
Removed:
with: [
65
Removed:
html dropdownItem: 'Exercises'.
66
Removed:
html dropdownItem: 'Muscles' ] ] ].
59
Added:
callback: [ pm activePage: page ];
60
Added:
with: page title asCapitalizedPhrase ] ] ].
67
61
html div
68
62
class: 'ms-auto';
69
63
with: [ html render: loginRegisterLogout ] ]
src/MyFitnessTracker/MFTMuscle.class.st
@@ -177,7 +177,7 @@
177
177
178
178
| muscleNameFromSender |
179
179
muscleNameFromSender := (thisContext sender selector splitCamelCase
180
Removed:
collect: #asLowercase) joinUsing: ' '.
180
Added:
collect: #asLowercase) joinUsing: Character space.
181
181
^ self new
182
182
latinName: muscleNameFromSender;
183
183
yourself
src/MyFitnessTracker/MFTPageManager.class.st
@@ -0,0 +1,103 @@
1
Added:
Class {
2
Added:
#name : #MFTPageManager,
3
Added:
#superclass : #Object,
4
Added:
#instVars : [
5
Added:
'mainNavigationBar',
6
Added:
'mainPages',
7
Added:
'activePage'
8
Added:
],
9
Added:
#category : #MyFitnessTracker
10
Added:
}
11
Added:
12
Added:
{ #category : #accessing }
13
Added:
MFTPageManager >> activePage [
14
Added:
15
Added:
^ activePage
16
Added:
]
17
Added:
18
Added:
{ #category : #accessing }
19
Added:
MFTPageManager >> activePage: aMFTComponent [
20
Added:
21
Added:
activePage := aMFTComponent
22
Added:
]
23
Added:
24
Added:
{ #category : #'as yet unclassified' }
25
Added:
MFTPageManager >> defaultFooter [
26
Added:
27
Added:
^ MFTFooterComponent new
28
Added:
]
29
Added:
30
Added:
{ #category : #'as yet unclassified' }
31
Added:
MFTPageManager >> defaultPublicPages [
32
Added:
33
Added:
^ OrderedCollection newFromArray: {
34
Added:
MFTPublicLandingPage new.
35
Added:
MFTWorkoutsListPage placeholder.
36
Added:
MFTWeightsListPage placeholder.
37
Added:
MFTLearnPage new.
38
Added:
MFTProfilePage new }
39
Added:
]
40
Added:
41
Added:
{ #category : #initialization }
42
Added:
MFTPageManager >> initialize [
43
Added:
44
Added:
mainNavigationBar := MFTMainNavigationBarComponent new.
45
Added:
mainPages := self defaultPublicPages.
46
Added:
activePage := mainPages first
47
Added:
]
48
Added:
49
Added:
{ #category : #'as yet unclassified' }
50
Added:
MFTPageManager >> loadUserLandingPage [
51
Added:
52
Added:
mainPages at: 1 put: MFTUserLandingPage new
53
Added:
]
54
Added:
55
Added:
{ #category : #'as yet unclassified' }
56
Added:
MFTPageManager >> loadUserPages [
57
Added:
58
Added:
self loadUserLandingPage.
59
Added:
self loadUserWorkouts.
60
Added:
self loadUserWeights.
61
Added:
]
62
Added:
63
Added:
{ #category : #'as yet unclassified' }
64
Added:
MFTPageManager >> loadUserWeights [
65
Added:
66
Added:
(mainPages at: 3) loadUserWeights
67
Added:
]
68
Added:
69
Added:
{ #category : #'as yet unclassified' }
70
Added:
MFTPageManager >> loadUserWorkouts [
71
Added:
72
Added:
(mainPages at: 2) loadUserWorkouts
73
Added:
]
74
Added:
75
Added:
{ #category : #accessing }
76
Added:
MFTPageManager >> mainNavigationBar [
77
Added:
78
Added:
^ mainNavigationBar
79
Added:
]
80
Added:
81
Added:
{ #category : #accessing }
82
Added:
MFTPageManager >> mainNavigationBar: anObject [
83
Added:
84
Added:
mainNavigationBar := anObject
85
Added:
]
86
Added:
87
Added:
{ #category : #accessing }
88
Added:
MFTPageManager >> mainPages [
89
Added:
90
Added:
^ mainPages
91
Added:
]
92
Added:
93
Added:
{ #category : #accessing }
94
Added:
MFTPageManager >> mainPages: aCollection [
95
Added:
96
Added:
mainPages := aCollection
97
Added:
]
98
Added:
99
Added:
{ #category : #'as yet unclassified' }
100
Added:
MFTPageManager >> registerProspectHeader [
101
Added:
102
Added:
^ MFTRegisterProspectHaederComponent new
103
Added:
]
src/MyFitnessTracker/MFTPublicLandingPage.class.st
@@ -16,6 +16,8 @@
16
16
17
17
html container: [
18
18
self renderMainCardOn: html.
19
Added:
self renderWorkoutsGraphChartOn: html.
20
Added:
self renderWeightsGraphChartOn: html.
19
21
self renderPromisesCardOn: html ]
20
22
]
21
23
@@ -36,7 +38,8 @@
36
38
beLarge;
37
39
class: 'mt-3';
38
40
callback: [
39
Removed:
self session activePage: (self session mainPages at: 2) ];
41
Added:
self session pageManager activePage:
42
Added:
(self session pageManager mainPages at: 2) ];
40
43
with: 'Log a workout' ] ] ]
41
44
]
42
45
@@ -46,10 +49,100 @@
46
49
html card
47
50
marginTopAndBottom: 3;
48
51
with: [
49
Removed:
html cardHeader: 'We promise you...'.
52
Added:
html cardHeader: 'My promise to all account owners...'.
50
53
html cardBody: [
51
54
html unorderedList: [
52
55
html listItem: 'All features shall always be entirely free.'.
53
56
html listItem: 'No user data shall ever be sold.'.
54
57
html listItem: '.' ] ] ]
58
Added:
]
59
Added:
60
Added:
{ #category : #rendering }
61
Added:
MFTPublicLandingPage >> renderWeightsGraphChartOn: html [
62
Added:
63
Added:
html card with: [
64
Added:
html cardHeader: 'Weight'.
65
Added:
html cardBody: [ self renderWeightsGraphOn: html ] ]
66
Added:
]
67
Added:
68
Added:
{ #category : #rendering }
69
Added:
MFTPublicLandingPage >> renderWeightsGraphOn: html [
70
Added:
71
Added:
| labels data |
72
Added:
labels := self session weights collect: #date.
73
Added:
data := #( 10 15 40 25 ).
74
Added:
html canvas
75
Added:
id: 'weightChart';
76
Added:
width: 100;
77
Added:
height: 30.
78
Added:
html script url: 'https://cdn.jsdelivr.net/npm/chart.js'.
79
Added:
html script:
80
Added:
'var ctx = document.getElementById("weightChart").getContext("2d");
81
Added:
var weightChart = new Chart(ctx, {
82
Added:
type: "bar",
83
Added:
data: {
84
Added:
labels: ' , labels asJavascript , ',
85
Added:
datasets: [{
86
Added:
label: "# of Votes",
87
Added:
data: ' , data asJavascript , ',
88
Added:
backgroundColor: "rgba(255, 99, 132, 0.2)",
89
Added:
borderColor: "rgba(255, 99, 132, 1)",
90
Added:
borderWidth: 1
91
Added:
}]
92
Added:
},
93
Added:
options: {
94
Added:
scales: {
95
Added:
yAxes: [{
96
Added:
ticks: {
97
Added:
beginAtZero: true
98
Added:
}
99
Added:
}]
100
Added:
}
101
Added:
}
102
Added:
});'
103
Added:
]
104
Added:
105
Added:
{ #category : #rendering }
106
Added:
MFTPublicLandingPage >> renderWorkoutsGraphChartOn: html [
107
Added:
108
Added:
html card with: [
109
Added:
html cardHeader: 'Workouts'.
110
Added:
html cardBody: [ self renderWorkoutsGraphOn: html ] ]
111
Added:
]
112
Added:
113
Added:
{ #category : #rendering }
114
Added:
MFTPublicLandingPage >> renderWorkoutsGraphOn: html [
115
Added:
116
Added:
| labels data |
117
Added:
labels := #( 'January' 'February' 'March' 'April' ).
118
Added:
data := #( 10 15 40 25 ).
119
Added:
html canvas
120
Added:
id: 'workoutChart';
121
Added:
width: 100;
122
Added:
height: 30.
123
Added:
html script url: 'https://cdn.jsdelivr.net/npm/chart.js'.
124
Added:
html script:
125
Added:
'var ctx = document.getElementById("workoutChart").getContext("2d");
126
Added:
var workoutChart = new Chart(ctx, {
127
Added:
type: "bar",
128
Added:
data: {
129
Added:
labels: ' , labels asJavascript , ',
130
Added:
datasets: [{
131
Added:
label: "# of Votes",
132
Added:
data: ' , data asJavascript , ',
133
Added:
backgroundColor: "rgba(255, 99, 132, 0.2)",
134
Added:
borderColor: "rgba(255, 99, 132, 1)",
135
Added:
borderWidth: 1
136
Added:
}]
137
Added:
},
138
Added:
options: {
139
Added:
scales: {
140
Added:
yAxes: [{
141
Added:
ticks: {
142
Added:
beginAtZero: true
143
Added:
}
144
Added:
}]
145
Added:
}
146
Added:
}
147
Added:
});'
55
148
]
src/MyFitnessTracker/MFTRegisterProspectHaederComponent.class.st
@@ -0,0 +1,109 @@
1
Added:
Class {
2
Added:
#name : #MFTRegisterProspectHaederComponent,
3
Added:
#superclass : #SBSComponent,
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:
MFTRegisterProspectHaederComponent >> 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:
MFTRegisterProspectHaederComponent >> 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:
MFTRegisterProspectHaederComponent >> 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:
MFTRegisterProspectHaederComponent >> 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/MFTSQLite3Database.class.st
@@ -8,6 +8,12 @@
8
8
}
9
9
10
10
{ #category : #'as yet unclassified' }
11
Added:
MFTSQLite3Database class >> dateTimeNow [
12
Added:
13
Added:
^ DateAndTime now asUTC rounded
14
Added:
]
15
Added:
16
Added:
{ #category : #'as yet unclassified' }
11
17
MFTSQLite3Database class >> defaultConnection [
12
18
13
19
^ self newFromDefault connection
@@ -45,13 +51,30 @@
45
51
"date_of_birth" TEXT,
46
52
PRIMARY KEY("id" AUTOINCREMENT)
47
53
)';
54
Added:
execute: 'CREATE TABLE IF NOT EXISTS "workouts" (
55
Added:
"id" INTEGER UNIQUE,
56
Added:
"username" TEXT NOT NULL,
57
Added:
"password_hash" BLOB NOT NULL,
58
Added:
"real_first_name" TEXT,
59
Added:
"real_last_name" TEXT,
60
Added:
"date_of_birth" TEXT,
61
Added:
PRIMARY KEY("id" AUTOINCREMENT)
62
Added:
)';
63
Added:
execute: 'CREATE TABLE IF NOT EXISTS "weights" (
64
Added:
"id" INTEGER UNIQUE,
65
Added:
"user_id" INTEGER NOT NULL,
66
Added:
"date" TEXT NOT NULL,
67
Added:
"value" NUMERIC NOT NULL,
68
Added:
PRIMARY KEY("id" AUTOINCREMENT)
69
Added:
)';
48
70
close
49
71
]
50
72
51
73
{ #category : #'class initialization' }
52
74
MFTSQLite3Database class >> initialize [
53
75
54
Removed:
defaultLocation := (Smalltalk imageDirectory / 'mft.sqlite3') fullName
76
Added:
defaultLocation := (Smalltalk imageDirectory / 'mft.sqlite3') fullName.
77
Added:
self ensureTablesExist.
55
78
]
56
79
57
80
{ #category : #'instance creation' }
@@ -60,6 +83,64 @@
60
83
^ self on: defaultLocation
61
84
]
62
85
86
Added:
{ #category : #'class initialization' }
87
Added:
MFTSQLite3Database class >> populateDefaultWeights [
88
Added:
89
Added:
self defaultConnection
90
Added:
open;
91
Added:
execute: 'INSERT INTO weights(user_id, date, value)
92
Added:
VALUES( ?, ?, ?)'
93
Added:
with: {
94
Added:
7.
95
Added:
DateAndTime yesterday asUTC rounded.
96
Added:
90.5 };
97
Added:
execute: 'INSERT INTO weights(user_id, date, value)
98
Added:
VALUES( ?, ?, ?)'
99
Added:
with: {
100
Added:
7.
101
Added:
DateAndTime today asUTC rounded.
102
Added:
101.3 };
103
Added:
execute: 'INSERT INTO weights(user_id, date, value)
104
Added:
VALUES( ?, ?, ?)'
105
Added:
with: {
106
Added:
7.
107
Added:
DateAndTime now asUTC rounded.
108
Added:
90.5 };
109
Added:
close
110
Added:
]
111
Added:
112
Added:
{ #category : #'class initialization' }
113
Added:
MFTSQLite3Database class >> populateDefaultWorkouts [
114
Added:
115
Added:
self defaultConnection
116
Added:
open;
117
Added:
execute:
118
Added:
'INSERT INTO workouts(user_id, date, primary_muscle, secondary_muscles)
119
Added:
VALUES( ?, ?, ?, ?)'
120
Added:
with: {
121
Added:
7.
122
Added:
DateAndTime yesterday asUTC rounded.
123
Added:
MFTMuscle quadricepsFemoris latinName.
124
Added:
MFTMuscle hamstring englishName };
125
Added:
execute:
126
Added:
'INSERT INTO workouts(user_id, date, primary_muscle, secondary_muscles)
127
Added:
VALUES( ?, ?, ?, ?)'
128
Added:
with: {
129
Added:
7.
130
Added:
self dateTimeNow.
131
Added:
MFTMuscle latissimusDorsi latinName.
132
Added:
MFTMuscle erectorSpinae latinName };
133
Added:
execute:
134
Added:
'INSERT INTO workouts(user_id, date, primary_muscle, secondary_muscles)
135
Added:
VALUES( ?, ?, ?, ?)'
136
Added:
with: {
137
Added:
7.
138
Added:
self dateTimeNow.
139
Added:
MFTMuscle trapezius latinName.
140
Added:
MFTMuscle brachioRadialis latinName };
141
Added:
close
142
Added:
]
143
Added:
63
144
{ #category : #'as yet unclassified' }
64
145
MFTSQLite3Database class >> registerNewUser: aMFTUser [
65
146
@@ -74,6 +155,21 @@
74
155
close
75
156
]
76
157
158
Added:
{ #category : #'as yet unclassified' }
159
Added:
MFTSQLite3Database class >> selectUser: id [
160
Added:
161
Added:
| connection user |
162
Added:
connection := self defaultConnection.
163
Added:
connection open.
164
Added:
user := (connection
165
Added:
execute: 'SELECT *
166
Added:
FROM users
167
Added:
WHERE id = ?'
168
Added:
value: id) onlyRow.
169
Added:
connection close.
170
Added:
^ user
171
Added:
]
172
Added:
77
173
{ #category : #setting }
78
174
MFTSQLite3Database class >> storeProspectEmail: anEmail [
79
175
@@ -86,6 +182,20 @@
86
182
]
87
183
88
184
{ #category : #'as yet unclassified' }
185
Added:
MFTSQLite3Database >> login: aMFTUser atDateAndTime: aDateAndTime [
186
Added:
187
Added:
connection
188
Added:
open;
189
Added:
execute: 'UPDATE users
190
Added:
SET last_login_date_time = ?
191
Added:
WHERE username = ?'
192
Added:
with: {
193
Added:
aDateAndTime.
194
Added:
aMFTUser username };
195
Added:
close
196
Added:
]
197
Added:
198
Added:
{ #category : #'as yet unclassified' }
89
199
MFTSQLite3Database >> select: attributes from: aTable [
90
200
91
201
^ connection execute:
@@ -93,21 +203,41 @@
93
203
]
94
204
95
205
{ #category : #'as yet unclassified' }
206
Added:
MFTSQLite3Database >> select: attributes from: aTable where: aCondition [
207
Added:
208
Added:
^ connection execute:
209
Added:
'SELECT ' , attributes , ' FROM ' , aTable asString , 'WHERE '
210
Added:
, aCondition
211
Added:
]
212
Added:
213
Added:
{ #category : #'as yet unclassified' }
96
214
MFTSQLite3Database >> selectAll: aTable [
97
215
98
216
^ self select: '*' from: aTable
99
217
]
100
218
101
219
{ #category : #'as yet unclassified' }
102
Removed:
MFTSQLite3Database >> updateLoginDateAndTime: aMFTUser [
220
Added:
MFTSQLite3Database >> selectWeightsWhereUser: aMFTUser [
103
221
104
Removed:
connection
105
Removed:
open;
106
Removed:
execute: 'UPDATE users
107
Removed:
SET last_login_date_time = ?
108
Removed:
WHERE username = ?'
109
Removed:
with: {
110
Removed:
DateAndTime now asUTC rounded.
111
Removed:
aMFTUser username };
112
Removed:
close
222
Added:
^ (self class defaultConnection
223
Added:
open;
224
Added:
execute: 'SELECT weights.*
225
Added:
FROM weights
226
Added:
INNER JOIN users ON weights.user_id = users.id
227
Added:
WHERE users.username = ?'
228
Added:
value: 'foobar123') rows collect: [ :each |
229
Added:
MFTWeight newFromDictionary: each asDictionary ]
230
Added:
]
231
Added:
232
Added:
{ #category : #'as yet unclassified' }
233
Added:
MFTSQLite3Database >> selectWorkoutsWhereUser: aMFTUser [
234
Added:
235
Added:
^ (self class defaultConnection
236
Added:
open;
237
Added:
execute: 'SELECT workouts.*
238
Added:
FROM workouts
239
Added:
INNER JOIN users ON workouts.user_id = users.id
240
Added:
WHERE users.username = ?'
241
Added:
value: 'foobar123') rows collect: [ :each |
242
Added:
MFTWorkout newFromDictionary: each asDictionary ]
113
243
]
src/MyFitnessTracker/MFTScreenComponent.class.st
@@ -2,8 +2,8 @@
2
2
#name : #MFTScreenComponent,
3
3
#superclass : #SBSComponent,
4
4
#instVars : [
5
Removed:
'header',
6
Removed:
'footer'
5
Added:
'footer',
6
Added:
'pm'
7
7
],
8
8
#category : #'MyFitnessTracker-Components'
9
9
}
@@ -12,9 +12,9 @@
12
12
MFTScreenComponent >> children [
13
13
14
14
^ {
15
Removed:
header.
16
Removed:
self session mainNavigationBar.
17
Removed:
self session activePage.
15
Added:
pm registerProspectHeader.
16
Added:
pm mainNavigationBar.
17
Added:
pm activePage.
18
18
footer }
19
19
]
20
20
@@ -22,16 +22,17 @@
22
22
MFTScreenComponent >> initialize [
23
23
24
24
super initialize.
25
Removed:
header := MFTHeaderProspectComponent new.
26
Removed:
footer := MFTFooterComponent new
25
Added:
pm := self session pageManager.
26
Added:
footer := pm defaultFooter
27
27
]
28
28
29
29
{ #category : #rendering }
30
30
MFTScreenComponent >> renderContentOn: html [
31
31
32
Added:
self session user isLoggedIn ifFalse: [
33
Added:
html render: pm registerProspectHeader ].
32
34
html
33
Removed:
render: header;
34
Removed:
render: self session mainNavigationBar;
35
Removed:
render: self session activePage;
35
Added:
render: pm mainNavigationBar;
36
Added:
render: pm activePage;
36
37
render: footer
37
38
]
src/MyFitnessTracker/MFTSession.class.st
@@ -4,26 +4,12 @@
4
4
#instVars : [
5
5
'user',
6
6
'db',
7
Removed:
'mainNavigationBar',
8
Removed:
'mainPages',
9
Removed:
'activePage'
7
Added:
'pageManager'
10
8
],
11
9
#category : #MyFitnessTracker
12
10
}
13
11
14
12
{ #category : #accessing }
15
Removed:
MFTSession >> activePage [
16
Removed:
17
Removed:
^ activePage
18
Removed:
]
19
Removed:
20
Removed:
{ #category : #accessing }
21
Removed:
MFTSession >> activePage: anObject [
22
Removed:
23
Removed:
activePage := anObject
24
Removed:
]
25
Removed:
26
Removed:
{ #category : #accessing }
27
13
MFTSession >> db [
28
14
29
15
^ db
@@ -40,38 +26,35 @@
40
26
41
27
super initialize.
42
28
user := MFTUser new.
43
Removed:
mainNavigationBar := MFTMainNavigationBarComponent new.
44
Removed:
mainPages := OrderedCollection newFromArray: {
45
Removed:
MFTPublicLandingPage new.
46
Removed:
MFTWorkoutsListPage placeholder.
47
Removed:
MFTWeightsListPage new.
48
Removed:
MFTLearnPage new.
49
Removed:
MFTProfilePage new }.
50
Removed:
activePage := mainPages first
29
Added:
pageManager := MFTPageManager new.
51
30
]
52
31
53
Removed:
{ #category : #accessing }
54
Removed:
MFTSession >> mainNavigationBar [
32
Added:
{ #category : #login }
33
Added:
MFTSession >> login: aMFTUser [
55
34
56
Removed:
^ mainNavigationBar
35
Added:
| time |
36
Added:
time := DateAndTime now asUTC rounded asString.
37
Added:
"Reflect the login in database"
38
Added:
self db: MFTSQLite3Database newFromDefault.
39
Added:
self db connection open.
40
Added:
self db login: aMFTUser atDateAndTime: time.
41
Added:
"Reflect the login in the session user"
42
Added:
self user: aMFTUser.
43
Added:
self user lastLoginDateAndTime: time.
44
Added:
self user isLoggedIn: true.
45
Added:
pageManager loadUserPages
57
46
]
58
47
59
48
{ #category : #accessing }
60
Removed:
MFTSession >> mainNavigationBar: anObject [
49
Added:
MFTSession >> pageManager [
61
50
62
Removed:
mainNavigationBar := anObject
51
Added:
^ pageManager
63
52
]
64
53
65
54
{ #category : #accessing }
66
Removed:
MFTSession >> mainPages [
55
Added:
MFTSession >> pageManager: aMFTPageManager [
67
56
68
Removed:
^ mainPages
69
Removed:
]
70
Removed:
71
Removed:
{ #category : #accessing }
72
Removed:
MFTSession >> mainPages: anObject [
73
Removed:
74
Removed:
mainPages := anObject
57
Added:
pageManager := aMFTPageManager
75
58
]
76
59
77
60
{ #category : #'parent/child' }
src/MyFitnessTracker/MFTUser.class.st
@@ -36,6 +36,16 @@
36
36
yourself
37
37
]
38
38
39
Added:
{ #category : #'instance creation' }
40
Added:
MFTUser class >> newFromID: id [
41
Added:
42
Added:
| dictionary |
43
Added:
dictionary := (MFTSQLite3Database selectUser: id) asDictionary.
44
Added:
^ self
45
Added:
newFromDictionary: dictionary;
46
Added:
yourself
47
Added:
]
48
Added:
39
49
{ #category : #accessing }
40
50
MFTUser >> dateOfBirth [
41
51
src/MyFitnessTracker/MFTUserLandingPage.class.st
@@ -8,18 +8,37 @@
8
8
MFTUserLandingPage >> initialize [
9
9
10
10
super initialize.
11
Removed:
title := self session user ifNotNil: [
12
Removed:
self session user username capitalized ]
11
Added:
title := self session user username
13
12
]
14
13
15
14
{ #category : #rendering }
16
15
MFTUserLandingPage >> renderContentOn: html [
17
16
18
17
html container: [
19
Removed:
html displayHeading
20
Removed:
level: 1;
21
Removed:
with: 'Welcome, ' , self session user username , '!'.
22
Removed:
html unorderedList: [
23
Removed:
(self session db selectAll: 'prospects') do: [ :prospect |
24
Removed:
html listItem: prospect ] ] ]
18
Added:
self renderMainCardOn: html.
19
Added:
self renderWorkoutsSummaryCardOn: html ]
20
Added:
]
21
Added:
22
Added:
{ #category : #rendering }
23
Added:
MFTUserLandingPage >> renderMainCardOn: html [
24
Added:
25
Added:
html card: [
26
Added:
html jumbotron
27
Added:
class: 'p-3 bg-light';
28
Added:
with: [
29
Added:
html displayHeading
30
Added:
level: 1;
31
Added:
with: [
32
Added:
html
33
Added:
text: 'Welcome, ';
34
Added:
code: self session user username;
35
Added:
text: '!' ].
36
Added:
html big textMuted with:
37
Added:
'Last logged in ' , self session user lastLoginDateAndTime ] ]
38
Added:
]
39
Added:
40
Added:
{ #category : #rendering }
41
Added:
MFTUserLandingPage >> renderWorkoutsSummaryCardOn: html [
42
Added:
43
Added:
html card: [ html text: self session user username. ]
25
44
]
src/MyFitnessTracker/MFTWeight.class.st
@@ -3,7 +3,8 @@
3
3
#superclass : #Object,
4
4
#instVars : [
5
5
'date',
6
Removed:
'value'
6
Added:
'value',
7
Added:
'user'
7
8
],
8
9
#category : #MyFitnessTracker
9
10
}
@@ -25,6 +26,16 @@
25
26
yourself
26
27
]
27
28
29
Added:
{ #category : #'instance creation' }
30
Added:
MFTWeight class >> newFromDictionary: aDictionary [
31
Added:
32
Added:
^ self new
33
Added:
date: (Date fromString: (aDictionary at: 'date'));
34
Added:
user: (MFTUser newFromID: (aDictionary at: 'user_id'));
35
Added:
value: (aDictionary at: 'value');
36
Added:
yourself
37
Added:
]
38
Added:
28
39
{ #category : #accessing }
29
40
MFTWeight class >> value: aValue [
30
41
@@ -50,6 +61,18 @@
50
61
51
62
super initialize.
52
63
date := Date today
64
Added:
]
65
Added:
66
Added:
{ #category : #accessing }
67
Added:
MFTWeight >> user [
68
Added:
69
Added:
^ user
70
Added:
]
71
Added:
72
Added:
{ #category : #accessing }
73
Added:
MFTWeight >> user: aMFTUser [
74
Added:
75
Added:
user := aMFTUser
53
76
]
54
77
55
78
{ #category : #evaluating }
src/MyFitnessTracker/MFTWeightDialog.class.st
@@ -0,0 +1,99 @@
1
Added:
Class {
2
Added:
#name : #MFTWeightDialog,
3
Added:
#superclass : #SBSComponent,
4
Added:
#instVars : [
5
Added:
'weight',
6
Added:
'addOrEdit'
7
Added:
],
8
Added:
#category : #'MyFitnessTracker-Dialogs'
9
Added:
}
10
Added:
11
Added:
{ #category : #actions }
12
Added:
MFTWeightDialog class >> edit: aMFTWeight [
13
Added:
14
Added:
^ self new
15
Added:
weight: aMFTWeight;
16
Added:
addOrEdit: #edit;
17
Added:
yourself
18
Added:
]
19
Added:
20
Added:
{ #category : #accessing }
21
Added:
MFTWeightDialog >> addOrEdit [
22
Added:
23
Added:
^ addOrEdit
24
Added:
]
25
Added:
26
Added:
{ #category : #accessing }
27
Added:
MFTWeightDialog >> addOrEdit: aRole [
28
Added:
29
Added:
addOrEdit := aRole
30
Added:
]
31
Added:
32
Added:
{ #category : #initialization }
33
Added:
MFTWeightDialog >> initialize [
34
Added:
35
Added:
super initialize.
36
Added:
weight := MFTWeight new.
37
Added:
addOrEdit := #add
38
Added:
]
39
Added:
40
Added:
{ #category : #rendering }
41
Added:
MFTWeightDialog >> renderContentOn: html [
42
Added:
43
Added:
html container: [
44
Added:
html heading: self addOrEdit asCapitalizedPhrase , ' weight'.
45
Added:
html form with: [
46
Added:
self renderDateInputOn: html.
47
Added:
self renderWeightInputOn: html.
48
Added:
self renderSubmitWeightOn: html ] ]
49
Added:
]
50
Added:
51
Added:
{ #category : #rendering }
52
Added:
MFTWeightDialog >> renderDateInputOn: html [
53
Added:
54
Added:
html formGroup: [
55
Added:
html label: 'Date'.
56
Added:
html dateInput5 formControl
57
Added:
value: weight date yyyymmdd;
58
Added:
callback: [ :date | weight date: (Date fromString: date) ] ]
59
Added:
]
60
Added:
61
Added:
{ #category : #rendering }
62
Added:
MFTWeightDialog >> renderSubmitWeightOn: html [
63
Added:
64
Added:
html formGroup: [
65
Added:
html buttonGroup with: [
66
Added:
html formButton
67
Added:
bePrimary;
68
Added:
beSubmit;
69
Added:
callback: [ self answer: weight ];
70
Added:
with: 'Submit'.
71
Added:
html formButton
72
Added:
beSecondary;
73
Added:
callback: [ self answer: nil ];
74
Added:
with: 'Cancel' ] ]
75
Added:
]
76
Added:
77
Added:
{ #category : #rendering }
78
Added:
MFTWeightDialog >> renderWeightInputOn: html [
79
Added:
80
Added:
html formGroup: [
81
Added:
html label: 'Weight'.
82
Added:
html textInput
83
Added:
formControl;
84
Added:
placeholder: weight value;
85
Added:
callback: [ :value | weight value: value ];
86
Added:
with: weight value ]
87
Added:
]
88
Added:
89
Added:
{ #category : #accessing }
90
Added:
MFTWeightDialog >> weight [
91
Added:
92
Added:
^ weight
93
Added:
]
94
Added:
95
Added:
{ #category : #accessing }
96
Added:
MFTWeightDialog >> weight: anObject [
97
Added:
98
Added:
weight := anObject
99
Added:
]
src/MyFitnessTracker/MFTWeightsListPage.class.st
@@ -11,44 +11,117 @@
11
11
MFTWeightsListPage class >> placeholder [
12
12
13
13
^ self new
14
Removed:
weights: {
15
Removed:
(MFTWeight value: 90).
16
Removed:
(MFTWeight value: 100).
17
Removed:
(MFTWeight value: 102).
18
Removed:
(MFTWeight date: (Date fromString: '01/01/2024') value: 105) };
14
Added:
weights: (OrderedCollection withAll: {
15
Added:
(MFTWeight date: (Date fromString: '01/01/2024') value: 90).
16
Added:
(MFTWeight date: (Date fromString: '01/02/2024') value: 91).
17
Added:
(MFTWeight date: (Date fromString: '01/03/2024') value: 92).
18
Added:
(MFTWeight date: (Date fromString: '01/04/2024') value: 93) });
19
19
yourself
20
20
]
21
21
22
Added:
{ #category : #adding }
23
Added:
MFTWeightsListPage >> addWeight [
24
Added:
25
Added:
| newWeight |
26
Added:
newWeight := self call: MFTWeightDialog new.
27
Added:
newWeight ifNotNil: [ weights addFirst: newWeight ]
28
Added:
]
29
Added:
30
Added:
{ #category : #accessing }
31
Added:
MFTWeightsListPage >> editWeightAt: i [
32
Added:
33
Added:
self call: (MFTWeightDialog edit: (weights at: i))
34
Added:
]
35
Added:
22
36
{ #category : #initialization }
23
37
MFTWeightsListPage >> initialize [
24
38
25
39
super initialize.
26
Removed:
title := 'weight'.
40
Added:
title := 'weights'.
27
41
weights := OrderedCollection new
28
42
]
29
43
44
Added:
{ #category : #'as yet unclassified' }
45
Added:
MFTWeightsListPage >> loadUserWeights [
46
Added:
47
Added:
weights := self session db selectWeightsWhereUser:
48
Added:
self session user
49
Added:
]
50
Added:
51
Added:
{ #category : #'as yet unclassified' }
52
Added:
MFTWeightsListPage >> randomWittyCTA [
53
Added:
54
Added:
| listOfStrings randomString |
55
Added:
listOfStrings := #( 'First time?' 'Get your sweat on!'
56
Added:
'Go go go!' 'Go get ''em, Tiger!' ).
57
Added:
randomString := listOfStrings atRandom.
58
Added:
^ randomString
59
Added:
]
60
Added:
61
Added:
{ #category : #accessing }
62
Added:
MFTWeightsListPage >> removeWeightAt: i [
63
Added:
64
Added:
weights removeAt: i
65
Added:
]
66
Added:
30
67
{ #category : #rendering }
68
Added:
MFTWeightsListPage >> renderActionsButtonGroupOn: html [
69
Added:
70
Added:
html form: [
71
Added:
html buttonGroup: [
72
Added:
html formButton
73
Added:
bePrimary;
74
Added:
callback: [ self addWeight ];
75
Added:
with: 'Add weight' ] ]
76
Added:
]
77
Added:
78
Added:
{ #category : #rendering }
31
79
MFTWeightsListPage >> renderContentOn: html [
32
80
33
Removed:
html container with: [
34
Removed:
html table
35
Removed:
class: 'table table-striped';
81
Added:
weights sort: [ :a :b | a date > b date ].
82
Added:
html container: [
83
Added:
weights ifEmpty: [
84
Added:
html jumbotron: [ html heading: self randomWittyCTA ] ].
85
Added:
self renderActionsButtonGroupOn: html.
86
Added:
self renderWeightsAsTableOn: html ]
87
Added:
]
88
Added:
89
Added:
{ #category : #rendering }
90
Added:
MFTWeightsListPage >> renderWeightActionsButtonGroupOn: html at: i [
91
Added:
92
Added:
html form: [
93
Added:
html buttonGroup
94
Added:
beSmall;
36
95
with: [
37
Removed:
weights do: [ :each |
38
Removed:
html tableRow: [
39
Removed:
html
40
Removed:
tableData: each date ddmmyyyy;
41
Removed:
tableData: each value ] ] ].
42
Removed:
html form: [
43
Removed:
html buttonGroup: [
44
96
html formButton
45
Removed:
bePrimary;
46
Removed:
callback: [ "Edit the weight" ];
97
Added:
beSecondary;
98
Added:
formControl;
99
Added:
callback: [ self editWeightAt: i ];
47
100
with: 'Edit'.
48
101
html formButton
49
102
beDanger;
50
Removed:
callback: [ nil ];
51
Removed:
with: 'Delete' ] ] ]
103
Added:
formControl;
104
Added:
callback: [ self removeWeightAt: i ];
105
Added:
with: 'Delete' ] ]
106
Added:
]
107
Added:
108
Added:
{ #category : #rendering }
109
Added:
MFTWeightsListPage >> renderWeightsAsTableOn: html [
110
Added:
111
Added:
html table
112
Added:
class: 'table table-striped';
113
Added:
with: [
114
Added:
html tableHead: [
115
Added:
html
116
Added:
tableHeading;
117
Added:
tableHeading: 'Date';
118
Added:
tableHeading: 'Value' ].
119
Added:
weights doWithIndex: [ :weight :i |
120
Added:
html tableRow: [
121
Added:
html tableData: [
122
Added:
self renderWeightActionsButtonGroupOn: html at: i ].
123
Added:
html tableData: weight date.
124
Added:
html tableData: weight value ] ] ]
52
125
]
53
126
54
127
{ #category : #accessing }
src/MyFitnessTracker/MFTWorkout.class.st
@@ -10,6 +10,18 @@
10
10
#category : #'MyFitnessTracker-Model'
11
11
}
12
12
13
Added:
{ #category : #'instance creation' }
14
Added:
MFTWorkout class >> newFromDictionary: aDictionary [
15
Added:
16
Added:
^ self new
17
Added:
date: (Date fromString: (aDictionary at: 'date'));
18
Added:
primaryMuscle:
19
Added:
(MFTMuscle new latinName: (aDictionary at: 'primary_muscle'));
20
Added:
secondaryMuscles:
21
Added:
(MFTMuscle new latinName: (aDictionary at: 'secondary_muscles'));
22
Added:
yourself
23
Added:
]
24
Added:
13
25
{ #category : #accessing }
14
26
MFTWorkout class >> primaryMuscle: aMFTMuscle [
15
27
src/MyFitnessTracker/MFTWorkoutDialog.class.st
@@ -91,7 +91,8 @@
91
91
MFTWorkoutDialog >> renderContentOn: html [
92
92
93
93
html container: [
94
Removed:
html heading: 'Add workout'.
94
Added:
html heading: self addOrEdit asCapitalizedPhrase , ' workout for '
95
Added:
, workout date asString.
95
96
html form: [
96
97
self renderDateInputOn: html.
97
98
self renderExercisesTableOn: html.
src/MyFitnessTracker/MFTWorkoutsListPage.class.st
@@ -118,6 +118,13 @@
118
118
]
119
119
120
120
{ #category : #'as yet unclassified' }
121
Added:
MFTWorkoutsListPage >> loadUserWorkouts [
122
Added:
123
Added:
workouts := self session db selectWorkoutsWhereUser:
124
Added:
self session user
125
Added:
]
126
Added:
127
Added:
{ #category : #'as yet unclassified' }
121
128
MFTWorkoutsListPage >> randomWittyCTA [
122
129
123
130
| listOfStrings randomString |
@@ -190,6 +197,12 @@
190
197
html accordionBody with: [
191
198
html render: workout.
192
199
self renderWorkoutActionsButtonGroup: html at: i ] ] ] ] ]
200
Added:
]
201
Added:
202
Added:
{ #category : #'as yet unclassified' }
203
Added:
MFTWorkoutsListPage >> userWorkouts [
204
Added:
205
Added:
self session db selectWorkoutsWhereUser: self session user
193
206
]
194
207
195
208
{ #category : #accessing }