solara 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (175) hide show
  1. checksums.yaml +7 -0
  2. data/bin/solara +18 -0
  3. data/solara/lib/.DS_Store +0 -0
  4. data/solara/lib/core/.DS_Store +0 -0
  5. data/solara/lib/core/aliases/alias_generator.rb +128 -0
  6. data/solara/lib/core/aliases/alias_generator_manager.rb +28 -0
  7. data/solara/lib/core/aliases/solara_terminal_setup.rb +103 -0
  8. data/solara/lib/core/brands/brand_onboarder.rb +46 -0
  9. data/solara/lib/core/brands/brand_switcher.rb +204 -0
  10. data/solara/lib/core/brands/brands_manager.rb +154 -0
  11. data/solara/lib/core/dashboard/.DS_Store +0 -0
  12. data/solara/lib/core/dashboard/brand/.DS_Store +0 -0
  13. data/solara/lib/core/dashboard/brand/BrandDetail.js +11 -0
  14. data/solara/lib/core/dashboard/brand/BrandDetailController.js +361 -0
  15. data/solara/lib/core/dashboard/brand/BrandDetailModel.js +155 -0
  16. data/solara/lib/core/dashboard/brand/BrandDetailView.js +245 -0
  17. data/solara/lib/core/dashboard/brand/brand.html +477 -0
  18. data/solara/lib/core/dashboard/brand/source/BrandLocalSource.js +123 -0
  19. data/solara/lib/core/dashboard/brand/source/BrandRemoteSource.js +260 -0
  20. data/solara/lib/core/dashboard/brands/Brands.js +10 -0
  21. data/solara/lib/core/dashboard/brands/BrandsController.js +155 -0
  22. data/solara/lib/core/dashboard/brands/BrandsModel.js +136 -0
  23. data/solara/lib/core/dashboard/brands/BrandsView.js +136 -0
  24. data/solara/lib/core/dashboard/brands/brands.html +345 -0
  25. data/solara/lib/core/dashboard/component/AddFieldSheet.js +212 -0
  26. data/solara/lib/core/dashboard/component/AliasesBottomSheet.js +128 -0
  27. data/solara/lib/core/dashboard/component/BrandOptionsBottomSheet.js +130 -0
  28. data/solara/lib/core/dashboard/component/ConfirmationDialog.js +103 -0
  29. data/solara/lib/core/dashboard/component/MessageBottomSheet.js +80 -0
  30. data/solara/lib/core/dashboard/component/OnboardBrandBottomSheet.js +214 -0
  31. data/solara/lib/core/dashboard/dashboard_manager.rb +19 -0
  32. data/solara/lib/core/dashboard/dashboard_server.rb +132 -0
  33. data/solara/lib/core/dashboard/handler/base_handler.rb +25 -0
  34. data/solara/lib/core/dashboard/handler/brand_alisases_handler.rb +33 -0
  35. data/solara/lib/core/dashboard/handler/brand_configurations_handler.rb +18 -0
  36. data/solara/lib/core/dashboard/handler/brand_configurations_manager.rb +73 -0
  37. data/solara/lib/core/dashboard/handler/brand_icon_handler.rb +20 -0
  38. data/solara/lib/core/dashboard/handler/brand_section_handler.rb +20 -0
  39. data/solara/lib/core/dashboard/handler/brands_handler.rb +14 -0
  40. data/solara/lib/core/dashboard/handler/current_brand_handler.rb +18 -0
  41. data/solara/lib/core/dashboard/handler/doctor_handler.rb +39 -0
  42. data/solara/lib/core/dashboard/handler/edit_section_handler.rb +55 -0
  43. data/solara/lib/core/dashboard/handler/offboard_brand_handler.rb +34 -0
  44. data/solara/lib/core/dashboard/handler/onboard_brand_handler.rb +53 -0
  45. data/solara/lib/core/dashboard/handler/redirect_handler.rb +12 -0
  46. data/solara/lib/core/dashboard/handler/switch_handler.rb +25 -0
  47. data/solara/lib/core/dashboard/index.html +36 -0
  48. data/solara/lib/core/dashboard/local.html +41 -0
  49. data/solara/lib/core/dashboard/res/favicon/android-chrome-192x192.png +0 -0
  50. data/solara/lib/core/dashboard/res/favicon/android-chrome-512x512.png +0 -0
  51. data/solara/lib/core/dashboard/res/favicon/apple-touch-icon.png +0 -0
  52. data/solara/lib/core/dashboard/res/favicon/favicon-16x16.png +0 -0
  53. data/solara/lib/core/dashboard/res/favicon/favicon-32x32.png +0 -0
  54. data/solara/lib/core/dashboard/res/favicon/favicon.ico +0 -0
  55. data/solara/lib/core/dashboard/res/favicon/site.webmanifest +1 -0
  56. data/solara/lib/core/dashboard/solara.png +0 -0
  57. data/solara/lib/core/doctor/brand_doctor.rb +94 -0
  58. data/solara/lib/core/doctor/doctor_manager.rb +35 -0
  59. data/solara/lib/core/doctor/project_doctor.rb +8 -0
  60. data/solara/lib/core/doctor/schema/brand_configurations.json +60 -0
  61. data/solara/lib/core/doctor/schema/platform/android/android_config.json +23 -0
  62. data/solara/lib/core/doctor/schema/platform/android/android_signing.json +23 -0
  63. data/solara/lib/core/doctor/schema/platform/ios/ios_config.json +27 -0
  64. data/solara/lib/core/doctor/schema/platform/ios/ios_signing.json +27 -0
  65. data/solara/lib/core/doctor/schema/platform/shared/theme.json +48 -0
  66. data/solara/lib/core/doctor/validator/brand_settings_validator.rb +55 -0
  67. data/solara/lib/core/doctor/validator/brand_settings_validator_manager.rb +82 -0
  68. data/solara/lib/core/doctor/validator/directory_structure_validator.rb +38 -0
  69. data/solara/lib/core/doctor/validator/file_structure_validator.rb +37 -0
  70. data/solara/lib/core/doctor/validator/json_file_validator.rb +21 -0
  71. data/solara/lib/core/doctor/validator/json_schema_validator.rb +32 -0
  72. data/solara/lib/core/doctor/validator/project_filesystem_validator.rb +70 -0
  73. data/solara/lib/core/doctor/validator/template/android_template_validation_config.yml +51 -0
  74. data/solara/lib/core/doctor/validator/template/flutter_template_validation_config.yml +53 -0
  75. data/solara/lib/core/doctor/validator/template/ios_template_validation_config.yml +51 -0
  76. data/solara/lib/core/doctor/validator/template/template_validator.rb +108 -0
  77. data/solara/lib/core/doctor/validator/validation_strategy.rb +7 -0
  78. data/solara/lib/core/scripts/brand_config_generator.rb +245 -0
  79. data/solara/lib/core/scripts/brand_config_manager.rb +90 -0
  80. data/solara/lib/core/scripts/brand_exporter.rb +38 -0
  81. data/solara/lib/core/scripts/brand_importer.rb +84 -0
  82. data/solara/lib/core/scripts/brand_offboarder.rb +19 -0
  83. data/solara/lib/core/scripts/brand_resources_manager.rb +77 -0
  84. data/solara/lib/core/scripts/directory_creator.rb +22 -0
  85. data/solara/lib/core/scripts/file_manager.rb +90 -0
  86. data/solara/lib/core/scripts/file_path.rb +327 -0
  87. data/solara/lib/core/scripts/folder_copier.rb +41 -0
  88. data/solara/lib/core/scripts/gitignore_manager.rb +54 -0
  89. data/solara/lib/core/scripts/interactive_file_system_validator.rb +110 -0
  90. data/solara/lib/core/scripts/platform/android/android_manifest_switcher.rb +24 -0
  91. data/solara/lib/core/scripts/platform/android/android_strings_switcher.rb +39 -0
  92. data/solara/lib/core/scripts/platform/android/gradle_switcher.rb +233 -0
  93. data/solara/lib/core/scripts/platform/android/properties_generator.rb +31 -0
  94. data/solara/lib/core/scripts/platform/ios/ios_file_path_manager.rb +109 -0
  95. data/solara/lib/core/scripts/platform/ios/ios_plist_manager.rb +42 -0
  96. data/solara/lib/core/scripts/platform/ios/xcconfig_generator.rb +44 -0
  97. data/solara/lib/core/scripts/platform/ios/xcode_asset_manager.rb +56 -0
  98. data/solara/lib/core/scripts/platform/ios/xcode_project_manager.rb +82 -0
  99. data/solara/lib/core/scripts/platform/ios/xcode_project_switcher.rb +130 -0
  100. data/solara/lib/core/scripts/project_settings_manager.rb +39 -0
  101. data/solara/lib/core/scripts/solara_logger.rb +103 -0
  102. data/solara/lib/core/scripts/solara_settings_manager.rb +73 -0
  103. data/solara/lib/core/scripts/solara_status_manager.rb +55 -0
  104. data/solara/lib/core/scripts/solara_version_manager.rb +42 -0
  105. data/solara/lib/core/scripts/strings_xml_manager.rb +22 -0
  106. data/solara/lib/core/scripts/terminal_input_manager.rb +22 -0
  107. data/solara/lib/core/scripts/theme_generator.rb +250 -0
  108. data/solara/lib/core/scripts/yaml_manager.rb +72 -0
  109. data/solara/lib/core/solara_configurator.rb +15 -0
  110. data/solara/lib/core/template/brands/android/android_config.json +8 -0
  111. data/solara/lib/core/template/brands/android/android_signing.json +6 -0
  112. data/solara/lib/core/template/brands/android/res/.DS_Store +0 -0
  113. data/solara/lib/core/template/brands/android/res/mipmap-hdpi/ic_launcher.png +0 -0
  114. data/solara/lib/core/template/brands/android/res/mipmap-hdpi/ic_launcher_round.png +0 -0
  115. data/solara/lib/core/template/brands/android/res/mipmap-mdpi/ic_launcher.png +0 -0
  116. data/solara/lib/core/template/brands/android/res/mipmap-mdpi/ic_launcher_round.png +0 -0
  117. data/solara/lib/core/template/brands/android/res/mipmap-xhdpi/ic_launcher.png +0 -0
  118. data/solara/lib/core/template/brands/android/res/mipmap-xhdpi/ic_launcher_round.png +0 -0
  119. data/solara/lib/core/template/brands/android/res/mipmap-xxhdpi/ic_launcher.png +0 -0
  120. data/solara/lib/core/template/brands/android/res/mipmap-xxhdpi/ic_launcher_round.png +0 -0
  121. data/solara/lib/core/template/brands/android/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
  122. data/solara/lib/core/template/brands/android/res/mipmap-xxxhdpi/ic_launcher_round.png +0 -0
  123. data/solara/lib/core/template/brands/brands.json +4 -0
  124. data/solara/lib/core/template/brands/ios/assets/.DS_Store +0 -0
  125. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/100.png +0 -0
  126. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/102.png +0 -0
  127. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/1024.png +0 -0
  128. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/114.png +0 -0
  129. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/120.png +0 -0
  130. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/128.png +0 -0
  131. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/144.png +0 -0
  132. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/152.png +0 -0
  133. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/16.png +0 -0
  134. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/167.png +0 -0
  135. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/172.png +0 -0
  136. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/180.png +0 -0
  137. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/196.png +0 -0
  138. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/20.png +0 -0
  139. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/216.png +0 -0
  140. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/256.png +0 -0
  141. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/29.png +0 -0
  142. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/32.png +0 -0
  143. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/40.png +0 -0
  144. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/48.png +0 -0
  145. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/50.png +0 -0
  146. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/512.png +0 -0
  147. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/55.png +0 -0
  148. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/57.png +0 -0
  149. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/58.png +0 -0
  150. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/60.png +0 -0
  151. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/64.png +0 -0
  152. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/66.png +0 -0
  153. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/72.png +0 -0
  154. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/76.png +0 -0
  155. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/80.png +0 -0
  156. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/87.png +0 -0
  157. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/88.png +0 -0
  158. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/92.png +0 -0
  159. data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/Contents.json +1 -0
  160. data/solara/lib/core/template/brands/ios/ios_config.json +7 -0
  161. data/solara/lib/core/template/brands/ios/ios_signing.json +7 -0
  162. data/solara/lib/core/template/brands/shared/.DS_Store +0 -0
  163. data/solara/lib/core/template/brands/shared/brand_config.json +2 -0
  164. data/solara/lib/core/template/brands/shared/theme.json +46 -0
  165. data/solara/lib/core/template/config/android_template_config.json +57 -0
  166. data/solara/lib/core/template/config/flutter_template_config.json +62 -0
  167. data/solara/lib/core/template/config/ios_template_config.json +57 -0
  168. data/solara/lib/core/template/project_template_generator.rb +63 -0
  169. data/solara/lib/platform_detector.rb +84 -0
  170. data/solara/lib/solara/cli.rb +5 -0
  171. data/solara/lib/solara/version.rb +3 -0
  172. data/solara/lib/solara.rb +238 -0
  173. data/solara/lib/solara_initializer.rb +44 -0
  174. data/solara/lib/solara_manager.rb +73 -0
  175. metadata +346 -0
