From f1ee1ba20ab88353785f9016e1fabd4abb43fa87 Mon Sep 17 00:00:00 2001 From: Val Erastov Date: Sun, 24 Feb 2019 20:22:17 -0800 Subject: [PATCH] implement project manager and bind with actions and menus --- web/app/cad/actions/coreActions.js | 38 +++++++- web/app/cad/part/menuConfig.js | 5 +- web/app/cad/projectManager/ProjectManager.jsx | 24 +++-- .../cad/projectManager/ProjectManager.less | 6 -- .../projectManager/projectManagerPlugin.js | 93 +++++++++++++------ web/app/cad/sketch/inPlaceSketcher.js | 8 +- web/app/cad/sketch/sketcherPlugin.js | 2 +- web/app/cad/storagePlugin.js | 20 +++- 8 files changed, 136 insertions(+), 60 deletions(-) diff --git a/web/app/cad/actions/coreActions.js b/web/app/cad/actions/coreActions.js index f2d62163..a33eca62 100644 --- a/web/app/cad/actions/coreActions.js +++ b/web/app/cad/actions/coreActions.js @@ -61,7 +61,7 @@ export default [ id: 'NativeFormatExport', appearance: { cssIcons: ['book'], - label: 'Download Bundle', + label: 'Download Project', info: 'export model and its sketches as a json bundle', }, invoke: (context) => context.services.export.nativeFormat() @@ -70,13 +70,43 @@ export default [ { id: 'NativeFormatImport', appearance: { - cssIcons: ['book'], - label: 'Import Bundle', - info: 'import native format json(model and its sketches)', + cssIcons: ['download', 'flip-vertical'], + label: 'Import Project', + info: 'empty current project and import replacing with native format json(model and its sketches)', }, invoke: (context) => context.services.projectManager.importProject() }, + { + id: 'NativeFormatImportAs', + appearance: { + cssIcons: ['download', 'flip-vertical'], + label: 'Import Project as...', + info: 'import native format json(model and its sketches) as a new project', + }, + invoke: (context) => context.services.projectManager.importProjectAs() + }, + + { + id: 'NewProject', + appearance: { + cssIcons: ['file-o'], + label: 'New Project...', + info: 'create new project and open in a new tab', + }, + invoke: (context) => context.services.projectManager.newProject() + }, + + { + id: 'CloneCurrentProject', + appearance: { + cssIcons: ['copy'], + label: 'Clone Project...', + info: 'clone current project and open in a new tab', + }, + invoke: (context) => context.services.projectManager.cloneProject(context.services.project.id) + }, + { id: 'RefreshSketches', appearance: { diff --git a/web/app/cad/part/menuConfig.js b/web/app/cad/part/menuConfig.js index 10196fb2..683b58b1 100644 --- a/web/app/cad/part/menuConfig.js +++ b/web/app/cad/part/menuConfig.js @@ -2,7 +2,8 @@ export default [ { id: 'file', cssIcons: ['file'], - actions: ['Save', 'StlExport', 'ImagePngExport', 'NativeFormatExport', '-', 'NativeFormatImport', '-', 'ReassignSketch'] + actions: ['NewProject', '-', 'Save', 'StlExport', 'ImagePngExport', 'NativeFormatExport', '-', 'NativeFormatImport', + 'NativeFormatImportAs', '-', 'CloneCurrentProject', '-', 'ReassignSketch'] }, { id: 'craft', @@ -13,7 +14,7 @@ export default [ { id: 'primitives', label: 'add', - cssIcons: ['cube', 'plus'], + cssIcons: ['cube'], info: 'set of available solid creation operations', actions: ['PLANE', 'BOX', 'SPHERE', 'CONE', 'CYLINDER', 'TORUS'] }, diff --git a/web/app/cad/projectManager/ProjectManager.jsx b/web/app/cad/projectManager/ProjectManager.jsx index 63a705b0..dac196c6 100644 --- a/web/app/cad/projectManager/ProjectManager.jsx +++ b/web/app/cad/projectManager/ProjectManager.jsx @@ -5,28 +5,36 @@ import Fa from 'ui/components/Fa'; import ls from './ProjectManager.less'; import {ContextMenu, ContextMenuItem} from 'ui/components/Menu'; import cmn from 'ui/styles/common.less'; -import Button from '../../../../modules/ui/components/controls/Button'; -import Folder from '../../../../modules/ui/components/Folder'; +import Folder from 'ui/components/Folder'; +import connect from 'ui/connect'; @mapContext(ctx => ({ - projectManager: ctx.services.projectManager + projectManager: ctx.services.projectManager, + download: projectId => ctx.services.projectManager.exportProject(projectId), + clone: projectId => ctx.services.projectManager.cloneProject(projectId, true), + rename: projectId => ctx.services.projectManager.renameProject(projectId, true), + remove: projectId => ctx.services.projectManager.deleteProject(projectId), })) +@connect(streams => streams.storage.update) export class ProjectManager extends React.Component { render() { let {projectManager} = this.props; let projects = projectManager.listProjects(); return
- -
{projects.map(p =>
- } onClick={() => alert(1)}/> - } onClick={() => alert(1)}/> - } onClick={() => alert(1)}/> + } + onClick={() => this.props.download(p.id)}/> + } + onClick={() => this.props.clone(p.id)}/> + } + onClick={() => this.props.rename(p.id)}/> + } + onClick={() => this.props.remove(p.id)}/> }> {p.id} diff --git a/web/app/cad/projectManager/ProjectManager.less b/web/app/cad/projectManager/ProjectManager.less index ac85a47a..41d8bf5b 100644 --- a/web/app/cad/projectManager/ProjectManager.less +++ b/web/app/cad/projectManager/ProjectManager.less @@ -1,9 +1,3 @@ .root a { text-decoration: none; } - -.btn { - width: 90%; - margin: 2px; - display: block; -} \ No newline at end of file diff --git a/web/app/cad/projectManager/projectManagerPlugin.js b/web/app/cad/projectManager/projectManagerPlugin.js index 63f8c2da..93e1366d 100644 --- a/web/app/cad/projectManager/projectManagerPlugin.js +++ b/web/app/cad/projectManager/projectManagerPlugin.js @@ -3,9 +3,8 @@ import {ProjectManager} from './ProjectManager'; import exportTextData from '../../../../modules/gems/exportTextData'; export function activate(ctx) { - - function importProject() { - // let uploader = document.getElementById("uploader"); + + function importProjectImpl(getId, onDone) { let uploader = document.createElement('input'); uploader.setAttribute('type', 'file'); uploader.style.display = 'none'; @@ -17,21 +16,14 @@ export function activate(ctx) { reader.onload = () => { try { let bundle = JSON.parse(reader.result); - - let promptedId = uploader.value; - let iof = promptedId.search(/([^/\\]+)$/); - if (iof !== -1) { - promptedId = promptedId.substring(iof).replace(/\.json$/, ''); - } - - - let projectId = prompt("New Project Name", promptedId); - if (projectId && !checkExistence(projectId)) { + let projectId = getId(uploader.value, bundle); + + if (projectId) { let sketchesNamespace = PROJECTS_PREFIX + projectId + SKETCH_SUFFIX; ctx.services.storage.set(PROJECTS_PREFIX + projectId, JSON.stringify(bundle.model)); bundle.sketches.forEach(s => ctx.services.storage.set(sketchesNamespace + s.id, JSON.stringify(s.data))); - openProject(projectId); + onDone(projectId); } } finally { document.body.removeChild(uploader); @@ -42,6 +34,30 @@ export function activate(ctx) { uploader.addEventListener('change', read, false); } + function importProjectAs() { + function promptId(fileName, bundle) { + let promptedId = fileName; + let iof = promptedId.search(/([^/\\]+)$/); + if (iof !== -1) { + promptedId = promptedId.substring(iof).replace(/\.json$/, ''); + } + + let projectId = prompt("New Project Name", promptedId); + if (!projectId && !checkExistence(projectId)) { + return null + } + return projectId; + } + importProjectImpl(promptId, openProject); + } + + function importProject() { + if (confirm('Current project will be wiped off and replaced with the being imported one. Continue?')) { + ctx.services.project.empty(); + importProjectImpl(() => ctx.services.project.id, ctx.services.project.load); + } + } + function exportProject(id) { let modelData = ctx.services.storage.get(PROJECTS_PREFIX + id); if (modelData) { @@ -63,13 +79,13 @@ export function activate(ctx) { return false; } - function cloneProject(oldId, newId) { + function cloneProjectImpl(oldId, newId) { if (checkExistence(newId)) { return } let data = ctx.services.storage.get(PROJECTS_PREFIX + oldId); if (data) { - ctx.services.storage.set(PROJECTS_PREFIX + newId); + ctx.services.storage.set(PROJECTS_PREFIX + newId, data); let sketchesNamespace = PROJECTS_PREFIX + oldId + SKETCH_SUFFIX; let sketchKeys = ctx.services.storage.getAllKeysFromNamespace(sketchesNamespace); sketchKeys.forEach(key => { @@ -78,6 +94,18 @@ export function activate(ctx) { } } + function cloneProject(oldProjectId, silent) { + let newProjectId = prompt("New Project Name", oldProjectId); + if (newProjectId) { + cloneProjectImpl(oldProjectId, newProjectId); + if (!silent) { + openProject(newProjectId); + } + return true; + } + return false; + } + function exists(projectId) { return ctx.services.storage.exists(PROJECTS_PREFIX + projectId); } @@ -114,35 +142,42 @@ export function activate(ctx) { return Object.keys(projects).sort().map(key => projects[key]); } + function deleteProjectImpl(projectId) { + ctx.services.storage.remove(PROJECTS_PREFIX + projectId); + let sketchesNamespace = PROJECTS_PREFIX + projectId + SKETCH_SUFFIX; + ctx.services.storage.getAllKeysFromNamespace(sketchesNamespace).forEach(key => ctx.services.storage.remove(key)); + } + function deleteProject(projectId) { if (confirm(`Project ${projectId} will be deleted. Continue?`)) { - ctx.services.storage.remove(PROJECTS_PREFIX + projectId); + deleteProjectImpl(projectId) } } - - function renameProject(oldProjectId, newProjectId) { - if (checkExistence(newProjectId)) { - return + + function renameProject(oldProjectId, silent) { + if (cloneProject(oldProjectId, silent)) { + deleteProjectImpl(oldProjectId); } - cloneProject(oldProjectId, newProjectId); - deleteProject(oldProjectId); } - function newProject(newProjectId) { - if (checkExistence(newProjectId)) { - return + function newProject() { + let newProjectId = prompt("New Project Name"); + if (newProjectId) { + if (checkExistence(newProjectId)) { + return + } + openProject(newProjectId); } - openProject(newProjectId); } function openProject(projectId) { window.open('?' + projectId); } - ctx.services.ui.registerFloatView('ProjectManager', ProjectManager, 'Project Manager', 'book'); + ctx.services.ui.registerFloatView('ProjectManager', ProjectManager, 'Project Manager', 'database'); ctx.services.projectManager = { listProjects, openProject, newProject, renameProject, deleteProject, - exists, cloneProject, exportProject, importProject + exists, cloneProject, exportProject, importProjectAs, importProject } } \ No newline at end of file diff --git a/web/app/cad/sketch/inPlaceSketcher.js b/web/app/cad/sketch/inPlaceSketcher.js index e0617217..ac5fbc2e 100644 --- a/web/app/cad/sketch/inPlaceSketcher.js +++ b/web/app/cad/sketch/inPlaceSketcher.js @@ -8,10 +8,9 @@ import DPR from 'dpr'; export class InPlaceSketcher { - constructor(ctx, onSketchUpdate) { + constructor(ctx) { this.face = null; // should be only one in the state this.ctx = ctx; - this.onSketchUpdate = onSketchUpdate; } get inEditMode() { @@ -37,7 +36,7 @@ export class InPlaceSketcher { this.viewer.toolManager.setDefaultTool(new DelegatingPanTool(this.viewer, viewer3d.sceneSetup.renderer.domElement)); viewer3d.sceneSetup.trackballControls.addEventListener( 'change', this.onCameraChange); - let sketchData = localStorage.getItem(this.sketchStorageKey); + let sketchData = this.ctx.services.storage.get(this.sketchStorageKey); this.viewer.historyManager.init(sketchData); this.viewer.io.loadSketch(sketchData); this.ctx.streams.sketcher.sketchingFace.value = face; @@ -101,8 +100,7 @@ export class InPlaceSketcher { }; save() { - localStorage.setItem(this.sketchStorageKey, this.viewer.io.serializeSketch()); - this.onSketchUpdate({key: this.sketchStorageKey}); + this.ctx.services.storage.set(this.sketchStorageKey, this.viewer.io.serializeSketch()); } } diff --git a/web/app/cad/sketch/sketcherPlugin.js b/web/app/cad/sketch/sketcherPlugin.js index 124806bd..c9092adb 100644 --- a/web/app/cad/sketch/sketcherPlugin.js +++ b/web/app/cad/sketch/sketcherPlugin.js @@ -95,7 +95,7 @@ export function activate(ctx) { services.storage.set(sketchStorageKey, JSON.stringify(data)); } - let inPlaceEditor = new InPlaceSketcher(ctx, onSketchUpdate); + let inPlaceEditor = new InPlaceSketcher(ctx); function sketchFace(face) { updateSketchBoundaries(face); if (inPlaceEditor.inEditMode) { diff --git a/web/app/cad/storagePlugin.js b/web/app/cad/storagePlugin.js index 914f4036..5c9907a1 100644 --- a/web/app/cad/storagePlugin.js +++ b/web/app/cad/storagePlugin.js @@ -10,6 +10,7 @@ export function activate({services, streams}) { function set(key, value) { localStorage.setItem(key, value); + notify(key); } function get(key) { @@ -17,12 +18,23 @@ export function activate({services, streams}) { } function remove(key) { - return localStorage.removeItem(key); + try { + return localStorage.removeItem(key); + } finally { + notify(key); + } } function exists(key) { return localStorage.hasOwnProperty(key); } + + function notify(key) { + streams.storage.update.next({ + key, + timestamp: Date.now + }); + } function getAllKeysFromNamespace(namespace) { let keys = []; @@ -35,11 +47,9 @@ export function activate({services, streams}) { return keys; } - function addListener(handler) { - window.addEventListener('storage', handler, false); - } + window.addEventListener('storage', evt => notify(evt.key), false); - addListener(() => streams.storage.update.next(Date.now)); + const addListener = listener => streams.storage.update.attach(listener); services.storage = { set, get, remove, addListener, getAllKeysFromNamespace, exists