Skip to content

Commit

Permalink
- Hybrid JSON processing with optional manifest configuration. Implem…
Browse files Browse the repository at this point in the history
…ent flexible JSON file handling that:

  - Automatically processes all JSON files in directory
  - Allows customization via manifest configuration (class names, types)
  - Removes need to manually add new files to manifest
  - Provides override capability when specific configuration needed
- Add JSON manifest for generating platform-specific code from JSON files
    - This commit introduces a JSON manifest that enables the generation of platform-specific code based on the provided JSON files. This
      enhancement streamlines the development process and ensures better code organization for different platforms.
- Disable mandatory resource checks in ResourceManifestProcessor during brand onboarding
- Implement rollback mechanism if an error occurs during the onboarding process.
- Optimize .gitignore: Use root-relative paths to avoid unintended ignores
- Display checkbox for boolean value
- Only switch the brand on changing the values in dashboard if it's the current brand.
- Dashboard UI improvements
  • Loading branch information
MalekKamel committed Oct 26, 2024
1 parent d118c6e commit eacf50a
Show file tree
Hide file tree
Showing 17 changed files with 374 additions and 222 deletions.
40 changes: 22 additions & 18 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,34 +1,38 @@
# Changelog

## Version 0.5.0

Enhance Dashboard: Implement Comprehensive Support for JSON Objects
- Added functionality to fully process and display JSON objects within the dashboard.
- Updated data handling methods to accommodate nested structures and arrays.
- Improved UI components to render JSON data dynamically.

Add JSON manifest for generating platform-specific code from JSON files
- JSON manifest that enables the generation of platform-specific code based on the provided JSON files. This enhancement streamlines the development process and ensures better code organization for different platforms.

Enhance brand switching process by enabling resource copying
- Implemented functionality to copy resources associated with a brand during the switching process.
- Utilized a resources manifest to streamline the resource management.
- This improvement ensures a smoother transition between brands, maintaining consistency and reducing manual resource handling.

## Version 0.4.0
## Version 0.6.0

- Hybrid JSON processing with optional manifest configuration. Implement flexible JSON file handling that:
- Automatically processes all JSON files in directory
- Allows customization via manifest configuration (class names, types)
- Removes need to manually add new files to manifest
- Provides override capability when specific configuration needed
- Add JSON manifest for generating platform-specific code from JSON files
- This commit introduces a JSON manifest that enables the generation of platform-specific code based on the provided JSON files. This
enhancement streamlines the development process and ensures better code organization for different platforms.
- Disable mandatory resource checks in ResourceManifestProcessor during brand onboarding
- Implement rollback mechanism if an error occurs during the onboarding process.
- Optimize .gitignore: Use root-relative paths to avoid unintended ignores
- Display checkbox for boolean value
- Only switch the brand on changing the values in dashboard if it's the current brand.
- Dashboard UI improvements

## Version 0.4.0 & 0.5.0

Enhance Dashboard: Implement Comprehensive Support for JSON Objects

- Added functionality to fully process and display JSON objects within the dashboard.
- Updated data handling methods to accommodate nested structures and arrays.
- Improved UI components to render JSON data dynamically.
- Bug fixes and improvements

## Version 0.3.0
## Version 0.1.0 & 0.2.0 & 0.3.0

### Enhancements

- **Brand Configuration Code Generation**
- Added support for **JSON format** and **serialization** to streamline configuration management.
- Introduced a static JSON string, `asJson`, in each class for **dynamic parsing** of configuration properties, enhancing accessibility and ease of use.
- Introduced a static JSON string, `asJson`, in each class for **dynamic parsing** of configuration properties,
enhancing accessibility and ease of use.

These updates significantly improve the flexibility and usability of the brand configuration system.
2 changes: 1 addition & 1 deletion solara/lib/core/brands/brand_onboarder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ def initialize(brand_key, brand_name, clone_brand_key: nil)
end

def onboard
if @clone_brand_key.nil? || @clone_brand_key.empty?
if @clone_brand_key.nil? || @clone_brand_key.strip.empty?
generate_brand_template
else
clone_brand
Expand Down
82 changes: 58 additions & 24 deletions solara/lib/core/brands/brand_switcher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def start
def switch
BrandFontSwitcher.new(@brand_key).switch

ResourceManifestSwitcher.new(@brand_key).switch
ResourceManifestSwitcher.new(@brand_key, ignore_health_check: @ignore_health_check).switch
JsonManifestSwitcher.new(@brand_key).switch

case @platform
Expand All @@ -51,13 +51,14 @@ def switch
end

class ResourceManifestSwitcher
def initialize(brand_key)
def initialize(brand_key, ignore_health_check:)
@brand_key = brand_key
@ignore_health_check = ignore_health_check
end

