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,11 @@
1
+ // app.js
2
+ import BrandDetailModel from './BrandDetailModel.js';
3
+ import BrandDetailView from './BrandDetailView.js';
4
+ import BrandDetailController from './BrandDetailController.js';
5
+
6
+ document.addEventListener('DOMContentLoaded', async () => {
7
+ const model = new BrandDetailModel();
8
+ const view = new BrandDetailView(model);
9
+ const controller = new BrandDetailController(model, view);
10
+ await controller.initializeApp();
11
+ });
@@ -0,0 +1,361 @@
1
+ import {DataSource} from './BrandDetailModel.js';
2
+
3
+ class BrandDetailController {
4
+ constructor(model, view) {
5
+ this.model = model;
6
+ this.view = view;
7
+ this.onSectionChanged = this.onSectionChanged.bind(this);
8
+ this.deleteField = this.deleteField.bind(this);
9
+ this.initializeEventListeners();
10
+ this.view.setOnSectionChangedHandler(this.onSectionChanged.bind(this));
11
+ this.view.setOnDeleteFieldHandler(this.deleteField.bind(this));
12
+ }
13
+
14
+ async initializeApp() {
15
+ switch (this.model.source) {
16
+ case DataSource.LOCAL:
17
+ return await this.setupLoal();
18
+ case DataSource.REMOTE:
19
+ return await this.setupRemote();
20
+ default:
21
+ throw new Error('Unknown data source');
22
+ }
23
+ }
24
+
25
+ async setupRemote() {
26
+ this.view.uploadJsonBtn.addEventListener('click', async () => {
27
+ await this.uploadJson()
28
+ });
29
+
30
+ this.view.uploadBrandBtn.addEventListener('click', async () => {
31
+ await this.uploadBrand()
32
+ });
33
+ this.view.addNewBrandBtn.addEventListener('click', async () => {
34
+ await this.addNewBrand()
35
+ });
36
+ }
37
+
38
+ async uploadJson() {
39
+ try {
40
+ const [fileHandle] = await window.showOpenFilePicker({
41
+ types: [{
42
+ description: 'JSON File',
43
+ accept: {
44
+ 'application/json': ['.json'],
45
+ },
46
+ }],
47
+ });
48
+
49
+ const file = await fileHandle.getFile();
50
+
51
+ const json = await this.model.getBrandConfigurationsJsonFromDirectory(file);
52
+
53
+ if (!json) {
54
+ return
55
+ }
56
+ await this.addBrand(json.brand.key, json.brand.name, json.configurations)
57
+ } catch (error) {
58
+ console.error('Error:', error);
59
+ }
60
+ }
61
+
62
+ async uploadBrand() {
63
+ try {
64
+ const dirHandle = await window.showDirectoryPicker();
65
+ const configurations = await this.model.createBrandConfigurationsFromDirectory(dirHandle);
66
+
67
+ if (configurations.length === 0) {
68
+ alert("Please choose the appropriate brand folder that includes the brand JSON files.")
69
+ return
70
+ }
71
+
72
+ this.view.showOnboardBrandForm((key, name) => {
73
+ this.addBrand(key, name, configurations)
74
+ })
75
+ } catch (error) {
76
+ console.error('Error:', error);
77
+ }
78
+ }
79
+
80
+ async addNewBrand() {
81
+ this.view.showOnboardBrandForm(async (key, name) => {
82
+ const configurations = await this.model.createNewBrandConfogurations()
83
+ console.log(configurations)
84
+ await this.addBrand(key, name, configurations)
85
+ })
86
+ }
87
+
88
+ async addBrand(key, name, configurations) {
89
+ const result = await this.model.createBrandDetail(key, name, configurations)
90
+ await this.onLoadSections(result)
91
+ this.view.toggleAddBrandContainer(false);
92
+ }
93
+
94
+ async setupLoal() {
95
+ try {
96
+ const response = await this.model.fetchBrandDetails();
97
+ await this.onLoadSections(response.result);
98
+ const {isCurrentBrand, contentChanged} = await this.model.fetchCurrentBrand();
99
+
100
+ if (!isCurrentBrand) {
101
+ this.view.showSwitchButton();
102
+ } else if (contentChanged) {
103
+ this.view.showApplyChangesButton();
104
+ }
105
+
106
+ await this.checkBrandHealth();
107
+ } catch (error) {
108
+ console.error('Error initializing app:', error);
109
+ alert(error.message);
110
+ }
111
+ }
112
+
113
+ async onLoadSections(configuraationsResult) {
114
+ try {
115
+ this.view.updateAppNameTitle(`${configuraationsResult.brand.key} (${configuraationsResult.brand.name})`);
116
+ await this.showSections(configuraationsResult);
117
+ } catch (error) {
118
+ console.error('Error initializing app:', error);
119
+ alert(error.message);
120
+ }
121
+ }
122
+
123
+ initializeEventListeners() {
124
+ this.view.allBrandsButton.addEventListener('click', () => {
125
+ window.location.href = `../brands/brands.html?source=${this.model.source}`;
126
+ });
127
+
128
+ document.getElementById('applyChangesButton').addEventListener('click', () => this.switchToBrand());
129
+ document.getElementById('switchButton').addEventListener('click', () => this.switchToBrand());
130
+
131
+ this.view.exportBrandBtn.addEventListener('click', () => this.exportBrand());
132
+ }
133
+
134
+ async showSections(configuraationsResult) {
135
+ try {
136
+ this.view.sectionsContainer.dataset.brandName = configuraationsResult.brand.name
137
+ this.view.sectionsContainer.dataset.brandKey = configuraationsResult.brand.key
138
+
139
+ const sectionItems = configuraationsResult.configurations
140
+
141
+ for (let i = 0; i < sectionItems.length; i++) {
142
+ const sectionInfo = sectionItems[i];
143
+ console.log('Processing section:', i, sectionInfo);
144
+
145
+ if (sectionInfo.key === 'theme') {
146
+ this.createThemeSections(sectionInfo)
147
+ } else {
148
+ this.createSection(sectionInfo, sectionInfo.content, sectionInfo.name, sectionInfo.inputType)
149
+ }
150
+ }
151
+ } catch (error) {
152
+ console.error('Error loading configurations:', error);
153
+ alert(error.message);
154
+ }
155
+ }
156
+
157
+ createThemeSections(sectionInfo) {
158
+ this.createSection(sectionInfo, sectionInfo.content.colors, 'Theme Colors', 'color', 'colors')
159
+ this.createSection(sectionInfo, sectionInfo.content.typography, 'Theme Typography', 'text', 'typography')
160
+ this.createSection(sectionInfo, sectionInfo.content.spacing, 'Theme Spacing', 'text', 'spacing')
161
+ this.createSection(sectionInfo, sectionInfo.content.borderRadius, 'Theme Border Radius', 'text', 'borderRadius')
162
+ this.createSection(sectionInfo, sectionInfo.content.elevation, 'Theme Elevation', 'text', 'elevation')
163
+ }
164
+
165
+ createSection(sectionInfo, content, sectionName, inputType, propertiesGroupName = null) {
166
+ const sectionElement = this.view.createSection(sectionInfo.key, sectionName, inputType);
167
+ sectionElement.dataset.propertiesGroupName = propertiesGroupName
168
+
169
+ this.view.sectionsContainer.appendChild(sectionElement);
170
+
171
+ console.log('Section element created:', sectionElement);
172
+
173
+ this.view.populateSection(sectionInfo, sectionElement, content, inputType);
174
+
175
+ const addButton = document.createElement('button');
176
+ addButton.textContent = 'Add Field';
177
+ addButton.className = 'add-field-btn';
178
+ addButton.onclick = () => this.addNewField(sectionInfo, sectionElement, inputType);
179
+ sectionElement.appendChild(addButton);
180
+ }
181
+
182
+ async onSectionChanged(sectionItem, sectionElement) {
183
+ try {
184
+ const configuration = await this.getSectionData(sectionElement.dataset.key)
185
+ await this.model.saveSection(sectionItem, configuration);
186
+ this.view.showApplyChangesButton();
187
+ await this.checkBrandHealth();
188
+ } catch (error) {
189
+ console.error('Error saving section:', error);
190
+ alert(error.message);
191
+ }
192
+ }
193
+
194
+ getSectionConfiguration(sectionElement) {
195
+ const configuration = {};
196
+ const inputs = sectionElement.querySelectorAll('input:not(.array-item-input)');
197
+ inputs.forEach(input => {
198
+ const keys = input.id.split('.');
199
+ let current = configuration;
200
+ for (let i = 0; i < keys.length - 1; i++) {
201
+ if (!current[keys[i]]) {
202
+ current[keys[i]] = {};
203
+ }
204
+ current = current[keys[i]];
205
+ }
206
+ const lastKey = keys[keys.length - 1];
207
+
208
+ if (input.classList.contains('array-input')) {
209
+ const arrayItemsContainer = input.closest('.input-wrapper').querySelector('.array-items-container');
210
+ current[lastKey] = this.getArrayValue(arrayItemsContainer);
211
+ } else {
212
+ let value = input.value;
213
+ if (input.type === 'color') {
214
+ value = `#${value.substring(1).toUpperCase()}`;
215
+ } else if (!isNaN(value) && value.trim() !== '') {
216
+ // Convert to number if it's a valid number string
217
+ value = parseFloat(value);
218
+ }
219
+ current[lastKey] = value;
220
+ }
221
+ });
222
+ return configuration;
223
+ }
224
+
225
+ addNewField(sectionItem, sectionElement, inputType) {
226
+ this.view.showAddFieldForm(sectionItem, sectionElement, inputType);
227
+ }
228
+
229
+ deleteField(sectionItem, fieldContainer) {
230
+ this.view.showConfirmationDialog(
231
+ 'Are you sure you want to delete this item?',
232
+ () => {
233
+ const sectionElement = fieldContainer.closest('.section');
234
+ fieldContainer.remove();
235
+ this.onSectionChanged(sectionItem, sectionElement);
236
+ }
237
+ );
238
+ }
239
+
240
+ getArrayValue(container) {
241
+ const arrayItems = container.querySelectorAll('.array-item-input');
242
+ return Array.from(arrayItems).map(item => item.value);
243
+ }
244
+
245
+ async switchToBrand() {
246
+ try {
247
+ await this.model.switchToBrand();
248
+ const applyChangesButton = document.getElementById('applyChangesButton');
249
+ applyChangesButton.style.display = 'none';
250
+ location.reload();
251
+ } catch (error) {
252
+ console.error('Error switching to brand:', error);
253
+ alert(error.message);
254
+ }
255
+ }
256
+
257
+ async checkBrandHealth() {
258
+ try {
259
+ const result = await this.model.checkBrandHealth();
260
+ const errorButton = document.getElementById('error-button');
261
+
262
+ if (!result.passed) {
263
+ errorButton.style.display = "flex";
264
+ const errors = result.errors
265
+ .map((error, index) => `${index + 1}. ${error}`)
266
+ .join('\n');
267
+ this.view.updateErrorCount(result.errors.length);
268
+
269
+ errorButton.onclick = () => {
270
+ this.view.showMessage(`Health check for all brands completed with errors: \n\n${errors}`);
271
+ };
272
+ } else {
273
+ errorButton.style.display = "none";
274
+ }
275
+ } catch (error) {
276
+ console.error('Error checking brand health:', error);
277
+ alert(error.message);
278
+ }
279
+ }
280
+
281
+ async getSectionData(sectionKey) {
282
+ try {
283
+ const container = this.view.sectionsContainer;
284
+
285
+ const sectionElements = Array.from(container.querySelectorAll('.section'))
286
+ .filter(element => element.dataset.key === sectionKey);
287
+
288
+ if (sectionElements.length === 1) {
289
+ return this.getSectionConfiguration(sectionElements[0])
290
+ }
291
+
292
+ const configurations = sectionElements.map(sectionElement => {
293
+ const propertiesGroupName = sectionElement.dataset.propertiesGroupName;
294
+ const config = this.getSectionConfiguration(sectionElement);
295
+
296
+ if (propertiesGroupName !== "null") {
297
+ return {[propertiesGroupName]: config};
298
+ } else {
299
+ return config;
300
+ }
301
+ });
302
+
303
+ const result = configurations.reduce((acc, config) => {
304
+ const key = Object.keys(config)[0]; // Get the key from the configuration item
305
+ acc[key] = config[key]; // Assign the value to the dynamic object
306
+ return acc;
307
+ }, {});
308
+
309
+ const json = JSON.stringify(result, null, 2); // Pretty-printing JSON
310
+ console.log(json);
311
+ return result;
312
+
313
+ } catch (error) {
314
+ console.error('Error downloading brand:', error);
315
+ alert(error.message);
316
+ }
317
+ }
318
+
319
+ async exportBrand() {
320
+ try {
321
+ const sectionsContainer = this.view.sectionsContainer;
322
+
323
+ const brandKey = this.view.sectionsContainer.dataset.brandKey;
324
+ const brandName = this.view.sectionsContainer.dataset.brandName;
325
+
326
+ const sectionElements = Array.from(sectionsContainer.querySelectorAll('.section'));
327
+
328
+ const uniqueSections = new Map();
329
+
330
+ // The theme section has been divided into multiple categories (e.g., colors, typography).
331
+ // In the getSectionData function, we merge these sections. To prevent duplication when
332
+ // processing the sections, we will ensure that each section key is processed only once.
333
+ await Promise.all(sectionElements.map(async sectionElement => {
334
+ const key = sectionElement.dataset.key;
335
+ // Check if the key already exists in the map
336
+ if (!uniqueSections.has(key)) {
337
+ const configurations = await this.getSectionData(key);
338
+ uniqueSections.set(
339
+ key, {
340
+ key: key,
341
+ name: sectionElement.dataset.name,
342
+ inputType: sectionElement.dataset.inputType,
343
+ content: configurations
344
+ });
345
+ }
346
+ }));
347
+
348
+ // Convert the map values to an array
349
+ const result = Array.from(uniqueSections.values());
350
+
351
+ this.model.exportBrand(brandKey, brandName, result);
352
+
353
+ } catch (error) {
354
+ console.error('Error downloading brand:', error);
355
+ alert(error.message);
356
+ }
357
+ }
358
+
359
+ }
360
+
361
+ export default BrandDetailController;
@@ -0,0 +1,155 @@
1
+ import BrandLocalSource from './source/BrandLocalSource.js';
2
+ import BrandRemoteSource from './source/BrandRemoteSource.js';
3
+
4
+ export const DataSource = {
5
+ LOCAL: 'local',
6
+ REMOTE: 'remote',
7
+ };
8
+
9
+ class BrandDetailModel {
10
+ constructor() {
11
+ this.localSource = new BrandLocalSource();
12
+ this.remoteSource = new BrandRemoteSource();
13
+
14
+ this.brandKey = this.getQueryFromUrl('brand_key');
15
+
16
+ const sourceFromUrl = this.getQueryFromUrl('source');
17
+ // TODO: uncomment
18
+ this.source = sourceFromUrl === DataSource.LOCAL ? DataSource.LOCAL : DataSource.REMOTE;
19
+ // this.source = DataSource.REMOTE;
20
+ }
21
+
22
+ getQueryFromUrl(name) {
23
+ return this.localSource.getQueryFromUrl(name);
24
+ }
25
+
26
+ async fetchBrandDetails() {
27
+ switch (this.source) {
28
+ case DataSource.LOCAL:
29
+ return await this.localSource.fetchBrandDetails(this.brandKey);
30
+ case DataSource.REMOTE:
31
+ return await this.remoteSource.fetchBrandDetails(this.brandKey);
32
+ default:
33
+ throw new Error('Unknown data source');
34
+ }
35
+ }
36
+
37
+ async fetchCurrentBrand() {
38
+ switch (this.source) {
39
+ case DataSource.LOCAL:
40
+ return await this.localSource.fetchCurrentBrand(this.brandKey);
41
+ case DataSource.REMOTE:
42
+ return await this.remoteSource.fetchCurrentBrand(this.brandKey);
43
+ default:
44
+ throw new Error('Unknown data source');
45
+ }
46
+ }
47
+
48
+ async saveSection(sectionItem, configuration) {
49
+ switch (this.source) {
50
+ case DataSource.LOCAL:
51
+ return await this.localSource.saveSection(sectionItem, configuration, this.brandKey);
52
+ case DataSource.REMOTE:
53
+ return await this.remoteSource.saveSection(sectionItem, configuration, this.brandKey);
54
+ default:
55
+ throw new Error('Unknown data source');
56
+ }
57
+ }
58
+
59
+ async switchToBrand() {
60
+ switch (this.source) {
61
+ case DataSource.LOCAL:
62
+ return await this.localSource.switchToBrand(this.brandKey);
63
+ case DataSource.REMOTE:
64
+ return await this.remoteSource.switchToBrand(this.brandKey);
65
+ default:
66
+ throw new Error('Unknown data source');
67
+ }
68
+ }
69
+
70
+ async checkBrandHealth() {
71
+ switch (this.source) {
72
+ case DataSource.LOCAL:
73
+ return await this.localSource.checkBrandHealth(this.brandKey);
74
+ case DataSource.REMOTE:
75
+ return await this.remoteSource.checkBrandHealth(this.brandKey);
76
+ default:
77
+ throw new Error('Unknown data source');
78
+ }
79
+ }
80
+
81
+ async createNewBrandConfogurations() {
82
+ return await this.remoteSource.createNewBrandConfogurations();
83
+ }
84
+
85
+ async createBrandConfigurationsFromDirectory(dirHandle) {
86
+ return await this.remoteSource.createBrandConfigurationsFromDirectory(dirHandle);
87
+ }
88
+
89
+ async getBrandConfigurationsJsonFromDirectory(dirHandle) {
90
+ return await this.remoteSource.getBrandConfigurationsJsonFromDirectory(dirHandle);
91
+ }
92
+
93
+ async fetchSolaraVersion() {
94
+ const versionUrl = 'https://raw.githubusercontent.com/Solara-Kit/Solara/main/solara/lib/solara/version.rb';
95
+
96
+ try {
97
+ const response = await fetch(versionUrl);
98
+
99
+ if (!response.ok) {
100
+ throw new Error('Network response was not okay');
101
+ }
102
+
103
+ const data = await response.text();
104
+ // Assuming the version is in a line like `VERSION = "x.y.z"`
105
+ const versionMatch = data.match(/VERSION\s*=\s*["']([^"']+)["']/);
106
+
107
+ if (versionMatch) {
108
+ return versionMatch[1]; // Return the version
109
+ } else {
110
+ throw new Error('VERSION not found');
111
+ }
112
+
113
+ } catch (error) {
114
+ console.error('There was a problem with the fetch operation:', error);
115
+ return null; // Return null or handle the error as needed
116
+ }
117
+ }
118
+
119
+ async createBrandDetail(key, name, configurations) {
120
+ const solaraVersion = await this.fetchSolaraVersion()
121
+ return {
122
+ solaraVersion: solaraVersion,
123
+ brand: {key: key, name: name},
124
+ configurations: configurations
125
+ };
126
+ }
127
+
128
+ async exportBrand(key, name, configurations) {
129
+ try {
130
+ const data = await this.createBrandDetail(key, name, configurations)
131
+
132
+ const json = JSON.stringify(data, null, 2); // Pretty-printing JSON
133
+
134
+ // Create a Blob from the JSON string
135
+ const blob = new Blob([json], {type: 'application/json'});
136
+ const url = URL.createObjectURL(blob);
137
+
138
+ // Create a link element to download the Blob
139
+ const a = document.createElement('a');
140
+ a.href = url;
141
+ a.download = `${key}-solara-configurations.json`; // Set the filename
142
+ document.body.appendChild(a);
143
+ a.click(); // Programmatically click the link to trigger the download
144
+ document.body.removeChild(a); // Clean up the DOM
145
+
146
+ // Revoke the object URL after the download
147
+ URL.revokeObjectURL(url);
148
+ } catch (error) {
149
+ console.error('Error downloading brand:', error);
150
+ alert(error.message);
151
+ }
152
+ }
153
+ }
154
+
155
+ export default BrandDetailModel;