@@ -0,0 +1,260 @@
1
+ import Ajv from 'https://cdn.skypack.dev/ajv@8.11.0';
2
+
3
+ class BrandRemoteSource {
4
+ constructor() {
5
+ }
6
+
7
+ async fetchBrandDetails(brandKey) {
8
+
9
+ }
10
+
11
+ async fetchCurrentBrand(brandKey) {
12
+ // Empty for now
13
+ }
14
+
15
+ async saveSection(sectionItem, configuration, brandKey) {
16
+ // Empty for now
17
+ }
18
+
19
+ async switchToBrand(brandKey) {
20
+ // Empty for now
21
+ }
22
+
23
+ async checkBrandHealth(brandKey) {
24
+ // Empty for now
25
+ }
26
+
27
+ async createNewBrandConfogurations() {
28
+ const configurations_template = `
29
+ [
30
+ {
31
+ "key": "theme",
32
+ "name": "Theme Configuration",
33
+ "inputType": "color",
34
+ "content": {
35
+ "colors": {
36
+ "primary": "#CAAC16",
37
+ "secondary": "#5AC8FA",
38
+ "background": "#FFFFFF",
39
+ "surface": "#F2F2F7",
40
+ "error": "#FF3B30",
41
+ "onPrimary": "#FFFFFF",
42
+ "onSecondary": "#000000",
43
+ "onBackground": "#000000",
44
+ "onSurface": "#000000",
45
+ "onError": "#FFFFFF"
46
+ },
47
+ "typography": {
48
+ "fontFamily": {
49
+ "regular": "Roboto",
50
+ "medium": "Roboto-Medium",
51
+ "bold": "Roboto-Bold"
52
+ },
53
+ "fontSize": {
54
+ "small": 12,
55
+ "medium": 16,
56
+ "large": 20,
57
+ "extraLarge": 24
58
+ }
59
+ },
60
+ "spacing": {
61
+ "small": 8,
62
+ "medium": 16,
63
+ "large": 24,
64
+ "extraLarge": 32
65
+ },
66
+ "borderRadius": {
67
+ "small": 4,
68
+ "medium": 8,
69
+ "large": 12
70
+ },
71
+ "elevation": {
72
+ "none": 0,
73
+ "low": 2,
74
+ "medium": 4,
75
+ "high": 8
76
+ }
77
+ }
78
+ },
79
+ {
80
+ "key": "brand_config",
81
+ "name": "Brand Configuration",
82
+ "inputType": "text",
83
+ "content": {}
84
+ },
85
+ {
86
+ "key": "android_config",
87
+ "name": "Android Platform Configuration",
88
+ "inputType": "text",
89
+ "content": {
90
+ "brandName": "",
91
+ "applicationId": "",
92
+ "versionName": "1.0.0",
93
+ "versionCode": 1,
94
+ "sourceSets": []
95
+ }
96
+ },
97
+ {
98
+ "key": "android_signing",
99
+ "name": "Android Signing",
100
+ "inputType": "text",
101
+ "content": {
102
+ "storeFile": "",
103
+ "keyAlias": "",
104
+ "storePassword": "",
105
+ "keyPassword": ""
106
+ }
107
+ },
108
+ {
109
+ "key": "ios_config",
110
+ "name": "iOS Platform Configuration",
111
+ "inputType": "text",
112
+ "content": {
113
+ "PRODUCT_NAME": "",
114
+ "PRODUCT_BUNDLE_IDENTIFIER": "",
115
+ "MARKETING_VERSION": "1.0.0",
116
+ "BUNDLE_VERSION": "1",
117
+ "APL_MRCH_ID": ""
118
+ }
119
+ },
120
+ {
121
+ "key": "ios_signing",
122
+ "name": "iOS Signing",
123
+ "inputType": "text",
124
+ "content": {
125
+ "CODE_SIGN_IDENTITY": "",
126
+ "DEVELOPMENT_TEAM": "",
127
+ "PROVISIONING_PROFILE_SPECIFIER": "",
128
+ "CODE_SIGN_STYLE": "Automatic",
129
+ "CODE_SIGN_ENTITLEMENTS": ""
130
+ }
131
+ }
132
+ ]`;
133
+ return JSON.parse(configurations_template);
134
+ }
135
+
136
+ async getBrandConfigurationsJsonFromDirectory(dirHandle) {
137
+ const schema = await this.fetchBrandConfigurationsSchema();
138
+
139
+ const ajv = new Ajv();
140
+ const validate = ajv.compile(schema);
141
+
142
+ const text = await dirHandle.text();
143
+ let json;
144
+
145
+ try {
146
+ json = JSON.parse(text);
147
+ } catch (e) {
148
+ console.error("Invalid JSON:", e);
149
+ return null;
150
+ }
151
+
152
+ const valid = validate(json);
153
+
154
+ if (valid) {
155
+ return json;
156
+ } else {
157
+ console.error("Schema validation failed:", validate.errors);
158
+ const errorMessages = validate.errors.map(error => `${error.instancePath}: ${error.message}`).join(', ');
159
+ alert(`The selected JSON file is not a valid configuration for Solara brands. Errors: ${errorMessages}`);
160
+ }
161
+
162
+ return null;
163
+ }
164
+
165
+ async fetchBrandConfigurationsSchema() {
166
+ const url = "https://raw.githubusercontent.com/Solara-Kit/Solara/main/solara/lib/core/doctor/schema/brand_configurations.json";
167
+
168
+ try {
169
+ const response = await fetch(url);
170
+ if (!response.ok) {
171
+ throw new Error('Network response was not ok: ' + response.statusText);
172
+ }
173
+ const data = await response.json();
174
+ console.log(data);
175
+ return data; // Return the data instead of null
176
+ } catch (error) {
177
+ console.error('There was a problem with the fetch operation:', error);
178
+ return null; // Return null in case of error
179
+ }
180
+ }
181
+
182
+ async createBrandConfigurationsFromDirectory(dirHandle) {
183
+ const configList = [];
184
+ const expectedFiles = [
185
+ {
186
+ key: 'theme',
187
+ name: 'Theme Configuration',
188
+ input_type: 'color',
189
+ filename: 'theme.json'
190
+ },
191
+ {
192
+ key: 'brand_config',
193
+ name: 'Brand Configuration',
194
+ input_type: 'text',
195
+ filename: 'brand_config.json'
196
+ },
197
+ {
198
+ key: 'android_config',
199
+ name: 'Android Platform Configuration',
200
+ input_type: 'text',
201
+ filename: 'android_config.json'
202
+ },
203
+ {
204
+ key: 'android_signing',
205
+ name: 'Android Signing',
206
+ input_type: 'text',
207
+ filename: 'android_signing.json'
208
+ },
209
+ {
210
+ key: 'ios_config',
211
+ name: 'iOS Platform Configuration',
212
+ input_type: 'text',
213
+ filename: 'ios_config.json'
214
+ },
215
+ {
216
+ key: 'ios_signing',
217
+ name: 'iOS Signing',
218
+ input_type: 'text',
219
+ filename: 'ios_signing.json'
220
+ }
221
+ ];
222
+
223
+ for (const file of expectedFiles) {
224
+ try {
225
+ const fileContent = await this.findAndReadFile(dirHandle, file.filename);
226
+ if (fileContent) {
227
+ configList.push({
228
+ key: file.key,
229
+ name: file.name,
230
+ inputType: file.input_type,
231
+ content: JSON.parse(fileContent)
232
+ });
233
+ }
234
+ } catch (error) {
235
+ console.warn(`File not found or invalid JSON: ${file.filename}`);
236
+ }
237
+ }
238
+
239
+ return configList;
240
+ }
241
+
242
+ async findAndReadFile(dirHandle, filename) {
243
+ const queue = [dirHandle];
244
+ while (queue.length > 0) {
245
+ const currentHandle = queue.shift();
246
+ for await (const entry of currentHandle.values()) {
247
+ if (entry.kind === 'file' && entry.name === filename) {
248
+ const file = await entry.getFile();
249
+ return await file.text();
250
+ } else if (entry.kind === 'directory') {
251
+ queue.push(entry);
252
+ }
253
+ }
254
+ }
255
+ return null;
256
+ }
257
+
258
+ }
259
+
260
+ export default BrandRemoteSource;
@@ -0,0 +1,10 @@
1
+ import BrandsModel from './BrandsModel.js';
2
+ import BrandsView from './BrandsView.js';
3
+ import BrandsController from './BrandsController.js';
4
+
5
+ document.addEventListener('DOMContentLoaded', async () => {
6
+ const model = new BrandsModel();
7
+ const view = new BrandsView(model.source);
8
+ const controller = new BrandsController(model, view);
9
+ await controller.init();
10
+ });
@@ -0,0 +1,155 @@
1
+ class BrandsController {
2
+ constructor(model, view) {
3
+ this.model = model;
4
+ this.view = view;
5
+ this.view.setOnSwitch(this.switchToBrand.bind(this));
6
+ }
7
+
8
+ async init() {
9
+ await this.model.fetchCurrentBrand();
10
+ await this.renderBrands();
11
+ await this.checkBrandsHealth();
12
+ this.attachEventListeners();
13
+ }
14
+
15
+ async renderBrands() {
16
+ const brands = await this.model.fetchBrands();
17
+ this.view.renderBrands(brands, this.model.currentBrand);
18
+ }
19
+
20
+ attachEventListeners() {
21
+ document.querySelector('.onboard-brand-button')
22
+ .addEventListener('click', () => this.showOnboardBrandForm());
23
+ document.getElementById('brandSearch')
24
+ .addEventListener('input', (e) => this.filterBrands(e.target.value));
25
+
26
+ this.view.brandOptionsSheet.addEventListener('clone', (event) => {
27
+ console.log('Clone', event.detail);
28
+ this.handleCloneOption(event)
29
+ });
30
+
31
+ this.view.brandOptionsSheet.addEventListener('offboard', (event) => {
32
+ console.log('Offboard', event.detail);
33
+ this.handleOffboardOption(event)
34
+ });
35
+
36
+ this.view.brandOptionsSheet.addEventListener('doctor', (event) => {
37
+ console.log('Doctor', event.detail);
38
+ this.handleDoctorOption(event)
39
+ });
40
+
41
+ this.view.brandOptionsSheet.addEventListener('aliases', (event) => {
42
+ console.log('Aliases', event.detail);
43
+ this.handleAliasesOption(event)
44
+ });
45
+
46
+ this.view.brandOptionsSheet.addEventListener('settings', (event) => {
47
+ console.log('Settings', event.detail);
48
+ this.handleSettingsOption(event)
49
+ });
50
+ }
51
+
52
+ showOnboardBrandForm() {
53
+ this.view.showOnboardBrandForm();
54
+ this.view.onboardSheet.addEventListener('onboard', async (event) => {
55
+ event.preventDefault();
56
+ const {brandKey, brandName} = event.detail;
57
+ await this.handleOnboardBrandSubmit(brandKey, brandName);
58
+ });
59
+ }
60
+
61
+ async handleOnboardBrandSubmit(brandKey, brandName) {
62
+ try {
63
+ await this.model.onboardBrand(brandName, brandKey);
64
+ await this.view.hideOnboardBrandForm();
65
+ location.reload();
66
+ } catch (error) {
67
+ console.error('Error during submission:', error);
68
+ alert(error);
69
+ }
70
+ }
71
+
72
+ filterBrands(searchTerm) {
73
+ const filteredBrands = this.model.filterBrands(searchTerm);
74
+ this.view.renderBrands(filteredBrands, this.model.currentBrand);
75
+ }
76
+
77
+ async checkBrandsHealth() {
78
+ const result = await this.model.runDoctor("");
79
+ const errorButton = document.getElementById('error-button');
80
+
81
+ if (!result.passed) {
82
+ this.view.showErrorButton();
83
+ this.view.updateErrorCount(result.errors.length);
84
+
85
+ errorButton.addEventListener('click', () => {
86
+ const errors = result.errors
87
+ .map((error, index) => `${index + 1}. ${error}`)
88
+ .join('\n');
89
+ this.view.showMessage(`Health check for all brands completed with errors: \n\n${errors}`);
90
+ });
91
+ } else {
92
+ this.view.hideErrorButton();
93
+ }
94
+ }
95
+
96
+ handleCloneOption(event) {
97
+ event.stopPropagation();
98
+ this.showOnboardBrandForm();
99
+ }
100
+
101
+ handleOffboardOption(event) {
102
+ event.stopPropagation();
103
+ const brandKey = this.view.brandOptionsSheet.dataset.brandKey;
104
+ const brandName = this.view.brandOptionsSheet.dataset.brandName;
105
+ this.view.showConfirmationDialog(`Are you sure you need to offboard ${brandKey} (${brandName}) and delete all its configurations?`,
106
+ async () => {
107
+ await this.model.offboardBrand(brandKey);
108
+ location.reload();
109
+ });
110
+ }
111
+
112
+ async handleDoctorOption(event) {
113
+ event.stopPropagation();
114
+
115
+ const brandKey = this.view.brandOptionsSheet.dataset.brandKey;
116
+ const result = await this.model.runDoctor(brandKey);
117
+ if (!result.passed) {
118
+ const errors = result.errors
119
+ .map((error, index) => `${index + 1}. ${error}`)
120
+ .join('\n');
121
+ this.view.showMessage(`Health check for ${brandKey} completed with errors:\n${errors}`);
122
+ } else {
123
+ this.view.showMessage(`Health check for ${brandKey}. All systems operational.`);
124
+ }
125
+ }
126
+
127
+ async handleAliasesOption(event) {
128
+ event.stopPropagation();
129
+
130
+ const brandKey = this.view.brandOptionsSheet.dataset.brandKey;
131
+ const aliases = await this.model.fetchAliases(brandKey);
132
+ if (aliases) {
133
+ this.view.showAliasesBottomSheet(aliases, brandKey);
134
+ }
135
+ }
136
+
137
+ handleSettingsOption(event) {
138
+ event.stopPropagation();
139
+
140
+ const brandKey = this.view.brandOptionsSheet.dataset.brandKey;
141
+ window.location.href = this.view.brandUrl(brandKey);
142
+ }
143
+
144
+ async switchToBrand(brandKey) {
145
+ try {
146
+ await this.model.switchToBrand(brandKey);
147
+ location.reload();
148
+ } catch (error) {
149
+ console.error('Error switching to brand:', error);
150
+ alert(error.message);
151
+ }
152
+ }
153
+ }
154
+
155
+ export default BrandsController;
@@ -0,0 +1,136 @@
1
+ class BrandsModel {
2
+ constructor() {
3
+ this.allBrands = [];
4
+ this.currentBrand = null;
5
+ this.source = this.getQueryFromUrl('source') || 'remote';
6
+ }
7
+
8
+ getQueryFromUrl(name) {
9
+ const urlParams = new URLSearchParams(window.location.search);
10
+ return urlParams.get(name);
11
+ }
12
+
13
+ async fetchCurrentBrand() {
14
+ try {
15
+ const response = await fetch('/brand/current');
16
+ let result = await response.json();
17
+ if (!response.ok) {
18
+ throw new Error(result.error);
19
+ }
20
+ this.currentBrand = result;
21
+ return result;
22
+ } catch (error) {
23
+ console.error('Error fetching current brand:', error);
24
+ return null;
25
+ }
26
+ }
27
+
28
+ async fetchBrands() {
29
+ try {
30
+ const response = await fetch('/brands.json');
31
+ const result = await response.json();
32
+ if (!response.ok) {
33
+ throw new Error(result.error);
34
+ }
35
+ this.allBrands = result.sort((a, b) => a.key.localeCompare(b.key));
36
+ return this.allBrands;
37
+ } catch (error) {
38
+ console.error('Error fetching brands:', error);
39
+ throw error;
40
+ }
41
+ }
42
+
43
+ async fetchAliases(brand) {
44
+ try {
45
+ const response = await fetch(`/brand/aliases?brand_key=${brand.key}`);
46
+ let result = await response.json();
47
+ if (!response.ok) {
48
+ throw new Error(result.error);
49
+ }
50
+ return result;
51
+ } catch (error) {
52
+ console.error('Error fetching aliases:', error);
53
+ return null;
54
+ }
55
+ }
56
+
57
+ async switchToBrand(brandKey) {
58
+ try {
59
+ const response = await fetch('/switch', {
60
+ method: 'POST',
61
+ headers: {
62
+ 'Content-Type': 'application/json',
63
+ },
64
+ body: JSON.stringify({brand_key: brandKey}),
65
+ });
66
+ const result = await response.json();
67
+ if (!response.ok) {
68
+ throw new Error(result.error);
69
+ }
70
+ return result;
71
+ } catch (error) {
72
+ console.error('Error switching to brand:', error);
73
+ throw error;
74
+ }
75
+ }
76
+
77
+ async onboardBrand(brandName, brandKey, cloneBrandKey = null) {
78
+ try {
79
+ const response = await fetch('/brand/onboard', {
80
+ method: 'POST',
81
+ headers: {
82
+ 'Content-Type': 'application/json',
83
+ },
84
+ body: JSON.stringify({brand_name: brandName, brand_key: brandKey, clone_brand_key: cloneBrandKey}),
85
+ });
86
+ const result = await response.json();
87
+ if (!response.ok) {
88
+ throw new Error(result.error);
89
+ }
90
+ return result;
91
+ } catch (error) {
92
+ console.error('Error onboarding brand:', error);
93
+ throw error;
94
+ }
95
+ }
96
+
97
+ async offboardBrand(brandKey) {
98
+ try {
99
+ const response = await fetch(`/brand/offboard?brand_key=${encodeURIComponent(brandKey)}`, {
100
+ method: 'GET',
101
+ });
102
+ const result = await response.json();
103
+ if (!response.ok) {
104
+ throw new Error(result.error);
105
+ }
106
+ return result;
107
+ } catch (error) {
108
+ console.error('Error offboarding brand:', error);
109
+ throw error;
110
+ }
111
+ }
112
+
113
+ async runDoctor(brandKey) {
114
+ try {
115
+ const response = await fetch(`/brand/doctor?brand_key=${encodeURIComponent(brandKey)}`);
116
+ const result = await response.json();
117
+ if (!response.ok) {
118
+ throw new Error(result.error);
119
+ }
120
+ return result.result;
121
+ } catch (error) {
122
+ console.error('Error calling doctor API:', error);
123
+ throw error;
124
+ }
125
+ }
126
+
127
+ filterBrands(searchTerm) {
128
+ return this.allBrands.filter(brand =>
129
+ brand.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
130
+ brand.key.toLowerCase().includes(searchTerm.toLowerCase())
131
+ );
132
+ }
133
+
134
+ }
135
+
136
+ export default BrandsModel;
@@ -0,0 +1,136 @@
1
+ import '../component/OnboardBrandBottomSheet.js';
2
+ import '../component/ConfirmationDialog.js';
3
+ import '../component/MessageBottomSheet.js';
4
+ import '../component/BrandOptionsBottomSheet.js';
5
+ import '../component/AliasesBottomSheet.js';
6
+
7
+ class BrandsView {
8
+ constructor(source) {
9
+ this.source = source
10
+ this.brandList = document.getElementById('brandList');
11
+ this.currentBrandSection = document.getElementById('currentBrandSection');
12
+ this.currentBrandItem = document.getElementById('currentBrandItem');
13
+ this.brandOptionsSheet = document.getElementById('bottomSheet');
14
+ this.confirmationDialog = document.getElementById('confirmationDialog');
15
+ this.messageBottomSheet = document.getElementById('messageBottomSheet');
16
+ this.onboardSheet = document.getElementById('onboardBottomSheet');
17
+ }
18
+
19
+ createBrandItem(brand, isCurrent = false) {
20
+ const brandItem = document.createElement('div');
21
+ brandItem.className = 'brand-item';
22
+ brandItem.innerHTML = `
23
+ <div class="brand-image">
24
+ <img src="../brand/icon?brand_key=${brand.key}" alt="${brand.name} icon">
25
+ </div>
26
+ <div class="brand-info">
27
+ <div class="brand-name">${brand.name}</div>
28
+ <div class="brand-key">${brand.key}</div>
29
+ </div>
30
+ <div class="brand-actions">
31
+ <button class="switch-button">Switch</button>
32
+ <div class="overflow-menu">
33
+ <i class="fas fa-ellipsis-v"></i>
34
+ </div>
35
+ </div>
36
+ `;
37
+
38
+ brandItem.addEventListener('click', () => {
39
+ window.location.href = this.brandUrl(brand.key);
40
+ });
41
+
42
+ const switchButton = brandItem.querySelector('.switch-button');
43
+ if (isCurrent && brand.content_changed) {
44
+ switchButton.textContent = "Apply Changes";
45
+ switchButton.style.display = "block";
46
+ } else if (isCurrent && !brand.content_changed) {
47
+ switchButton.style.display = "none";
48
+ } else {
49
+ switchButton.style.display = "block";
50
+ }
51
+
52
+ switchButton.addEventListener('click', async (event) => {
53
+ event.stopPropagation(); // Prevent the click from bubbling up to the parent
54
+ await this.onSwitch(brand.key);
55
+ });
56
+
57
+ // Add click event for the overflow menu
58
+ const overflowMenu = brandItem.querySelector('.overflow-menu');
59
+ overflowMenu.addEventListener('click', (event) => {
60
+ event.stopPropagation();
61
+ this.showBrandOptionsSheet(brand);
62
+ });
63
+
64
+ brandItem.dataset.brand = brand.key;
65
+ return brandItem;
66
+ }
67
+
68
+ renderBrands(brands, currentBrand) {
69
+ this.brandList.innerHTML = '';
70
+ this.currentBrandItem.innerHTML = '';
71
+
72
+ if (currentBrand) {
73
+ this.currentBrandSection.style.display = 'block';
74
+ this.currentBrandItem.appendChild(this.createBrandItem(currentBrand, true));
75
+ }
76
+
77
+ brands.forEach(brand => {
78
+ if (!currentBrand || brand.key !== currentBrand.key) {
79
+ const brandItem = this.createBrandItem(brand);
80
+ this.brandList.appendChild(brandItem);
81
+ }
82
+ });
83
+ }
84
+
85
+ showOnboardBrandForm() {
86
+ this.onboardSheet.show();
87
+ }
88
+
89
+ async hideOnboardBrandForm() {
90
+ this.onboardSheet.hide();
91
+ }
92
+
93
+ showAliasesBottomSheet(aliases, brand) {
94
+ const aliasesSheet = document.getElementById('aliasesSheet');
95
+ aliasesSheet.show(aliases, brand);
96
+ }
97
+
98
+ showBrandOptionsSheet(brand) {
99
+ this.brandOptionsSheet.dataset.brandName = brand.name;
100
+ this.brandOptionsSheet.dataset.brandKey = brand.key;
101
+ this.brandOptionsSheet.show();
102
+ }
103
+
104
+ showConfirmationDialog(message, onConfirm) {
105
+ this.confirmationDialog.showDialog(message, onConfirm);
106
+ }
107
+
108
+ showMessage(message) {
109
+ this.messageBottomSheet.showMessage(message);
110
+ }
111
+
112
+ updateErrorCount(count) {
113
+ const countElement = document.querySelector('.count');
114
+ countElement.textContent = count;
115
+ }
116
+
117
+ showErrorButton() {
118
+ const errorButton = document.getElementById('error-button');
119
+ errorButton.style.display = "flex";
120
+ }
121
+
122
+ hideErrorButton() {
123
+ const errorButton = document.getElementById('error-button');
124
+ errorButton.style.display = "none";
125
+ }
126
+
127
+ setOnSwitch(handler) {
128
+ this.onSwitch = handler;
129
+ }
130
+
131
+ brandUrl(brandKey) {
132
+ return `../brand/brand.html?brand_key=${encodeURIComponent(brandKey)}&source=${this.source}`;
133
+ }
134
+ }
135
+
136
+ export default BrandsView;