def switch
Solara.logger.start_step("Process resource manifest: #{FilePath.resources_manifest}")
brand_resource_copier = ResourceManifestProcessor.new(@brand_key)
brand_resource_copier = ResourceManifestProcessor.new(@brand_key, ignore_health_check: @ignore_health_check)
brand_resource_copier.copy
Solara.logger.debug("#{@brand_key} resources copied successfully according to the manifest: #{FilePath.resources_manifest}.")
Solara.logger.end_step("Process resource manifest: #{FilePath.resources_manifest}")
Expand All @@ -66,39 +67,72 @@ def switch
end

class JsonManifestSwitcher
PLATFORM_CONFIGS = {
Platform::Flutter => {
language: Language::Dart,
output_path: :flutter_lib_artifacts
},
Platform::IOS => {
language: Language::Swift,
output_path: :ios_project_root_artifacts
},
Platform::Android => {
language: Language::Kotlin,
output_path: :android_project_java_artifacts
}
}.freeze

def initialize(brand_key)
@brand_key = brand_key
@manifest_path = FilePath.brand_json_dir(brand_key)
@brand_manifest_path = FilePath.brand_json_dir(brand_key)
@platform = SolaraSettingsManager.instance.platform
validate_inputs
end

def switch
Solara.logger.start_step("Process JSON manifest: #{@manifest_path}")
with_logging do
process_manifests
end
end

private

case SolaraSettingsManager.instance.platform
when Platform::Flutter
process_maifest(Language::Dart, FilePath.flutter_lib_artifacts)
process_maifest(Language::Dart, FilePath.brand_global_json_dir)
when Platform::IOS
process_maifest(Language::Swift, FilePath.ios_project_root_artifacts)
process_maifest(Language::Swift, FilePath.brand_global_json_dir)
when Platform::Android
process_maifest(Language::Kotlin, FilePath.android_project_java_artifacts )
process_maifest(Language::Kotlin, FilePath.brand_global_json_dir)
else
raise ArgumentError, "Invalid platform: #{@platform}"
end
def validate_inputs
raise ArgumentError, "Brand key cannot be nil" if @brand_key.nil?
raise ArgumentError, "Invalid platform: #{@platform}" unless PLATFORM_CONFIGS.key?(@platform)
raise ArgumentError, "Manifest path not found: #{@brand_manifest_path}" unless File.exist?(@brand_manifest_path)
end

Solara.logger.end_step("Process JSON manifest: #{@manifest_path}")
def with_logging
log_message = "Process JSON manifest: #{@brand_manifest_path}"
Solara.logger.start_step(log_message)
yield
Solara.logger.end_step(log_message)
rescue StandardError => e
Solara.logger.error("Failed to process manifest: #{e.message}")
raise
end

def process_manifests
config = PLATFORM_CONFIGS[@platform]
output_path = FilePath.send(config[:output_path])

[
@brand_manifest_path,
FilePath.brand_global_json_dir
].each do |manifest_path|
process_manifest(config[:language], manifest_path, output_path)
end
end

def process_maifest(language, output_path)
processor = JsonManifestProcessor.new(
@manifest_path,
def process_manifest(language, manifest_path, output_path)
JsonManifestProcessor.new(
manifest_path,
language,
output_path
)
processor.process
).process
rescue StandardError => e
raise StandardError, "Failed to process #{manifest_path}: #{e.message}"
end
end

