mirror of
https://github.com/The-Low-Code-Foundation/OpenNoodl.git
synced 2026-01-13 07:42:55 +01:00
working on problem opening projet
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
/**
|
||||
* Default Style Tokens (Minimal Set for MVP)
|
||||
*
|
||||
* This file defines the minimal set of CSS custom properties (design tokens)
|
||||
* that will be available in every Noodl project.
|
||||
*
|
||||
* These tokens can be used in any CSS property that accepts the relevant value type.
|
||||
* Example: style="background: var(--primary); padding: var(--space-md);"
|
||||
*
|
||||
* @module StyleTokens
|
||||
*/
|
||||
|
||||
export interface StyleToken {
|
||||
name: string;
|
||||
value: string;
|
||||
category: TokenCategory;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export type TokenCategory = 'color' | 'spacing' | 'border' | 'shadow';
|
||||
|
||||
/**
|
||||
* Minimal set of design tokens for MVP
|
||||
* Following modern design system conventions (similar to Tailwind/shadcn)
|
||||
*/
|
||||
export const DEFAULT_TOKENS: Record<string, StyleToken> = {
|
||||
// ===== COLORS =====
|
||||
'--primary': {
|
||||
name: '--primary',
|
||||
value: '#3b82f6', // Blue
|
||||
category: 'color',
|
||||
description: 'Primary brand color for main actions and highlights'
|
||||
},
|
||||
|
||||
'--background': {
|
||||
name: '--background',
|
||||
value: '#ffffff',
|
||||
category: 'color',
|
||||
description: 'Main background color'
|
||||
},
|
||||
|
||||
'--foreground': {
|
||||
name: '--foreground',
|
||||
value: '#0f172a', // Near black
|
||||
category: 'color',
|
||||
description: 'Main text color'
|
||||
},
|
||||
|
||||
'--border': {
|
||||
name: '--border',
|
||||
value: '#e2e8f0', // Light gray
|
||||
category: 'color',
|
||||
description: 'Default border color'
|
||||
},
|
||||
|
||||
// ===== SPACING =====
|
||||
'--space-sm': {
|
||||
name: '--space-sm',
|
||||
value: '8px',
|
||||
category: 'spacing',
|
||||
description: 'Small spacing (padding, margin, gap)'
|
||||
},
|
||||
|
||||
'--space-md': {
|
||||
name: '--space-md',
|
||||
value: '16px',
|
||||
category: 'spacing',
|
||||
description: 'Medium spacing (padding, margin, gap)'
|
||||
},
|
||||
|
||||
'--space-lg': {
|
||||
name: '--space-lg',
|
||||
value: '24px',
|
||||
category: 'spacing',
|
||||
description: 'Large spacing (padding, margin, gap)'
|
||||
},
|
||||
|
||||
// ===== BORDERS =====
|
||||
'--radius-md': {
|
||||
name: '--radius-md',
|
||||
value: '8px',
|
||||
category: 'border',
|
||||
description: 'Medium border radius for rounded corners'
|
||||
},
|
||||
|
||||
// ===== SHADOWS =====
|
||||
'--shadow-sm': {
|
||||
name: '--shadow-sm',
|
||||
value: '0 1px 2px 0 rgb(0 0 0 / 0.05)',
|
||||
category: 'shadow',
|
||||
description: 'Small shadow for subtle elevation'
|
||||
},
|
||||
|
||||
'--shadow-md': {
|
||||
name: '--shadow-md',
|
||||
value: '0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)',
|
||||
category: 'shadow',
|
||||
description: 'Medium shadow for moderate elevation'
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get all default tokens as a simple key-value map
|
||||
* Useful for CSS injection
|
||||
*/
|
||||
export function getDefaultTokenValues(): Record<string, string> {
|
||||
const values: Record<string, string> = {};
|
||||
for (const [key, token] of Object.entries(DEFAULT_TOKENS)) {
|
||||
values[key] = token.value;
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get tokens by category
|
||||
*/
|
||||
export function getTokensByCategory(category: TokenCategory): StyleToken[] {
|
||||
return Object.values(DEFAULT_TOKENS).filter((token) => token.category === category);
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
/**
|
||||
* Style Tokens Model
|
||||
*
|
||||
* Manages CSS custom properties (design tokens) for a Noodl project.
|
||||
* Tokens are stored in project metadata and can be customized per project.
|
||||
*
|
||||
* @module StyleTokens
|
||||
*/
|
||||
|
||||
import Model from '../../../../shared/model';
|
||||
import { EventDispatcher } from '../../../../shared/utils/EventDispatcher';
|
||||
import { ProjectModel } from '../projectmodel';
|
||||
import { getDefaultTokenValues, DEFAULT_TOKENS, StyleToken, TokenCategory } from './DefaultTokens';
|
||||
|
||||
export class StyleTokensModel extends Model {
|
||||
/** Custom token values (overrides defaults) */
|
||||
private customTokens: Record<string, string>;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.customTokens = {};
|
||||
this.loadFromProject();
|
||||
this.bindListeners();
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind to project events to stay in sync
|
||||
*/
|
||||
private bindListeners() {
|
||||
const onProjectChanged = () => {
|
||||
this.loadFromProject();
|
||||
this.notifyListeners('tokensChanged');
|
||||
};
|
||||
|
||||
EventDispatcher.instance.on(
|
||||
['ProjectModel.importComplete', 'ProjectModel.instanceHasChanged'],
|
||||
() => {
|
||||
if (ProjectModel.instance) {
|
||||
onProjectChanged();
|
||||
}
|
||||
},
|
||||
this
|
||||
);
|
||||
|
||||
EventDispatcher.instance.on(
|
||||
'ProjectModel.metadataChanged',
|
||||
({ key }) => {
|
||||
if (key === 'styleTokens') {
|
||||
onProjectChanged();
|
||||
}
|
||||
},
|
||||
this
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unbind listeners
|
||||
*/
|
||||
private unbindListeners() {
|
||||
EventDispatcher.instance.off(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load tokens from current project
|
||||
*/
|
||||
private loadFromProject() {
|
||||
if (ProjectModel.instance) {
|
||||
this.customTokens = ProjectModel.instance.getMetaData('styleTokens') || {};
|
||||
} else {
|
||||
this.customTokens = {};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save tokens to current project
|
||||
*/
|
||||
private saveToProject() {
|
||||
if (ProjectModel.instance) {
|
||||
this.unbindListeners();
|
||||
ProjectModel.instance.setMetaData('styleTokens', this.customTokens);
|
||||
this.bindListeners();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all tokens (defaults + custom overrides)
|
||||
*/
|
||||
getAllTokens(): Record<string, string> {
|
||||
const defaults = getDefaultTokenValues();
|
||||
return {
|
||||
...defaults,
|
||||
...this.customTokens
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a specific token value
|
||||
* @param name Token name (e.g., '--primary')
|
||||
* @returns Token value or undefined if not found
|
||||
*/
|
||||
getToken(name: string): string | undefined {
|
||||
// Check custom tokens first
|
||||
if (this.customTokens[name] !== undefined) {
|
||||
return this.customTokens[name];
|
||||
}
|
||||
|
||||
// Fall back to default
|
||||
const defaultToken = DEFAULT_TOKENS[name];
|
||||
return defaultToken?.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a custom token value
|
||||
* @param name Token name (e.g., '--primary')
|
||||
* @param value Token value (e.g., '#ff0000')
|
||||
*/
|
||||
setToken(name: string, value: string) {
|
||||
// Validate token name starts with --
|
||||
if (!name.startsWith('--')) {
|
||||
console.warn(`Token name must start with -- : ${name}`);
|
||||
return;
|
||||
}
|
||||
|
||||
this.customTokens[name] = value;
|
||||
this.saveToProject();
|
||||
this.notifyListeners('tokensChanged');
|
||||
this.notifyListeners('tokenChanged', { name, value });
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset a token to its default value
|
||||
* @param name Token name (e.g., '--primary')
|
||||
*/
|
||||
resetToken(name: string) {
|
||||
if (this.customTokens[name] !== undefined) {
|
||||
delete this.customTokens[name];
|
||||
this.saveToProject();
|
||||
this.notifyListeners('tokensChanged');
|
||||
this.notifyListeners('tokenReset', { name });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset all tokens to defaults
|
||||
*/
|
||||
resetAllTokens() {
|
||||
this.customTokens = {};
|
||||
this.saveToProject();
|
||||
this.notifyListeners('tokensChanged');
|
||||
this.notifyListeners('allTokensReset');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a token has been customized
|
||||
*/
|
||||
isTokenCustomized(name: string): boolean {
|
||||
return this.customTokens[name] !== undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get token metadata
|
||||
*/
|
||||
getTokenInfo(name: string): StyleToken | undefined {
|
||||
return DEFAULT_TOKENS[name];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all tokens by category
|
||||
*/
|
||||
getTokensByCategory(category: TokenCategory): Record<string, string> {
|
||||
const tokens: Record<string, string> = {};
|
||||
|
||||
for (const [name, tokenInfo] of Object.entries(DEFAULT_TOKENS)) {
|
||||
if (tokenInfo.category === category) {
|
||||
tokens[name] = this.getToken(name) || tokenInfo.value;
|
||||
}
|
||||
}
|
||||
|
||||
return tokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate CSS string for injection
|
||||
* @returns CSS custom properties as a string
|
||||
*/
|
||||
generateCSS(): string {
|
||||
const allTokens = this.getAllTokens();
|
||||
const entries = Object.entries(allTokens);
|
||||
|
||||
if (entries.length === 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const declarations = entries.map(([name, value]) => ` ${name}: ${value};`).join('\n');
|
||||
|
||||
return `:root {\n${declarations}\n}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup
|
||||
*/
|
||||
dispose() {
|
||||
this.unbindListeners();
|
||||
this.removeAllListeners();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Singleton instance
|
||||
*/
|
||||
export const StyleTokens = new StyleTokensModel();
|
||||
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Style Tokens System
|
||||
*
|
||||
* Exports for the Style Tokens system
|
||||
*
|
||||
* @module StyleTokens
|
||||
*/
|
||||
|
||||
export { StyleTokensModel, StyleTokens } from './StyleTokensModel';
|
||||
export { DEFAULT_TOKENS, getDefaultTokenValues, getTokensByCategory } from './DefaultTokens';
|
||||
export type { StyleToken, TokenCategory } from './DefaultTokens';
|
||||
@@ -169,6 +169,14 @@ export class ProjectModel extends Model {
|
||||
|
||||
if (json.rootNodeId) _this.rootNode = _this.findNodeWithId(json.rootNodeId);
|
||||
|
||||
// Handle rootComponent from templates (name of component instead of node ID)
|
||||
if (json.rootComponent && !_this.rootNode) {
|
||||
const rootComponent = _this.getComponentWithName(json.rootComponent);
|
||||
if (rootComponent) {
|
||||
_this.setRootComponent(rootComponent);
|
||||
}
|
||||
}
|
||||
|
||||
// Upgrade project if necessary
|
||||
ProjectModel.upgrade(_this);
|
||||
|
||||
|
||||
@@ -40,6 +40,9 @@ export interface ProjectContent {
|
||||
/** Project name (will be overridden by user input) */
|
||||
name: string;
|
||||
|
||||
/** Name of the root component that serves as the entry point */
|
||||
rootComponent?: string;
|
||||
|
||||
/** Array of component definitions */
|
||||
components: ComponentDefinition[];
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ export const helloWorldTemplate: ProjectTemplate = {
|
||||
|
||||
content: {
|
||||
name: 'Hello World Project',
|
||||
rootComponent: 'App',
|
||||
components: [
|
||||
// App component (root)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user