solara 0.5.0 → 0.7.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 +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
|