Expand Down
7 changes: 5 additions & 2 deletions solara/lib/core/dashboard/brand/BrandDetailController.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ class BrandDetailController {
const response = await this.model.fetchBrandDetails();
await this.onLoadSections(response.result);
const {isCurrentBrand, contentChanged} = await this.model.fetchCurrentBrand();
this.model.isCurrentBrand = isCurrentBrand

if (isCurrentBrand) {
this.view.setupSyncBrandButton(contentChanged ? '#ff4136' : '#4A90E2');
Expand All @@ -109,7 +110,7 @@ class BrandDetailController {
try {
this.view.addBrandOverlay.style.display = 'none'
this.view.header.style.display = 'flex';
this.view.updateAppNameTitle(`${configuraationsResult.brand.key} (${configuraationsResult.brand.name})`);
this.view.updateAppNameTitle(`${configuraationsResult.brand.name} - ${configuraationsResult.brand.key}`);
await this.showSections(configuraationsResult);
this.view.showIndex();
} catch (error) {
Expand Down Expand Up @@ -159,7 +160,9 @@ class BrandDetailController {
try {
await this.model.saveSection(container.dataset.key, section.content);
await this.checkBrandHealth();
await this.switchToBrand(true)
if (this.model.isCurrentBrand) {
await this.switchToBrand(true)
}
} catch (error) {
console.error('Error saving section:', error);
alert(error.message);
Expand Down
1 change: 1 addition & 0 deletions solara/lib/core/dashboard/brand/BrandDetailModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ class BrandDetailModel {
constructor() {
this.localSource = new BrandLocalSource();
this.remoteSource = new BrandRemoteSource();
this.isCurrentBrand = false;

this.brandKey = this.getQueryFromUrl('brand_key');

Expand Down
85 changes: 73 additions & 12 deletions solara/lib/core/dashboard/brand/SectionsFormManager.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import '../component/EditJsonSheet.js';

class SectionsFormManager {

constructor() {
this.sections = [];
this.sectionsContainer = document.getElementById('sections');
Expand Down Expand Up @@ -49,7 +48,6 @@ class SectionsFormManager {
})
return Array.from(data);
}

}

class SectionItemManager {
Expand Down Expand Up @@ -113,7 +111,7 @@ class SectionItemManager {

const isArray = Array.isArray(obj)

for (const [k, v] of Object.entries(obj)) {
for (const [k, v] of Object.entries(obj)) {
const item = document.createElement('div');

const cardValueContainer = document.createElement('div');
Expand All @@ -135,18 +133,62 @@ class SectionItemManager {
item.className = 'card-item';
itemKey.textContent = k.replace(/_/g, ' ')

if (this.isColorValue(v)) {
if (typeof v === 'boolean') {
// Create a container for the entire boolean input
const booleanContainer = document.createElement('div');
booleanContainer.className = 'boolean-container';

const checkboxContainer = document.createElement('div');
checkboxContainer.className = 'card-value checkbox-container';

const itemValue = document.createElement('input');
itemValue.type = 'checkbox';
itemValue.className = 'card-value checkbox';
itemValue.checked = v;

const valueLabel = document.createElement('span');
valueLabel.className = 'checkbox-value';
valueLabel.textContent = v.toString();

const updateValue = () => {
const newValue = !itemValue.checked;
itemValue.checked = newValue;
valueLabel.textContent = newValue.toString();
this.updateValue(obj, k, newValue, typeof v);
};

// Add click handlers to both container and checkbox
booleanContainer.onclick = (e) => {
if (e.target !== itemValue) { // Prevent double-toggle when clicking checkbox
updateValue();
}
};

itemValue.onchange = () => {
valueLabel.textContent = itemValue.checked.toString();
this.updateValue(obj, k, itemValue.checked, typeof v);
};

checkboxContainer.appendChild(itemValue);
checkboxContainer.appendChild(valueLabel);

// Move the key inside the boolean container
booleanContainer.appendChild(itemKey);
booleanContainer.appendChild(checkboxContainer);

cardValueContainer.appendChild(booleanContainer);
} else if (this.isColorValue(v)) {
const itemValue = document.createElement('input');
itemValue.type = 'color';
itemValue.className = 'card-value';
itemValue.value = v;
itemValue.onchange = () => this.updateValue(obj, k, itemValue.value);
itemValue.onchange = () => this.updateValue(obj, k, itemValue.value, typeof v);
cardValueContainer.appendChild(itemValue);
} else {
const itemValue = document.createElement('textarea');
itemValue.className = 'card-value';
itemValue.value = v;
itemValue.onchange = () => this.updateValue(obj, k, itemValue.value);
itemValue.onchange = () => this.updateValue(obj, k, itemValue.value, typeof v);
cardValueContainer.appendChild(itemValue);
}

Expand All @@ -165,8 +207,7 @@ class SectionItemManager {
}

isColorValue(value) {
// Check if the value is a valid color (hex with opacity, RGBA, or RGB)
const hexPattern = /^#([0-9A-F]{3}){1,2}([0-9A-F]{2})?$/i; // 3, 6, or 8 hex digits
const hexPattern = /^#([0-9A-F]{3}){1,2}([0-9A-F]{2})?$/i;
const rgbaPattern = /^rgba?\(\s*(\d{1,3}\s*,\s*){2}\d{1,3}\s*,?\s*(0|1|0?\.\d+|1?\.\d+)\s*\)$/;

return hexPattern.test(value) || rgbaPattern.test(value);
Expand All @@ -182,14 +223,35 @@ class SectionItemManager {
this.notifyChange()
}

updateValue(obj, key, value) {
updateValue(obj, key, value, originalType) {
try {
obj[key] = JSON.parse(value);
// Handle different types based on the original value's type
switch (originalType) {
case 'string':
obj[key] = String(value);
break;
case 'number':
// If the original was a number, keep it number
if (!isNaN(value) || value === '') {
obj[key] = Number(value);
}
break;
case 'boolean':
obj[key] = value.toLowerCase() === 'true';
break;
default:
// Try to parse as JSON, fallback to string if it fails
try {
obj[key] = JSON.parse(value);
} catch {
obj[key] = value;
}
}
} catch {
obj[key] = value;
}
this.displayJSONCards();
this.notifyChange()
this.notifyChange();
}

confirmDeleteProperty(obj, key) {
Expand Down Expand Up @@ -228,5 +290,4 @@ class SectionItemManager {
}
}


export default SectionsFormManager;
Loading

0 comments on commit eacf50a

Please sign in to comment.