solara 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/bin/solara +18 -0
- data/solara/lib/.DS_Store +0 -0
- data/solara/lib/core/.DS_Store +0 -0
- data/solara/lib/core/aliases/alias_generator.rb +128 -0
- data/solara/lib/core/aliases/alias_generator_manager.rb +28 -0
- data/solara/lib/core/aliases/solara_terminal_setup.rb +103 -0
- data/solara/lib/core/brands/brand_onboarder.rb +46 -0
- data/solara/lib/core/brands/brand_switcher.rb +204 -0
- data/solara/lib/core/brands/brands_manager.rb +154 -0
- data/solara/lib/core/dashboard/.DS_Store +0 -0
- data/solara/lib/core/dashboard/brand/.DS_Store +0 -0
- data/solara/lib/core/dashboard/brand/BrandDetail.js +11 -0
- data/solara/lib/core/dashboard/brand/BrandDetailController.js +361 -0
- data/solara/lib/core/dashboard/brand/BrandDetailModel.js +155 -0
- data/solara/lib/core/dashboard/brand/BrandDetailView.js +245 -0
- data/solara/lib/core/dashboard/brand/brand.html +477 -0
- data/solara/lib/core/dashboard/brand/source/BrandLocalSource.js +123 -0
- data/solara/lib/core/dashboard/brand/source/BrandRemoteSource.js +260 -0
- data/solara/lib/core/dashboard/brands/Brands.js +10 -0
- data/solara/lib/core/dashboard/brands/BrandsController.js +155 -0
- data/solara/lib/core/dashboard/brands/BrandsModel.js +136 -0
- data/solara/lib/core/dashboard/brands/BrandsView.js +136 -0
- data/solara/lib/core/dashboard/brands/brands.html +345 -0
- data/solara/lib/core/dashboard/component/AddFieldSheet.js +212 -0
- data/solara/lib/core/dashboard/component/AliasesBottomSheet.js +128 -0
- data/solara/lib/core/dashboard/component/BrandOptionsBottomSheet.js +130 -0
- data/solara/lib/core/dashboard/component/ConfirmationDialog.js +103 -0
- data/solara/lib/core/dashboard/component/MessageBottomSheet.js +80 -0
- data/solara/lib/core/dashboard/component/OnboardBrandBottomSheet.js +214 -0
- data/solara/lib/core/dashboard/dashboard_manager.rb +19 -0
- data/solara/lib/core/dashboard/dashboard_server.rb +132 -0
- data/solara/lib/core/dashboard/handler/base_handler.rb +25 -0
- data/solara/lib/core/dashboard/handler/brand_alisases_handler.rb +33 -0
- data/solara/lib/core/dashboard/handler/brand_configurations_handler.rb +18 -0
- data/solara/lib/core/dashboard/handler/brand_configurations_manager.rb +73 -0
- data/solara/lib/core/dashboard/handler/brand_icon_handler.rb +20 -0
- data/solara/lib/core/dashboard/handler/brand_section_handler.rb +20 -0
- data/solara/lib/core/dashboard/handler/brands_handler.rb +14 -0
- data/solara/lib/core/dashboard/handler/current_brand_handler.rb +18 -0
- data/solara/lib/core/dashboard/handler/doctor_handler.rb +39 -0
- data/solara/lib/core/dashboard/handler/edit_section_handler.rb +55 -0
- data/solara/lib/core/dashboard/handler/offboard_brand_handler.rb +34 -0
- data/solara/lib/core/dashboard/handler/onboard_brand_handler.rb +53 -0
- data/solara/lib/core/dashboard/handler/redirect_handler.rb +12 -0
- data/solara/lib/core/dashboard/handler/switch_handler.rb +25 -0
- data/solara/lib/core/dashboard/index.html +36 -0
- data/solara/lib/core/dashboard/local.html +41 -0
- data/solara/lib/core/dashboard/res/favicon/android-chrome-192x192.png +0 -0
- data/solara/lib/core/dashboard/res/favicon/android-chrome-512x512.png +0 -0
- data/solara/lib/core/dashboard/res/favicon/apple-touch-icon.png +0 -0
- data/solara/lib/core/dashboard/res/favicon/favicon-16x16.png +0 -0
- data/solara/lib/core/dashboard/res/favicon/favicon-32x32.png +0 -0
- data/solara/lib/core/dashboard/res/favicon/favicon.ico +0 -0
- data/solara/lib/core/dashboard/res/favicon/site.webmanifest +1 -0
- data/solara/lib/core/dashboard/solara.png +0 -0
- data/solara/lib/core/doctor/brand_doctor.rb +94 -0
- data/solara/lib/core/doctor/doctor_manager.rb +35 -0
- data/solara/lib/core/doctor/project_doctor.rb +8 -0
- data/solara/lib/core/doctor/schema/brand_configurations.json +60 -0
- data/solara/lib/core/doctor/schema/platform/android/android_config.json +23 -0
- data/solara/lib/core/doctor/schema/platform/android/android_signing.json +23 -0
- data/solara/lib/core/doctor/schema/platform/ios/ios_config.json +27 -0
- data/solara/lib/core/doctor/schema/platform/ios/ios_signing.json +27 -0
- data/solara/lib/core/doctor/schema/platform/shared/theme.json +48 -0
- data/solara/lib/core/doctor/validator/brand_settings_validator.rb +55 -0
- data/solara/lib/core/doctor/validator/brand_settings_validator_manager.rb +82 -0
- data/solara/lib/core/doctor/validator/directory_structure_validator.rb +38 -0
- data/solara/lib/core/doctor/validator/file_structure_validator.rb +37 -0
- data/solara/lib/core/doctor/validator/json_file_validator.rb +21 -0
- data/solara/lib/core/doctor/validator/json_schema_validator.rb +32 -0
- data/solara/lib/core/doctor/validator/project_filesystem_validator.rb +70 -0
- data/solara/lib/core/doctor/validator/template/android_template_validation_config.yml +51 -0
- data/solara/lib/core/doctor/validator/template/flutter_template_validation_config.yml +53 -0
- data/solara/lib/core/doctor/validator/template/ios_template_validation_config.yml +51 -0
- data/solara/lib/core/doctor/validator/template/template_validator.rb +108 -0
- data/solara/lib/core/doctor/validator/validation_strategy.rb +7 -0
- data/solara/lib/core/scripts/brand_config_generator.rb +245 -0
- data/solara/lib/core/scripts/brand_config_manager.rb +90 -0
- data/solara/lib/core/scripts/brand_exporter.rb +38 -0
- data/solara/lib/core/scripts/brand_importer.rb +84 -0
- data/solara/lib/core/scripts/brand_offboarder.rb +19 -0
- data/solara/lib/core/scripts/brand_resources_manager.rb +77 -0
- data/solara/lib/core/scripts/directory_creator.rb +22 -0
- data/solara/lib/core/scripts/file_manager.rb +90 -0
- data/solara/lib/core/scripts/file_path.rb +327 -0
- data/solara/lib/core/scripts/folder_copier.rb +41 -0
- data/solara/lib/core/scripts/gitignore_manager.rb +54 -0
- data/solara/lib/core/scripts/interactive_file_system_validator.rb +110 -0
- data/solara/lib/core/scripts/platform/android/android_manifest_switcher.rb +24 -0
- data/solara/lib/core/scripts/platform/android/android_strings_switcher.rb +39 -0
- data/solara/lib/core/scripts/platform/android/gradle_switcher.rb +233 -0
- data/solara/lib/core/scripts/platform/android/properties_generator.rb +31 -0
- data/solara/lib/core/scripts/platform/ios/ios_file_path_manager.rb +109 -0
- data/solara/lib/core/scripts/platform/ios/ios_plist_manager.rb +42 -0
- data/solara/lib/core/scripts/platform/ios/xcconfig_generator.rb +44 -0
- data/solara/lib/core/scripts/platform/ios/xcode_asset_manager.rb +56 -0
- data/solara/lib/core/scripts/platform/ios/xcode_project_manager.rb +82 -0
- data/solara/lib/core/scripts/platform/ios/xcode_project_switcher.rb +130 -0
- data/solara/lib/core/scripts/project_settings_manager.rb +39 -0
- data/solara/lib/core/scripts/solara_logger.rb +103 -0
- data/solara/lib/core/scripts/solara_settings_manager.rb +73 -0
- data/solara/lib/core/scripts/solara_status_manager.rb +55 -0
- data/solara/lib/core/scripts/solara_version_manager.rb +42 -0
- data/solara/lib/core/scripts/strings_xml_manager.rb +22 -0
- data/solara/lib/core/scripts/terminal_input_manager.rb +22 -0
- data/solara/lib/core/scripts/theme_generator.rb +250 -0
- data/solara/lib/core/scripts/yaml_manager.rb +72 -0
- data/solara/lib/core/solara_configurator.rb +15 -0
- data/solara/lib/core/template/brands/android/android_config.json +8 -0
- data/solara/lib/core/template/brands/android/android_signing.json +6 -0
- data/solara/lib/core/template/brands/android/res/.DS_Store +0 -0
- data/solara/lib/core/template/brands/android/res/mipmap-hdpi/ic_launcher.png +0 -0
- data/solara/lib/core/template/brands/android/res/mipmap-hdpi/ic_launcher_round.png +0 -0
- data/solara/lib/core/template/brands/android/res/mipmap-mdpi/ic_launcher.png +0 -0
- data/solara/lib/core/template/brands/android/res/mipmap-mdpi/ic_launcher_round.png +0 -0
- data/solara/lib/core/template/brands/android/res/mipmap-xhdpi/ic_launcher.png +0 -0
- data/solara/lib/core/template/brands/android/res/mipmap-xhdpi/ic_launcher_round.png +0 -0
- data/solara/lib/core/template/brands/android/res/mipmap-xxhdpi/ic_launcher.png +0 -0
- data/solara/lib/core/template/brands/android/res/mipmap-xxhdpi/ic_launcher_round.png +0 -0
- data/solara/lib/core/template/brands/android/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
- data/solara/lib/core/template/brands/android/res/mipmap-xxxhdpi/ic_launcher_round.png +0 -0
- data/solara/lib/core/template/brands/brands.json +4 -0
- data/solara/lib/core/template/brands/ios/assets/.DS_Store +0 -0
- data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/100.png +0 -0
- data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/102.png +0 -0
- data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/1024.png +0 -0
- data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/114.png +0 -0
- data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/120.png +0 -0
- data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/128.png +0 -0
- data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/144.png +0 -0
- data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/152.png +0 -0
- data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/16.png +0 -0
- data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/167.png +0 -0
- data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/172.png +0 -0
- data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/180.png +0 -0
- data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/196.png +0 -0
- data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/20.png +0 -0
- data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/216.png +0 -0
- data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/256.png +0 -0
- data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/29.png +0 -0
- data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/32.png +0 -0
- data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/40.png +0 -0
- data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/48.png +0 -0
- data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/50.png +0 -0
- data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/512.png +0 -0
- data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/55.png +0 -0
- data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/57.png +0 -0
- data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/58.png +0 -0
- data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/60.png +0 -0
- data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/64.png +0 -0
- data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/66.png +0 -0
- data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/72.png +0 -0
- data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/76.png +0 -0
- data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/80.png +0 -0
- data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/87.png +0 -0
- data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/88.png +0 -0
- data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/92.png +0 -0
- data/solara/lib/core/template/brands/ios/assets/AppIcon.appiconset/Contents.json +1 -0
- data/solara/lib/core/template/brands/ios/ios_config.json +7 -0
- data/solara/lib/core/template/brands/ios/ios_signing.json +7 -0
- data/solara/lib/core/template/brands/shared/.DS_Store +0 -0
- data/solara/lib/core/template/brands/shared/brand_config.json +2 -0
- data/solara/lib/core/template/brands/shared/theme.json +46 -0
- data/solara/lib/core/template/config/android_template_config.json +57 -0
- data/solara/lib/core/template/config/flutter_template_config.json +62 -0
- data/solara/lib/core/template/config/ios_template_config.json +57 -0
- data/solara/lib/core/template/project_template_generator.rb +63 -0
- data/solara/lib/platform_detector.rb +84 -0
- data/solara/lib/solara/cli.rb +5 -0
- data/solara/lib/solara/version.rb +3 -0
- data/solara/lib/solara.rb +238 -0
- data/solara/lib/solara_initializer.rb +44 -0
- data/solara/lib/solara_manager.rb +73 -0
- 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;
|