solara 0.5.0 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/solara/lib/.DS_Store +0 -0
- data/solara/lib/core/.DS_Store +0 -0
- data/solara/lib/core/brands/brand_onboarder.rb +29 -25
- data/solara/lib/core/brands/brand_switcher.rb +59 -24
- data/solara/lib/core/dashboard/brand/BrandDetailController.js +6 -3
- data/solara/lib/core/dashboard/brand/BrandDetailModel.js +1 -0
- data/solara/lib/core/dashboard/brand/SectionsFormManager.js +76 -15
- data/solara/lib/core/dashboard/brand/brand.html +47 -8
- data/solara/lib/core/dashboard/brand/source/BrandLocalSource.js +2 -2
- data/solara/lib/core/dashboard/brand/source/BrandRemoteSource.js +19 -3
- data/solara/lib/core/dashboard/component/OnboardBrandBottomSheet.js +4 -0
- data/solara/lib/core/dashboard/handler/edit_section_handler.rb +5 -17
- data/solara/lib/core/dashboard/handler/onboard_brand_handler.rb +0 -15
- data/solara/lib/core/doctor/schema/brand_configurations.json +2 -2
- data/solara/lib/core/doctor/schema/platform/json_manifest.json +20 -38
- data/solara/lib/core/doctor/validator/template/android_template_validation_config.yml +3 -3
- data/solara/lib/core/doctor/validator/template/flutter_template_validation_config.yml +2 -2
- data/solara/lib/core/doctor/validator/template/ios_template_validation_config.yml +3 -3
- data/solara/lib/core/scripts/brand_config_updater.rb +29 -0
- data/solara/lib/core/scripts/brand_configurations_manager.rb +76 -14
- data/solara/lib/core/scripts/brand_importer.rb +9 -20
- data/solara/lib/core/scripts/code_generator.rb +1 -1
- data/solara/lib/core/scripts/file_manager.rb +11 -15
- data/solara/lib/core/scripts/gitignore_manager.rb +3 -4
- data/solara/lib/core/scripts/json_manifest_processor.rb +86 -45
- data/solara/lib/core/scripts/resource_manifest_processor.rb +11 -10
- data/solara/lib/core/scripts/string_case.rb +18 -0
- data/solara/lib/core/template/.DS_Store +0 -0
- data/solara/lib/core/template/brands/json/Json-Manifest.md +8 -10
- data/solara/lib/core/template/brands/json/json_manifest.json +9 -11
- data/solara/lib/core/template/config/android_template_config.json +6 -6
- data/solara/lib/core/template/config/flutter_template_config.json +4 -4
- data/solara/lib/core/template/config/ios_template_config.json +6 -6
- data/solara/lib/core/template/configurations.json +34 -21
- data/solara/lib/core/template/project_template_generator.rb +119 -13
- data/solara/lib/solara/version.rb +1 -1
- data/solara/lib/solara_manager.rb +25 -13
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ee9572ad8162826e8ebd8ed6b9f5e6a0ba5c2ef23ecec56ed610206becc3e80f
|
4
|
+
data.tar.gz: d0cfbf2ba68d160fdf531f3148e216662063d2a9182c341ea22e1266e283bcf5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7e2e2c181701aa9af6099694beedc3f68e926446e8a1d67b4841e66b5ecf1da8fad76c9b479a52738f0bf7a48551328b7058c0bbafcc1574a68b9b388b37035a
|
7
|
+
data.tar.gz: e3e2d47d57ed66593435bb6294013c99711177bfadb4ae25ea74d2ebb1d511abc0eb98dd7fa9f4579106cf7ec26cf76393294b7d4fd9b6c89aa0c679343ef8e3
|
data/solara/lib/.DS_Store
CHANGED
Binary file
|
data/solara/lib/core/.DS_Store
CHANGED
Binary file
|
@@ -5,43 +5,47 @@ Dir[File.expand_path('platform/ios/*.rb', __dir__)].each { |file| require_relati
|
|
5
5
|
Dir[File.expand_path('platform/flutter/*.rb', __dir__)].each { |file| require_relative file }
|
6
6
|
|
7
7
|
class BrandOnboarder
|
8
|
-
def initialize(brand_key, brand_name, clone_brand_key: nil)
|
9
|
-
@brand_key = brand_key
|
10
|
-
@brand_name = brand_name
|
11
|
-
@clone_brand_key = clone_brand_key
|
12
|
-
end
|
13
8
|
|
14
|
-
def onboard
|
15
|
-
if
|
16
|
-
|
9
|
+
def onboard(brand_key, brand_name, clone_brand_key: nil)
|
10
|
+
if clone_brand_key.nil? || clone_brand_key.strip.empty?
|
11
|
+
generate_from_template(brand_key)
|
17
12
|
else
|
18
|
-
clone_brand
|
13
|
+
clone_brand(brand_key, clone_brand_key: clone_brand_key)
|
19
14
|
end
|
20
|
-
add_to_brands_list
|
15
|
+
add_to_brands_list(brand_key, brand_name)
|
21
16
|
end
|
22
17
|
|
23
|
-
def
|
24
|
-
|
25
|
-
|
26
|
-
template_dir = FilePath.template_brands
|
27
|
-
target_dir = FilePath.brand(@brand_key)
|
28
|
-
config_file = FilePath.template_config
|
29
|
-
|
30
|
-
generator = ProjectTemplateGenerator.new(template_dir, target_dir, config_file)
|
31
|
-
generator.create_project
|
18
|
+
def sync_with_template(brand_key)
|
19
|
+
generator = template_generator(brand_key)
|
20
|
+
generator.sync_with_template
|
32
21
|
end
|
33
22
|
|
34
|
-
def clone_brand
|
35
|
-
Solara.logger.debug("Cloning #{
|
36
|
-
source = FilePath.brand(
|
37
|
-
destination = FilePath.brand(
|
23
|
+
def clone_brand(brand_key, clone_brand_key:)
|
24
|
+
Solara.logger.debug("Cloning #{clone_brand_key} to #{brand_key}")
|
25
|
+
source = FilePath.brand(clone_brand_key)
|
26
|
+
destination = FilePath.brand(brand_key)
|
38
27
|
|
39
28
|
FileManager.delete_if_exists(destination)
|
40
29
|
FolderCopier.new(source, destination).copy
|
41
30
|
end
|
42
31
|
|
43
|
-
def add_to_brands_list
|
44
|
-
BrandsManager.instance.add_brand(
|
32
|
+
def add_to_brands_list(brand_key, brand_name)
|
33
|
+
BrandsManager.instance.add_brand(brand_name, brand_key)
|
34
|
+
end
|
35
|
+
|
36
|
+
def generate_from_template(brand_key)
|
37
|
+
Solara.logger.debug("Onboarding #{brand_key} from template.")
|
38
|
+
|
39
|
+
generator = template_generator(brand_key)
|
40
|
+
generator.create_project
|
41
|
+
end
|
42
|
+
|
43
|
+
def template_generator(brand_key)
|
44
|
+
template_dir = FilePath.template_brands
|
45
|
+
target_dir = FilePath.brand(brand_key)
|
46
|
+
config_file = FilePath.template_config
|
47
|
+
|
48
|
+
ProjectTemplateGenerator.new(template_dir, target_dir, config_file)
|
45
49
|
end
|
46
50
|
|
47
51
|
end
|
@@ -16,6 +16,7 @@ class BrandSwitcher
|
|
16
16
|
def start
|
17
17
|
Solara.logger.header("Switching to #{@brand_key}")
|
18
18
|
|
19
|
+
BrandOnboarder.new.sync_with_template(@brand_key)
|
19
20
|
@health_checker.check_health
|
20
21
|
BrandsManager.instance.save_current_brand(@brand_key)
|
21
22
|
@artifacts_switcher.switch
|
@@ -30,7 +31,7 @@ class BrandSwitcher
|
|
30
31
|
def switch
|
31
32
|
BrandFontSwitcher.new(@brand_key).switch
|
32
33
|
|
33
|
-
ResourceManifestSwitcher.new(@brand_key).switch
|
34
|
+
ResourceManifestSwitcher.new(@brand_key, ignore_health_check: @ignore_health_check).switch
|
34
35
|
JsonManifestSwitcher.new(@brand_key).switch
|
35
36
|
|
36
37
|
case @platform
|
@@ -51,13 +52,14 @@ class BrandSwitcher
|
|
51
52
|
end
|
52
53
|
|
53
54
|
class ResourceManifestSwitcher
|
54
|
-
def initialize(brand_key)
|
55
|
+
def initialize(brand_key, ignore_health_check:)
|
55
56
|
@brand_key = brand_key
|
57
|
+
@ignore_health_check = ignore_health_check
|
56
58
|
end
|
57
59
|
|
58
60
|
def switch
|
59
61
|
Solara.logger.start_step("Process resource manifest: #{FilePath.resources_manifest}")
|
60
|
-
brand_resource_copier = ResourceManifestProcessor.new(@brand_key)
|
62
|
+
brand_resource_copier = ResourceManifestProcessor.new(@brand_key, ignore_health_check: @ignore_health_check)
|
61
63
|
brand_resource_copier.copy
|
62
64
|
Solara.logger.debug("#{@brand_key} resources copied successfully according to the manifest: #{FilePath.resources_manifest}.")
|
63
65
|
Solara.logger.end_step("Process resource manifest: #{FilePath.resources_manifest}")
|
@@ -66,39 +68,72 @@ class ResourceManifestSwitcher
|
|
66
68
|
end
|
67
69
|
|
68
70
|
class JsonManifestSwitcher
|
71
|
+
PLATFORM_CONFIGS = {
|
72
|
+
Platform::Flutter => {
|
73
|
+
language: Language::Dart,
|
74
|
+
output_path: :flutter_lib_artifacts
|
75
|
+
},
|
76
|
+
Platform::IOS => {
|
77
|
+
language: Language::Swift,
|
78
|
+
output_path: :ios_project_root_artifacts
|
79
|
+
},
|
80
|
+
Platform::Android => {
|
81
|
+
language: Language::Kotlin,
|
82
|
+
output_path: :android_project_java_artifacts
|
83
|
+
}
|
84
|
+
}.freeze
|
85
|
+
|
69
86
|
def initialize(brand_key)
|
70
87
|
@brand_key = brand_key
|
71
|
-
@
|
88
|
+
@brand_manifest_path = FilePath.brand_json_dir(brand_key)
|
89
|
+
@platform = SolaraSettingsManager.instance.platform
|
90
|
+
validate_inputs
|
72
91
|
end
|
73
92
|
|
74
93
|
def switch
|
75
|
-
|
94
|
+
with_logging do
|
95
|
+
process_manifests
|
96
|
+
end
|
97
|
+
end
|
76
98
|
|
99
|
+
private
|
77
100
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
process_maifest(Language::Swift, FilePath.ios_project_root_artifacts)
|
84
|
-
process_maifest(Language::Swift, FilePath.brand_global_json_dir)
|
85
|
-
when Platform::Android
|
86
|
-
process_maifest(Language::Kotlin, FilePath.android_project_java_artifacts )
|
87
|
-
process_maifest(Language::Kotlin, FilePath.brand_global_json_dir)
|
88
|
-
else
|
89
|
-
raise ArgumentError, "Invalid platform: #{@platform}"
|
90
|
-
end
|
101
|
+
def validate_inputs
|
102
|
+
raise ArgumentError, "Brand key cannot be nil" if @brand_key.nil?
|
103
|
+
raise ArgumentError, "Invalid platform: #{@platform}" unless PLATFORM_CONFIGS.key?(@platform)
|
104
|
+
raise ArgumentError, "Manifest path not found: #{@brand_manifest_path}" unless File.exist?(@brand_manifest_path)
|
105
|
+
end
|
91
106
|
|
92
|
-
|
107
|
+
def with_logging
|
108
|
+
log_message = "Process JSON manifest: #{@brand_manifest_path}"
|
109
|
+
Solara.logger.start_step(log_message)
|
110
|
+
yield
|
111
|
+
Solara.logger.end_step(log_message)
|
112
|
+
rescue StandardError => e
|
113
|
+
Solara.logger.error("Failed to process manifest: #{e.message}")
|
114
|
+
raise
|
115
|
+
end
|
116
|
+
|
117
|
+
def process_manifests
|
118
|
+
config = PLATFORM_CONFIGS[@platform]
|
119
|
+
output_path = FilePath.send(config[:output_path])
|
120
|
+
|
121
|
+
[
|
122
|
+
@brand_manifest_path,
|
123
|
+
FilePath.brand_global_json_dir
|
124
|
+
].each do |manifest_path|
|
125
|
+
process_manifest(config[:language], manifest_path, output_path)
|
126
|
+
end
|
93
127
|
end
|
94
128
|
|
95
|
-
def
|
96
|
-
|
97
|
-
|
129
|
+
def process_manifest(language, manifest_path, output_path)
|
130
|
+
JsonManifestProcessor.new(
|
131
|
+
manifest_path,
|
98
132
|
language,
|
99
133
|
output_path
|
100
|
-
)
|
101
|
-
|
134
|
+
).process
|
135
|
+
rescue StandardError => e
|
136
|
+
raise StandardError, "Failed to process #{manifest_path}: #{e.message}"
|
102
137
|
end
|
103
138
|
end
|
104
139
|
|
@@ -91,6 +91,7 @@ class BrandDetailController {
|
|
91
91
|
const response = await this.model.fetchBrandDetails();
|
92
92
|
await this.onLoadSections(response.result);
|
93
93
|
const {isCurrentBrand, contentChanged} = await this.model.fetchCurrentBrand();
|
94
|
+
this.model.isCurrentBrand = isCurrentBrand
|
94
95
|
|
95
96
|
if (isCurrentBrand) {
|
96
97
|
this.view.setupSyncBrandButton(contentChanged ? '#ff4136' : '#4A90E2');
|
@@ -109,7 +110,7 @@ class BrandDetailController {
|
|
109
110
|
try {
|
110
111
|
this.view.addBrandOverlay.style.display = 'none'
|
111
112
|
this.view.header.style.display = 'flex';
|
112
|
-
this.view.updateAppNameTitle(`${configuraationsResult.brand.
|
113
|
+
this.view.updateAppNameTitle(`${configuraationsResult.brand.name} - ${configuraationsResult.brand.key}`);
|
113
114
|
await this.showSections(configuraationsResult);
|
114
115
|
this.view.showIndex();
|
115
116
|
} catch (error) {
|
@@ -157,9 +158,11 @@ class BrandDetailController {
|
|
157
158
|
if (this.model.isRemote()) return
|
158
159
|
|
159
160
|
try {
|
160
|
-
await this.model.saveSection(container.dataset.
|
161
|
+
await this.model.saveSection(container.dataset.filename, section.content);
|
161
162
|
await this.checkBrandHealth();
|
162
|
-
|
163
|
+
if (this.model.isCurrentBrand) {
|
164
|
+
await this.switchToBrand(true)
|
165
|
+
}
|
163
166
|
} catch (error) {
|
164
167
|
console.error('Error saving section:', error);
|
165
168
|
alert(error.message);
|
@@ -1,7 +1,6 @@
|
|
1
1
|
import '../component/EditJsonSheet.js';
|
2
2
|
|
3
3
|
class SectionsFormManager {
|
4
|
-
|
5
4
|
constructor() {
|
6
5
|
this.sections = [];
|
7
6
|
this.sectionsContainer = document.getElementById('sections');
|
@@ -23,7 +22,7 @@ class SectionsFormManager {
|
|
23
22
|
const sectionElement = document.createElement('div');
|
24
23
|
sectionElement.className = 'section';
|
25
24
|
|
26
|
-
sectionElement.dataset.
|
25
|
+
sectionElement.dataset.filename = section.filename
|
27
26
|
sectionElement.dataset.name = section.name
|
28
27
|
|
29
28
|
const titleContainer = document.createElement('div');
|
@@ -36,7 +35,7 @@ class SectionsFormManager {
|
|
36
35
|
|
37
36
|
sectionElement.appendChild(titleContainer);
|
38
37
|
|
39
|
-
sectionElement.id = section.
|
38
|
+
sectionElement.id = section.filename;
|
40
39
|
|
41
40
|
this.sectionsContainer.appendChild(sectionElement);
|
42
41
|
|
@@ -49,7 +48,6 @@ class SectionsFormManager {
|
|
49
48
|
})
|
50
49
|
return Array.from(data);
|
51
50
|
}
|
52
|
-
|
53
51
|
}
|
54
52
|
|
55
53
|
class SectionItemManager {
|
@@ -63,7 +61,7 @@ class SectionItemManager {
|
|
63
61
|
displayJSONCards() {
|
64
62
|
let cardContent = this.container.querySelector('.card-content')
|
65
63
|
if (cardContent !== null) this.container.innerHTML = ''
|
66
|
-
this.container.appendChild(this.createCard(this.section.content, 'root', null, this.section.
|
64
|
+
this.container.appendChild(this.createCard(this.section.content, 'root', null, this.section.filename));
|
67
65
|
}
|
68
66
|
|
69
67
|
createCard(obj, key, parent, cardTitle) {
|
@@ -113,7 +111,7 @@ class SectionItemManager {
|
|
113
111
|
|
114
112
|
const isArray = Array.isArray(obj)
|
115
113
|
|
116
|
-
|
114
|
+
for (const [k, v] of Object.entries(obj)) {
|
117
115
|
const item = document.createElement('div');
|
118
116
|
|
119
117
|
const cardValueContainer = document.createElement('div');
|
@@ -135,18 +133,62 @@ class SectionItemManager {
|
|
135
133
|
item.className = 'card-item';
|
136
134
|
itemKey.textContent = k.replace(/_/g, ' ')
|
137
135
|
|
138
|
-
if (
|
136
|
+
if (typeof v === 'boolean') {
|
137
|
+
// Create a container for the entire boolean input
|
138
|
+
const booleanContainer = document.createElement('div');
|
139
|
+
booleanContainer.className = 'boolean-container';
|
140
|
+
|
141
|
+
const checkboxContainer = document.createElement('div');
|
142
|
+
checkboxContainer.className = 'card-value checkbox-container';
|
143
|
+
|
144
|
+
const itemValue = document.createElement('input');
|
145
|
+
itemValue.type = 'checkbox';
|
146
|
+
itemValue.className = 'card-value checkbox';
|
147
|
+
itemValue.checked = v;
|
148
|
+
|
149
|
+
const valueLabel = document.createElement('span');
|
150
|
+
valueLabel.className = 'checkbox-value';
|
151
|
+
valueLabel.textContent = v.toString();
|
152
|
+
|
153
|
+
const updateValue = () => {
|
154
|
+
const newValue = !itemValue.checked;
|
155
|
+
itemValue.checked = newValue;
|
156
|
+
valueLabel.textContent = newValue.toString();
|
157
|
+
this.updateValue(obj, k, newValue, typeof v);
|
158
|
+
};
|
159
|
+
|
160
|
+
// Add click handlers to both container and checkbox
|
161
|
+
booleanContainer.onclick = (e) => {
|
162
|
+
if (e.target !== itemValue) { // Prevent double-toggle when clicking checkbox
|
163
|
+
updateValue();
|
164
|
+
}
|
165
|
+
};
|
166
|
+
|
167
|
+
itemValue.onchange = () => {
|
168
|
+
valueLabel.textContent = itemValue.checked.toString();
|
169
|
+
this.updateValue(obj, k, itemValue.checked, typeof v);
|
170
|
+
};
|
171
|
+
|
172
|
+
checkboxContainer.appendChild(itemValue);
|
173
|
+
checkboxContainer.appendChild(valueLabel);
|
174
|
+
|
175
|
+
// Move the key inside the boolean container
|
176
|
+
booleanContainer.appendChild(itemKey);
|
177
|
+
booleanContainer.appendChild(checkboxContainer);
|
178
|
+
|
179
|
+
cardValueContainer.appendChild(booleanContainer);
|
180
|
+
} else if (this.isColorValue(v)) {
|
139
181
|
const itemValue = document.createElement('input');
|
140
182
|
itemValue.type = 'color';
|
141
183
|
itemValue.className = 'card-value';
|
142
184
|
itemValue.value = v;
|
143
|
-
itemValue.onchange = () => this.updateValue(obj, k, itemValue.value);
|
185
|
+
itemValue.onchange = () => this.updateValue(obj, k, itemValue.value, typeof v);
|
144
186
|
cardValueContainer.appendChild(itemValue);
|
145
187
|
} else {
|
146
188
|
const itemValue = document.createElement('textarea');
|
147
189
|
itemValue.className = 'card-value';
|
148
190
|
itemValue.value = v;
|
149
|
-
itemValue.onchange = () => this.updateValue(obj, k, itemValue.value);
|
191
|
+
itemValue.onchange = () => this.updateValue(obj, k, itemValue.value, typeof v);
|
150
192
|
cardValueContainer.appendChild(itemValue);
|
151
193
|
}
|
152
194
|
|
@@ -165,8 +207,7 @@ class SectionItemManager {
|
|
165
207
|
}
|
166
208
|
|
167
209
|
isColorValue(value) {
|
168
|
-
|
169
|
-
const hexPattern = /^#([0-9A-F]{3}){1,2}([0-9A-F]{2})?$/i; // 3, 6, or 8 hex digits
|
210
|
+
const hexPattern = /^#([0-9A-F]{3}){1,2}([0-9A-F]{2})?$/i;
|
170
211
|
const rgbaPattern = /^rgba?\(\s*(\d{1,3}\s*,\s*){2}\d{1,3}\s*,?\s*(0|1|0?\.\d+|1?\.\d+)\s*\)$/;
|
171
212
|
|
172
213
|
return hexPattern.test(value) || rgbaPattern.test(value);
|
@@ -182,14 +223,35 @@ class SectionItemManager {
|
|
182
223
|
this.notifyChange()
|
183
224
|
}
|
184
225
|
|
185
|
-
updateValue(obj, key, value) {
|
226
|
+
updateValue(obj, key, value, originalType) {
|
186
227
|
try {
|
187
|
-
|
228
|
+
// Handle different types based on the original value's type
|
229
|
+
switch (originalType) {
|
230
|
+
case 'string':
|
231
|
+
obj[key] = String(value);
|
232
|
+
break;
|
233
|
+
case 'number':
|
234
|
+
// If the original was a number, keep it number
|
235
|
+
if (!isNaN(value) || value === '') {
|
236
|
+
obj[key] = Number(value);
|
237
|
+
}
|
238
|
+
break;
|
239
|
+
case 'boolean':
|
240
|
+
obj[key] = value.toLowerCase() === 'true';
|
241
|
+
break;
|
242
|
+
default:
|
243
|
+
// Try to parse as JSON, fallback to string if it fails
|
244
|
+
try {
|
245
|
+
obj[key] = JSON.parse(value);
|
246
|
+
} catch {
|
247
|
+
obj[key] = value;
|
248
|
+
}
|
249
|
+
}
|
188
250
|
} catch {
|
189
251
|
obj[key] = value;
|
190
252
|
}
|
191
253
|
this.displayJSONCards();
|
192
|
-
this.notifyChange()
|
254
|
+
this.notifyChange();
|
193
255
|
}
|
194
256
|
|
195
257
|
confirmDeleteProperty(obj, key) {
|
@@ -228,5 +290,4 @@ class SectionItemManager {
|
|
228
290
|
}
|
229
291
|
}
|
230
292
|
|
231
|
-
|
232
293
|
export default SectionsFormManager;
|
@@ -90,7 +90,7 @@
|
|
90
90
|
.right {
|
91
91
|
width: 15%;
|
92
92
|
position: fixed;
|
93
|
-
max-height:
|
93
|
+
max-height: 95%;
|
94
94
|
overflow-y: auto;
|
95
95
|
padding: 7px;
|
96
96
|
right: 0;
|
@@ -253,7 +253,7 @@
|
|
253
253
|
#error-button {
|
254
254
|
position: fixed;
|
255
255
|
bottom: 14px;
|
256
|
-
|
256
|
+
left: 14px;
|
257
257
|
background-color: #ff4136;
|
258
258
|
color: white;
|
259
259
|
border: none;
|
@@ -515,6 +515,45 @@
|
|
515
515
|
background-color: var(--background-color);
|
516
516
|
color: var(--text-color);
|
517
517
|
}
|
518
|
+
.boolean-container {
|
519
|
+
flex-direction: row;
|
520
|
+
display: flex;
|
521
|
+
align-items: center;
|
522
|
+
|
523
|
+
cursor: pointer;
|
524
|
+
padding: 0px;
|
525
|
+
flex: 1;
|
526
|
+
}
|
527
|
+
|
528
|
+
.boolean-container:hover {
|
529
|
+
background-color: var(--background-color));
|
530
|
+
}
|
531
|
+
|
532
|
+
.card-value.checkbox-container {
|
533
|
+
flex-direction: row;
|
534
|
+
display: flex;
|
535
|
+
align-items: start;
|
536
|
+
gap: 0px;
|
537
|
+
padding-bottom: 10px;
|
538
|
+
margin-left: 10px;
|
539
|
+
}
|
540
|
+
|
541
|
+
.card-value.checkbox {
|
542
|
+
width: auto;
|
543
|
+
height: auto;
|
544
|
+
margin: 0;
|
545
|
+
flex: 0;
|
546
|
+
cursor: pointer;
|
547
|
+
}
|
548
|
+
|
549
|
+
.checkbox-value {
|
550
|
+
font-size: 1.2em;
|
551
|
+
color: var(--text-color);
|
552
|
+
user-select: none;
|
553
|
+
flex: 0;
|
554
|
+
margin-left: 10px;
|
555
|
+
}
|
556
|
+
|
518
557
|
.card-actions {
|
519
558
|
display: flex;
|
520
559
|
align-items: center;
|
@@ -556,7 +595,7 @@
|
|
556
595
|
<img class="loading-overlay-logo" src="../solara.png" alt="Loading Logo">
|
557
596
|
</div>
|
558
597
|
|
559
|
-
<div id="toast" style="
|
598
|
+
<div id="toast" style=" display: none; position: fixed; top: 10%; left: 50%; transform: translate(-50%, 10%); background-color: #4CAF50; color: white; padding: 16px; border-radius: 5px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);">
|
560
599
|
<!-- Message will be set dynamically -->
|
561
600
|
</div>
|
562
601
|
|
@@ -591,11 +630,11 @@
|
|
591
630
|
<img src="../solara.png" alt="Solara Logo">
|
592
631
|
<h2>Solara simplifies the management of your brand configurations, allowing you to access and update them anytime, anywhere.</h2>
|
593
632
|
<div class="button-message">You can select a JSON file containing brand configurations that were exported using Solara.</div>
|
594
|
-
<button id="uploadJsonBtn" style="
|
595
|
-
<div class="button-message" style="
|
596
|
-
<button id="uploadBrandBtn" style="
|
597
|
-
<div class="button-message" style="
|
598
|
-
<button id="newBrandBtn" style="
|
633
|
+
<button id="uploadJsonBtn" style=" animation-delay: 0.5s;">Upload JSON</button>
|
634
|
+
<div class="button-message" style=" animation-delay: 0.7s;">Alternatively, upload from a folder that includes the brand's JSON files.</div>
|
635
|
+
<button id="uploadBrandBtn" style=" animation-delay: 0.9s;">Upload Folder</button>
|
636
|
+
<div class="button-message" style=" animation-delay: 1.1s;">You also have the option to create new brand configurations.</div>
|
637
|
+
<button id="newBrandBtn" style=" animation-delay: 1.3s;">New Brand</button>
|
599
638
|
</div>
|
600
639
|
|
601
640
|
</div>
|
@@ -43,13 +43,13 @@ class BrandLocalSource {
|
|
43
43
|
}
|
44
44
|
}
|
45
45
|
|
46
|
-
async saveSection(
|
46
|
+
async saveSection(filename, configuration, brandKey) {
|
47
47
|
if (this.savingInProgress) return;
|
48
48
|
this.savingInProgress = true;
|
49
49
|
|
50
50
|
const dataToSend = {
|
51
51
|
brand_key: brandKey,
|
52
|
-
|
52
|
+
filename: filename,
|
53
53
|
data: configuration
|
54
54
|
};
|
55
55
|
|
@@ -5,7 +5,7 @@ class BrandRemoteSource {
|
|
5
5
|
}
|
6
6
|
|
7
7
|
async createNewBrandConfigurations() {
|
8
|
-
const url = 'https://raw.githubusercontent.com/Solara-Kit/Solara/refs/heads/
|
8
|
+
const url = 'https://raw.githubusercontent.com/Solara-Kit/Solara/refs/heads/main/solara/lib/core/template/configurations.json';
|
9
9
|
|
10
10
|
try {
|
11
11
|
const response = await fetch(url);
|
@@ -21,8 +21,8 @@ class BrandRemoteSource {
|
|
21
21
|
}
|
22
22
|
const content = await contentResponse.json();
|
23
23
|
return {
|
24
|
-
|
25
|
-
name: config.
|
24
|
+
filename: config.filename,
|
25
|
+
name: this.snakeToCapitalizedSpaced(config.filename, 'ios'),
|
26
26
|
content: content
|
27
27
|
};
|
28
28
|
});
|
@@ -36,6 +36,22 @@ class BrandRemoteSource {
|
|
36
36
|
}
|
37
37
|
}
|
38
38
|
|
39
|
+
// TODO: should be ecnapsulated
|
40
|
+
snakeToCapitalizedSpaced(
|
41
|
+
snakeCaseString,
|
42
|
+
exclude = '',
|
43
|
+
transform = (item) => item.charAt(0).toUpperCase() + item.slice(1)
|
44
|
+
) {
|
45
|
+
// Split by underscores
|
46
|
+
const parts = snakeCaseString.split('_').map(item => {
|
47
|
+
// Return the item as-is if it matches the exclude value
|
48
|
+
return item === exclude ? item : transform(item);
|
49
|
+
});
|
50
|
+
|
51
|
+
// Join the parts with a space
|
52
|
+
return parts.join(' ');
|
53
|
+
}
|
54
|
+
|
39
55
|
async getBrandConfigurationsJsonFromDirectory(dirHandle) {
|
40
56
|
const schema = await this.fetchBrandConfigurationsSchema();
|
41
57
|
|
@@ -72,6 +72,10 @@ class OnboardBrandBottomSheet extends HTMLElement {
|
|
72
72
|
border-radius: 3.5px;
|
73
73
|
font-size: 11.2px;
|
74
74
|
background-color: var(--background-color);
|
75
|
+
<<<<<<< HEAD
|
76
|
+
=======
|
77
|
+
color: var(--text-color);
|
78
|
+
>>>>>>> develop
|
75
79
|
}
|
76
80
|
.tooltip {
|
77
81
|
position: relative;
|
@@ -7,14 +7,14 @@ class EditSectionHandler < BaseHandler
|
|
7
7
|
begin
|
8
8
|
request_payload = JSON.parse(req.body)
|
9
9
|
brand_key = request_payload['brand_key']
|
10
|
-
|
10
|
+
filename = request_payload['filename']
|
11
11
|
data = request_payload['data']
|
12
12
|
|
13
|
-
update_section(
|
13
|
+
update_section(filename, data, brand_key)
|
14
14
|
set_current_brand_content_changed(brand_key, true)
|
15
15
|
|
16
16
|
res.status = 200
|
17
|
-
res.body = JSON.generate({ success: true, message: "Configuration for #{
|
17
|
+
res.body = JSON.generate({ success: true, message: "Configuration for #{filename} updated successfully" })
|
18
18
|
res.content_type = 'application/json'
|
19
19
|
rescue JSON::ParserError => e
|
20
20
|
handle_error(res, e, "Invalid JSON in request body", 400)
|
@@ -27,20 +27,8 @@ class EditSectionHandler < BaseHandler
|
|
27
27
|
end
|
28
28
|
end
|
29
29
|
|
30
|
-
def update_section(
|
31
|
-
|
32
|
-
|
33
|
-
path = template[:path]
|
34
|
-
|
35
|
-
if File.exist?(path)
|
36
|
-
File.write(path, JSON.pretty_generate(data))
|
37
|
-
Solara.logger.debug("Updated Config for #{path}: #{data}")
|
38
|
-
else
|
39
|
-
raise "Config file not found: #{path}"
|
40
|
-
end
|
41
|
-
rescue StandardError => e
|
42
|
-
Solara.logger.failure("Error updating section: #{e.message}")
|
43
|
-
raise
|
30
|
+
def update_section(filename, data, brand_key)
|
31
|
+
BrandConfigUpdater.new.update(filename, data, brand_key)
|
44
32
|
end
|
45
33
|
|
46
34
|
def set_current_brand_content_changed(brand_key, changed)
|
@@ -26,18 +26,6 @@ class OnboardBrandHandler < BaseHandler
|
|
26
26
|
end
|
27
27
|
res.content_type = 'application/json'
|
28
28
|
end
|
29
|
-
|
30
|
-
def onboard_brand(brand_name, brand_key, clone_brand_key = nil)
|
31
|
-
if BrandsManager.instance.exists(brand_key)
|
32
|
-
return { success: false, message: "Brand with key (#{brand_key}) already added!" }
|
33
|
-
end
|
34
|
-
SolaraManager.new.onboard(brand_key, brand_name, clone_brand_key: clone_brand_key, open_dashboard: false)
|
35
|
-
{ success: true, message: "Brand added successfully" }
|
36
|
-
rescue StandardError => e
|
37
|
-
Solara.logger.failure("Error adding brand: #{e.message}")
|
38
|
-
raise
|
39
|
-
end
|
40
|
-
|
41
29
|
end
|
42
30
|
|
43
31
|
def onboard_brand(brand_name, brand_key, clone_brand_key = nil)
|
@@ -46,8 +34,5 @@ class OnboardBrandHandler < BaseHandler
|
|
46
34
|
end
|
47
35
|
SolaraManager.new.onboard(brand_key, brand_name, clone_brand_key: clone_brand_key, open_dashboard: false)
|
48
36
|
{ success: true, message: "Brand added successfully" }
|
49
|
-
rescue StandardError => e
|
50
|
-
Solara.logger.failure("Error adding brand: #{e.message}")
|
51
|
-
raise
|
52
37
|
end
|
53
38
|
end
|