Initial commit: Clean DSS implementation
Migrated from design-system-swarm with fresh git history.
Old project history preserved in /home/overbits/apps/design-system-swarm
Core components:
- MCP Server (Python FastAPI with mcp 1.23.1)
- Claude Plugin (agents, commands, skills, strategies, hooks, core)
- DSS Backend (dss-mvp1 - token translation, Figma sync)
- Admin UI (Node.js/React)
- Server (Node.js/Express)
- Storybook integration (dss-mvp1/.storybook)
Self-contained configuration:
- All paths relative or use DSS_BASE_PATH=/home/overbits/dss
- PYTHONPATH configured for dss-mvp1 and dss-claude-plugin
- .env file with all configuration
- Claude plugin uses ${CLAUDE_PLUGIN_ROOT} for portability
Migration completed: $(date)
🤖 Clean migration with full functionality preserved
This commit is contained in:
250
admin-ui/js/stores/project-store.js
Normal file
250
admin-ui/js/stores/project-store.js
Normal file
@@ -0,0 +1,250 @@
|
||||
/**
|
||||
* project-store.js
|
||||
* Project configuration store
|
||||
* Manages project list, current project selection, and project metadata
|
||||
* MVP3: Integrates with API backend while maintaining localStorage fallback
|
||||
*/
|
||||
|
||||
import apiClient from '../services/api-client.js';
|
||||
|
||||
export class ProjectStore {
|
||||
constructor() {
|
||||
// Load projects from localStorage (fallback for MVP2)
|
||||
const stored = localStorage.getItem('projects_list');
|
||||
this.projects = stored ? JSON.parse(stored) : this.getDefaultProjects();
|
||||
|
||||
// Load current project selection
|
||||
this.currentProjectId = localStorage.getItem('current_project_id') || (this.projects.length > 0 ? this.projects[0].id : null);
|
||||
|
||||
this.listeners = new Set();
|
||||
this.isLoading = false;
|
||||
this.hasLoadedFromAPI = false;
|
||||
|
||||
// Try to load from API on initialization
|
||||
this.syncWithAPI();
|
||||
}
|
||||
|
||||
async syncWithAPI() {
|
||||
try {
|
||||
const projects = await apiClient.getProjects();
|
||||
this.projects = projects;
|
||||
this.hasLoadedFromAPI = true;
|
||||
this.persist();
|
||||
this.notifyListeners();
|
||||
} catch (error) {
|
||||
console.warn('[ProjectStore] Failed to sync with API, using localStorage:', error);
|
||||
// Continue using localStorage data
|
||||
}
|
||||
}
|
||||
|
||||
getDefaultProjects() {
|
||||
return [
|
||||
{
|
||||
id: 'DEFAULT-DESIGN-SYSTEM',
|
||||
name: 'Default Design System',
|
||||
skinSelected: 'default',
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString()
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all projects
|
||||
* @returns {Array} Projects array
|
||||
*/
|
||||
getProjects() {
|
||||
return [...this.projects];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current project
|
||||
* @returns {Object|null} Current project or null
|
||||
*/
|
||||
getCurrentProject() {
|
||||
return this.projects.find(p => p.id === this.currentProjectId) || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get project by ID
|
||||
* @param {string} id - Project ID
|
||||
* @returns {Object|null} Project or null
|
||||
*/
|
||||
getProject(id) {
|
||||
return this.projects.find(p => p.id === id) || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new project
|
||||
* @param {Object} projectData - {name, key, description, figmaFileKey, jiraProjectKey, storybookUrl}
|
||||
* @returns {Object} Created project
|
||||
*/
|
||||
async createProject(projectData) {
|
||||
try {
|
||||
// Try API first if authenticated
|
||||
if (apiClient.accessToken) {
|
||||
const project = await apiClient.createProject(projectData);
|
||||
this.projects.push(project);
|
||||
this.persist();
|
||||
this.notifyListeners();
|
||||
return project;
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('[ProjectStore] API creation failed, using localStorage:', error);
|
||||
}
|
||||
|
||||
// Fallback to localStorage
|
||||
const now = new Date().toISOString();
|
||||
const project = {
|
||||
id: projectData.id || `PROJECT-${Date.now()}`,
|
||||
name: projectData.name || 'Untitled Project',
|
||||
key: projectData.key || `KEY-${Date.now()}`,
|
||||
description: projectData.description || '',
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
...projectData
|
||||
};
|
||||
|
||||
this.projects.push(project);
|
||||
this.persist();
|
||||
this.notifyListeners();
|
||||
return project;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update project
|
||||
* @param {string} id - Project ID
|
||||
* @param {Object} updates - Partial project updates
|
||||
* @returns {Object|null} Updated project or null
|
||||
*/
|
||||
async updateProject(id, updates) {
|
||||
const project = this.projects.find(p => p.id === id);
|
||||
if (!project) return null;
|
||||
|
||||
try {
|
||||
// Try API first if authenticated
|
||||
if (apiClient.accessToken) {
|
||||
const updated = await apiClient.updateProject(id, updates);
|
||||
const index = this.projects.findIndex(p => p.id === id);
|
||||
if (index !== -1) {
|
||||
this.projects[index] = updated;
|
||||
}
|
||||
this.persist();
|
||||
this.notifyListeners();
|
||||
return updated;
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('[ProjectStore] API update failed, using localStorage:', error);
|
||||
}
|
||||
|
||||
// Fallback to localStorage
|
||||
Object.assign(project, updates, {
|
||||
updatedAt: new Date().toISOString()
|
||||
});
|
||||
|
||||
this.persist();
|
||||
this.notifyListeners();
|
||||
return project;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete project
|
||||
* @param {string} id - Project ID
|
||||
* @returns {boolean} Success
|
||||
*/
|
||||
async deleteProject(id) {
|
||||
try {
|
||||
// Try API first if authenticated
|
||||
if (apiClient.accessToken) {
|
||||
await apiClient.deleteProject(id);
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('[ProjectStore] API deletion failed, using localStorage:', error);
|
||||
}
|
||||
|
||||
const index = this.projects.findIndex(p => p.id === id);
|
||||
if (index === -1) return false;
|
||||
|
||||
this.projects.splice(index, 1);
|
||||
|
||||
// If deleted project was selected, select first available
|
||||
if (this.currentProjectId === id) {
|
||||
this.currentProjectId = this.projects.length > 0 ? this.projects[0].id : null;
|
||||
localStorage.setItem('current_project_id', this.currentProjectId || '');
|
||||
}
|
||||
|
||||
this.persist();
|
||||
this.notifyListeners();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Select/switch to different project
|
||||
* @param {string} id - Project ID
|
||||
* @returns {Object|null} Selected project or null
|
||||
*/
|
||||
selectProject(id) {
|
||||
const project = this.projects.find(p => p.id === id);
|
||||
if (!project) return null;
|
||||
|
||||
this.currentProjectId = id;
|
||||
localStorage.setItem('current_project_id', id);
|
||||
this.notifyListeners();
|
||||
return project;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update selected skin for current project
|
||||
* @param {string} skin - Skin name
|
||||
* @returns {Object|null} Updated project
|
||||
*/
|
||||
setSkinForCurrentProject(skin) {
|
||||
if (!this.currentProjectId) return null;
|
||||
return this.updateProject(this.currentProjectId, { skinSelected: skin });
|
||||
}
|
||||
|
||||
/**
|
||||
* Persist to localStorage
|
||||
*/
|
||||
persist() {
|
||||
localStorage.setItem('projects_list', JSON.stringify(this.projects));
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe to changes
|
||||
* @param {Function} callback - Called on state changes
|
||||
* @returns {Function} Unsubscribe function
|
||||
*/
|
||||
subscribe(callback) {
|
||||
this.listeners.add(callback);
|
||||
return () => this.listeners.delete(callback);
|
||||
}
|
||||
|
||||
notifyListeners() {
|
||||
this.listeners.forEach(listener => listener({
|
||||
projects: this.getProjects(),
|
||||
currentProject: this.getCurrentProject()
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset to default project
|
||||
*/
|
||||
reset() {
|
||||
localStorage.removeItem('projects_list');
|
||||
localStorage.removeItem('current_project_id');
|
||||
this.projects = this.getDefaultProjects();
|
||||
this.currentProjectId = this.projects[0].id;
|
||||
this.notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
// Singleton instance
|
||||
let projectStoreInstance = null;
|
||||
|
||||
export function useProjectStore() {
|
||||
if (!projectStoreInstance) {
|
||||
projectStoreInstance = new ProjectStore();
|
||||
}
|
||||
return projectStoreInstance;
|
||||
}
|
||||
Reference in New Issue
Block a user