mirror of
https://github.com/The-Low-Code-Foundation/OpenNoodl.git
synced 2026-01-11 23:02:56 +01:00
Refactored dev-docs folder after multiple additions to organise correctly
This commit is contained in:
@@ -9,6 +9,7 @@ import { ipcRenderer, shell } from 'electron';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { filesystem } from '@noodl/platform';
|
||||
|
||||
import { CreateProjectModal } from '@noodl-core-ui/preview/launcher/Launcher/components/CreateProjectModal';
|
||||
import {
|
||||
CloudSyncType,
|
||||
LauncherProjectData
|
||||
@@ -19,6 +20,7 @@ import { GitHubUser } from '@noodl-core-ui/preview/launcher/Launcher/LauncherCon
|
||||
import { useEventListener } from '../../hooks/useEventListener';
|
||||
import { IRouteProps } from '../../pages/AppRoute';
|
||||
import { GitHubOAuthService } from '../../services/GitHubOAuthService';
|
||||
import { ProjectOrganizationService } from '../../services/ProjectOrganizationService';
|
||||
import { LocalProjectsModel, ProjectItem } from '../../utils/LocalProjectsModel';
|
||||
import { ToastLayer } from '../../views/ToastLayer/ToastLayer';
|
||||
|
||||
@@ -52,6 +54,9 @@ export function ProjectsPage(props: ProjectsPageProps) {
|
||||
const [githubIsAuthenticated, setGithubIsAuthenticated] = useState<boolean>(false);
|
||||
const [githubIsConnecting, setGithubIsConnecting] = useState<boolean>(false);
|
||||
|
||||
// Create project modal state
|
||||
const [isCreateModalVisible, setIsCreateModalVisible] = useState(false);
|
||||
|
||||
// Initialize and fetch projects on mount
|
||||
useEffect(() => {
|
||||
// Switch main window size to editor size
|
||||
@@ -147,40 +152,55 @@ export function ProjectsPage(props: ProjectsPageProps) {
|
||||
ToastLayer.showSuccess('Disconnected from GitHub');
|
||||
});
|
||||
|
||||
const handleCreateProject = useCallback(async () => {
|
||||
const handleCreateProject = useCallback(() => {
|
||||
setIsCreateModalVisible(true);
|
||||
}, []);
|
||||
|
||||
const handleChooseLocation = useCallback(async (): Promise<string | null> => {
|
||||
try {
|
||||
const direntry = await filesystem.openDialog({
|
||||
allowCreateDirectory: true
|
||||
});
|
||||
if (!direntry) return;
|
||||
|
||||
// For now, use a simple prompt for project name
|
||||
// TODO: Replace with a proper React dialog in future
|
||||
const name = prompt('Project name:');
|
||||
if (!name) return;
|
||||
|
||||
const path = filesystem.makeUniquePath(filesystem.join(direntry, name));
|
||||
|
||||
const activityId = 'creating-project';
|
||||
ToastLayer.showActivity('Creating new project', activityId);
|
||||
|
||||
LocalProjectsModel.instance.newProject(
|
||||
(project) => {
|
||||
ToastLayer.hideActivity(activityId);
|
||||
if (!project) {
|
||||
ToastLayer.showError('Could not create project');
|
||||
return;
|
||||
}
|
||||
// Navigate to editor with the newly created project
|
||||
props.route.router.route({ to: 'editor', project });
|
||||
},
|
||||
{ name, path, projectTemplate: '' }
|
||||
);
|
||||
return direntry || null;
|
||||
} catch (error) {
|
||||
console.error('Failed to create project:', error);
|
||||
ToastLayer.showError('Failed to create project');
|
||||
console.error('Failed to choose location:', error);
|
||||
return null;
|
||||
}
|
||||
}, [props.route]);
|
||||
}, []);
|
||||
|
||||
const handleCreateProjectConfirm = useCallback(
|
||||
async (name: string, location: string) => {
|
||||
setIsCreateModalVisible(false);
|
||||
|
||||
try {
|
||||
const path = filesystem.makeUniquePath(filesystem.join(location, name));
|
||||
|
||||
const activityId = 'creating-project';
|
||||
ToastLayer.showActivity('Creating new project', activityId);
|
||||
|
||||
LocalProjectsModel.instance.newProject(
|
||||
(project) => {
|
||||
ToastLayer.hideActivity(activityId);
|
||||
if (!project) {
|
||||
ToastLayer.showError('Could not create project');
|
||||
return;
|
||||
}
|
||||
// Navigate to editor with the newly created project
|
||||
props.route.router.route({ to: 'editor', project });
|
||||
},
|
||||
{ name, path, projectTemplate: '' }
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Failed to create project:', error);
|
||||
ToastLayer.showError('Failed to create project');
|
||||
}
|
||||
},
|
||||
[props.route]
|
||||
);
|
||||
|
||||
const handleCreateModalClose = useCallback(() => {
|
||||
setIsCreateModalVisible(false);
|
||||
}, []);
|
||||
|
||||
const handleOpenProject = useCallback(async () => {
|
||||
console.log('🔵 [handleOpenProject] Starting...');
|
||||
@@ -328,18 +348,28 @@ export function ProjectsPage(props: ProjectsPageProps) {
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Launcher
|
||||
projects={realProjects}
|
||||
onCreateProject={handleCreateProject}
|
||||
onOpenProject={handleOpenProject}
|
||||
onLaunchProject={handleLaunchProject}
|
||||
onOpenProjectFolder={handleOpenProjectFolder}
|
||||
onDeleteProject={handleDeleteProject}
|
||||
githubUser={githubUser}
|
||||
githubIsAuthenticated={githubIsAuthenticated}
|
||||
githubIsConnecting={githubIsConnecting}
|
||||
onGitHubConnect={handleGitHubConnect}
|
||||
onGitHubDisconnect={handleGitHubDisconnect}
|
||||
/>
|
||||
<>
|
||||
<Launcher
|
||||
projects={realProjects}
|
||||
onCreateProject={handleCreateProject}
|
||||
onOpenProject={handleOpenProject}
|
||||
onLaunchProject={handleLaunchProject}
|
||||
onOpenProjectFolder={handleOpenProjectFolder}
|
||||
onDeleteProject={handleDeleteProject}
|
||||
projectOrganizationService={ProjectOrganizationService.instance}
|
||||
githubUser={githubUser}
|
||||
githubIsAuthenticated={githubIsAuthenticated}
|
||||
githubIsConnecting={githubIsConnecting}
|
||||
onGitHubConnect={handleGitHubConnect}
|
||||
onGitHubDisconnect={handleGitHubDisconnect}
|
||||
/>
|
||||
|
||||
<CreateProjectModal
|
||||
isVisible={isCreateModalVisible}
|
||||
onClose={handleCreateModalClose}
|
||||
onConfirm={handleCreateProjectConfirm}
|
||||
onChooseLocation={handleChooseLocation}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
* Data is stored client-side in electron-store and keyed by project path.
|
||||
*/
|
||||
|
||||
import Store from 'electron-store';
|
||||
|
||||
import { EventDispatcher } from '../../../shared/utils/EventDispatcher';
|
||||
|
||||
// ============================================================================
|
||||
@@ -59,11 +61,23 @@ export const TAG_COLORS = [
|
||||
|
||||
export class ProjectOrganizationService extends EventDispatcher {
|
||||
private static _instance: ProjectOrganizationService;
|
||||
private store: Store<ProjectOrganizationData>;
|
||||
private data: ProjectOrganizationData;
|
||||
private storageKey = 'projectOrganization';
|
||||
|
||||
private constructor() {
|
||||
super();
|
||||
|
||||
// Initialize electron-store
|
||||
this.store = new Store<ProjectOrganizationData>({
|
||||
name: 'project_organization',
|
||||
defaults: {
|
||||
version: 1,
|
||||
folders: [],
|
||||
tags: [],
|
||||
projectMeta: {}
|
||||
}
|
||||
});
|
||||
|
||||
this.data = this.loadData();
|
||||
}
|
||||
|
||||
@@ -80,26 +94,21 @@ export class ProjectOrganizationService extends EventDispatcher {
|
||||
|
||||
private loadData(): ProjectOrganizationData {
|
||||
try {
|
||||
const stored = localStorage.getItem(this.storageKey);
|
||||
if (stored) {
|
||||
return JSON.parse(stored);
|
||||
}
|
||||
return this.store.store; // Get all data from store
|
||||
} catch (error) {
|
||||
console.error('[ProjectOrganizationService] Failed to load data:', error);
|
||||
return {
|
||||
version: 1,
|
||||
folders: [],
|
||||
tags: [],
|
||||
projectMeta: {}
|
||||
};
|
||||
}
|
||||
|
||||
// Return default empty structure
|
||||
return {
|
||||
version: 1,
|
||||
folders: [],
|
||||
tags: [],
|
||||
projectMeta: {}
|
||||
};
|
||||
}
|
||||
|
||||
private saveData(): void {
|
||||
try {
|
||||
localStorage.setItem(this.storageKey, JSON.stringify(this.data));
|
||||
this.store.store = this.data; // Save all data to store
|
||||
this.notifyListeners('dataChanged', this.data);
|
||||
} catch (error) {
|
||||
console.error('[ProjectOrganizationService] Failed to save data:', error);
|
||||
|
||||
@@ -2,6 +2,7 @@ import path from 'node:path';
|
||||
import { GitStore } from '@noodl-store/GitStore';
|
||||
import Store from 'electron-store';
|
||||
import { isEqual } from 'underscore';
|
||||
import { getTopLevelWorkingDirectory } from '@noodl/git/src/core/open';
|
||||
import { setRequestGitAccount } from '@noodl/git/src/core/trampoline/trampoline-askpass-handler';
|
||||
import { filesystem, platform } from '@noodl/platform';
|
||||
|
||||
@@ -9,13 +10,12 @@ import { ProjectModel } from '@noodl-models/projectmodel';
|
||||
import { templateRegistry } from '@noodl-utils/forge';
|
||||
|
||||
import Model from '../../../shared/model';
|
||||
import { projectFromDirectory, unzipIntoDirectory } from '../models/projectmodel.editor';
|
||||
import { RuntimeVersionInfo } from '../models/migration/types';
|
||||
import { detectRuntimeVersion } from '../models/migration/ProjectScanner';
|
||||
import { RuntimeVersionInfo } from '../models/migration/types';
|
||||
import { projectFromDirectory, unzipIntoDirectory } from '../models/projectmodel.editor';
|
||||
import FileSystem from './filesystem';
|
||||
import { tracker } from './tracker';
|
||||
import { guid } from './utils';
|
||||
import { getTopLevelWorkingDirectory } from '@noodl/git/src/core/open';
|
||||
|
||||
export interface ProjectItem {
|
||||
id: string;
|
||||
@@ -243,10 +243,6 @@ export class LocalProjectsModel extends Model {
|
||||
projectFromDirectory(dirEntry, (project) => {
|
||||
if (!project) {
|
||||
fn();
|
||||
// callback({
|
||||
// result: 'failure',
|
||||
// message: 'Failed to load project'
|
||||
// });
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -258,21 +254,42 @@ export class LocalProjectsModel extends Model {
|
||||
project.toDirectory(project._retainedProjectDirectory, (res) => {
|
||||
if (res.result === 'success') {
|
||||
fn(project);
|
||||
// callback({
|
||||
// result: 'success',
|
||||
// project: project
|
||||
// });
|
||||
} else {
|
||||
fn();
|
||||
// callback({
|
||||
// result: 'failure',
|
||||
// message: 'Failed to clone project'
|
||||
// });
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
this._unzipAndLaunchProject('./external/projecttemplates/helloworld.zip', dirEntry, fn, options);
|
||||
// Default template path
|
||||
const defaultTemplatePath = './external/projecttemplates/helloworld.zip';
|
||||
|
||||
// Check if template exists, otherwise create an empty project
|
||||
if (filesystem.exists(defaultTemplatePath)) {
|
||||
this._unzipAndLaunchProject(defaultTemplatePath, dirEntry, fn, options);
|
||||
} else {
|
||||
console.warn('Default project template not found, creating empty project');
|
||||
|
||||
// Create minimal project.json for empty project
|
||||
const minimalProject = {
|
||||
name: name,
|
||||
components: [],
|
||||
settings: {}
|
||||
};
|
||||
|
||||
await filesystem.writeFile(filesystem.join(dirEntry, 'project.json'), JSON.stringify(minimalProject, null, 2));
|
||||
|
||||
// Load the newly created empty project
|
||||
projectFromDirectory(dirEntry, (project) => {
|
||||
if (!project) {
|
||||
fn();
|
||||
return;
|
||||
}
|
||||
|
||||
project.name = name;
|
||||
this._addProject(project);
|
||||
fn(project);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -300,8 +317,8 @@ export class LocalProjectsModel extends Model {
|
||||
/**
|
||||
* Check if this project is in a git repository.
|
||||
*
|
||||
* @param project
|
||||
* @returns
|
||||
* @param project
|
||||
* @returns
|
||||
*/
|
||||
async isGitProject(project: ProjectModel): Promise<boolean> {
|
||||
const gitPath = await getTopLevelWorkingDirectory(project._retainedProjectDirectory);
|
||||
@@ -452,7 +469,7 @@ export class LocalProjectsModel extends Model {
|
||||
*/
|
||||
async detectAllProjectRuntimes(): Promise<void> {
|
||||
const projects = this.getProjects();
|
||||
|
||||
|
||||
// Detect in parallel but don't wait for all to complete
|
||||
// Instead, trigger detection and let events update the UI
|
||||
for (const project of projects) {
|
||||
|
||||
Reference in New Issue
Block a user