diff --git a/web/app/cad/actions/coreActions.js b/web/app/cad/actions/coreActions.js index 30d71e41..f2d62163 100644 --- a/web/app/cad/actions/coreActions.js +++ b/web/app/cad/actions/coreActions.js @@ -57,7 +57,26 @@ export default [ invoke: (context) => context.services.export.imagePng() }, + { + id: 'NativeFormatExport', + appearance: { + cssIcons: ['book'], + label: 'Download Bundle', + info: 'export model and its sketches as a json bundle', + }, + invoke: (context) => context.services.export.nativeFormat() + }, + { + id: 'NativeFormatImport', + appearance: { + cssIcons: ['book'], + label: 'Import Bundle', + info: 'import native format json(model and its sketches)', + }, + invoke: (context) => context.services.projectManager.importProject() + }, + { id: 'RefreshSketches', appearance: { diff --git a/web/app/cad/dom/components/FloatView.jsx b/web/app/cad/dom/components/FloatView.jsx index d738e533..651a5bbb 100644 --- a/web/app/cad/dom/components/FloatView.jsx +++ b/web/app/cad/dom/components/FloatView.jsx @@ -8,13 +8,19 @@ import ToolButton from 'ui/components/ToolButton'; @connect(state => state.ui.floatViews.map(views => ({views}))) @mapContext(ctx => ({ - getDescriptor: ctx.services.ui.getFloatView + getDescriptor: ctx.services.ui.getFloatView, + initialView: ctx.services.project.hints.FloatView || null })) export default class FloatView extends React.Component { - state = { - selected: null - }; + + constructor(props) { + super(); + this.state = { + selected: props.initialView + }; + } + render() { let {views, getDescriptor} = this.props; @@ -22,7 +28,7 @@ export default class FloatView extends React.Component { function view(id) { let {title, icon, Component} = getDescriptor(id); return {title}}> - +
; } @@ -31,18 +37,19 @@ export default class FloatView extends React.Component { return } + let selected = this.state.selected; + return
- {views.map(tabId => this.setState({selected: this.state.selected === tabId ? null : tabId})}> + onClick={() => this.setState({selected: selected === tabId ? null : tabId})}> {} )}
- {this.state.selected &&
- {view(this.state.selected)} -
} + {selected && view(selected)} +
; } } \ No newline at end of file diff --git a/web/app/cad/dom/components/FloatView.less b/web/app/cad/dom/components/FloatView.less index 5cc75788..f5d7d6c8 100644 --- a/web/app/cad/dom/components/FloatView.less +++ b/web/app/cad/dom/components/FloatView.less @@ -16,16 +16,19 @@ font-size: 13px; } -.main { - display: flex; - flex-direction: column; - width: 235px; - flex: 1; - overflow-y: scroll; -} - .folder { height: 100%; display: flex; flex-direction: column; +} + +.folderContent { + flex: 1; + display: flex; + width: 235px; + overflow: auto; +} + +.folderContent > * { + flex: 1; } \ No newline at end of file diff --git a/web/app/cad/exportPlugin.js b/web/app/cad/exportPlugin.js index ece3e132..08052260 100644 --- a/web/app/cad/exportPlugin.js +++ b/web/app/cad/exportPlugin.js @@ -33,7 +33,11 @@ export function activate(ctx) { ctx.services.viewer.sceneSetup.render(); } + function nativeFormat() { + ctx.services.projectManager.exportProject(ctx.services.project.id); + } + ctx.services.export = { - stlAscii, imagePng, toStlAsciiString + stlAscii, imagePng, toStlAsciiString, nativeFormat }; } \ No newline at end of file diff --git a/web/app/cad/init/startApplication.js b/web/app/cad/init/startApplication.js index eae4d271..0f41ee53 100644 --- a/web/app/cad/init/startApplication.js +++ b/web/app/cad/init/startApplication.js @@ -19,6 +19,7 @@ import * as CraftPlugin from '../craft/craftPlugin'; import * as CraftUiPlugin from '../craft/craftUiPlugin'; import * as StoragePlugin from '../storagePlugin'; import * as ProjectPlugin from '../projectPlugin'; +import * as ProjectManagerPlugin from '../projectManager/projectManagerPlugin'; import * as SketcherPlugin from '../sketch/sketcherPlugin'; import * as ExportPlugin from '../exportPlugin'; import * as TpiPlugin from '../tpi/tpiPlugin'; @@ -54,7 +55,8 @@ export default function startApplication(callback) { CadRegistryPlugin, ExportPlugin, TpiPlugin, - E0Plugin + E0Plugin, + ProjectManagerPlugin ]; let plugins = [ diff --git a/web/app/cad/projectManager/ProjectManager.jsx b/web/app/cad/projectManager/ProjectManager.jsx new file mode 100644 index 00000000..63a705b0 --- /dev/null +++ b/web/app/cad/projectManager/ProjectManager.jsx @@ -0,0 +1,43 @@ +import React from 'react'; +import mapContext from 'ui/mapContext'; +import {Section} from 'ui/components/Section'; +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'; + +@mapContext(ctx => ({ + projectManager: ctx.services.projectManager +})) +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)}/> + + }> + {p.id} + }> + {p.sketches.length &&
Sketches} defaultOpen={true}> + {p.sketches.map(sketch =>
)} +
} +
)} +
+
+
+ } + +} \ No newline at end of file diff --git a/web/app/cad/projectManager/ProjectManager.less b/web/app/cad/projectManager/ProjectManager.less new file mode 100644 index 00000000..ac85a47a --- /dev/null +++ b/web/app/cad/projectManager/ProjectManager.less @@ -0,0 +1,9 @@ +.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 new file mode 100644 index 00000000..63f8c2da --- /dev/null +++ b/web/app/cad/projectManager/projectManagerPlugin.js @@ -0,0 +1,148 @@ +import {PROJECTS_PREFIX, SKETCH_SUFFIX} from '../projectPlugin'; +import {ProjectManager} from './ProjectManager'; +import exportTextData from '../../../../modules/gems/exportTextData'; + +export function activate(ctx) { + + function importProject() { + // let uploader = document.getElementById("uploader"); + let uploader = document.createElement('input'); + uploader.setAttribute('type', 'file'); + uploader.style.display = 'none'; + + document.body.appendChild(uploader); + uploader.click(); + function read() { + let reader = new FileReader(); + 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 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); + } + } finally { + document.body.removeChild(uploader); + } + }; + reader.readAsText(uploader.files[0]); + } + uploader.addEventListener('change', read, false); + } + + function exportProject(id) { + let modelData = ctx.services.storage.get(PROJECTS_PREFIX + id); + if (modelData) { + let data = '{\n"model":\n' + modelData + ',\n "sketches": [\n'; + let sketchesNamespace = PROJECTS_PREFIX + id + SKETCH_SUFFIX; + let sketchKeys = ctx.services.storage.getAllKeysFromNamespace(sketchesNamespace); + data += sketchKeys.map(key => `{"id":"${key.substring(sketchesNamespace.length)}", "data": ` + + ctx.services.storage.get(key) + "}").join('\n,'); + data += '\n]\n}'; + exportTextData(data, id + '.json'); + } + } + + function checkExistence(projectId) { + if (exists(projectId)) { + alert('Project with name ' + projectId + ' already exists'); + return true; + } + return false; + } + + function cloneProject(oldId, newId) { + if (checkExistence(newId)) { + return + } + let data = ctx.services.storage.get(PROJECTS_PREFIX + oldId); + if (data) { + ctx.services.storage.set(PROJECTS_PREFIX + newId); + let sketchesNamespace = PROJECTS_PREFIX + oldId + SKETCH_SUFFIX; + let sketchKeys = ctx.services.storage.getAllKeysFromNamespace(sketchesNamespace); + sketchKeys.forEach(key => { + ctx.services.storage.set(PROJECTS_PREFIX + newId + SKETCH_SUFFIX + key.substring(sketchesNamespace.length), ctx.services.storage.get(key)); + }); + } + } + + function exists(projectId) { + return ctx.services.storage.exists(PROJECTS_PREFIX + projectId); + } + + function listProjects() { + + let allProjectKeys = ctx.services.storage.getAllKeysFromNamespace(PROJECTS_PREFIX); + + let projects = {}; + + function getProject(id) { + let project = projects[id]; + if (!project) { + project = { + id, + sketches: [] + }; + projects[id] = project; + } + return project; + } + + for(let key of allProjectKeys) { + let sketchSuffixIdx = key.indexOf(SKETCH_SUFFIX); + if (sketchSuffixIdx !== -1) { + let projectId = key.substring(PROJECTS_PREFIX.length, sketchSuffixIdx); + let sketchId = key.substring(sketchSuffixIdx + SKETCH_SUFFIX.length); + getProject(projectId).sketches.push(sketchId); + } else { + let projectId = key.substring(PROJECTS_PREFIX.length); + getProject(projectId) + } + } + return Object.keys(projects).sort().map(key => projects[key]); + } + + function deleteProject(projectId) { + if (confirm(`Project ${projectId} will be deleted. Continue?`)) { + ctx.services.storage.remove(PROJECTS_PREFIX + projectId); + } + } + + function renameProject(oldProjectId, newProjectId) { + if (checkExistence(newProjectId)) { + return + } + cloneProject(oldProjectId, newProjectId); + deleteProject(oldProjectId); + } + + function newProject(newProjectId) { + if (checkExistence(newProjectId)) { + return + } + openProject(newProjectId); + } + + function openProject(projectId) { + window.open('?' + projectId); + } + + ctx.services.ui.registerFloatView('ProjectManager', ProjectManager, 'Project Manager', 'book'); + + ctx.services.projectManager = { + listProjects, openProject, newProject, renameProject, deleteProject, + exists, cloneProject, exportProject, importProject + } +} \ No newline at end of file diff --git a/web/app/cad/projectPlugin.js b/web/app/cad/projectPlugin.js index 2c223afb..e3f9f58d 100644 --- a/web/app/cad/projectPlugin.js +++ b/web/app/cad/projectPlugin.js @@ -3,7 +3,8 @@ import {runSandbox} from './sandbox'; import {LOG_FLAGS} from './logFlags'; export const STORAGE_GLOBAL_PREFIX = 'TCAD'; -const STORAGE_PREFIX = `${STORAGE_GLOBAL_PREFIX}.projects.`; +export const PROJECTS_PREFIX = `${STORAGE_GLOBAL_PREFIX}.projects.`; +export const SKETCH_SUFFIX = '.sketch.'; export function activate(context) { @@ -14,15 +15,15 @@ export function activate(context) { processParams(hints, context); - const sketchNamespace = id + '.sketch.'; - const sketchStorageNamespace = STORAGE_PREFIX + sketchNamespace; + const sketchNamespace = id + SKETCH_SUFFIX; + const sketchStorageNamespace = PROJECTS_PREFIX + sketchNamespace; function sketchStorageKey(faceId) { return sketchStorageNamespace + faceId; } function projectStorageKey() { - return STORAGE_PREFIX + id; + return PROJECTS_PREFIX + id; } function getSketchURL(sketchId) { diff --git a/web/app/cad/storagePlugin.js b/web/app/cad/storagePlugin.js index 3e9bc637..914f4036 100644 --- a/web/app/cad/storagePlugin.js +++ b/web/app/cad/storagePlugin.js @@ -1,5 +1,12 @@ +import {stream} from '../../../modules/lstream'; -export function activate({services}) { +export function defineStreams(ctx) { + ctx.streams.storage = { + update: stream() + } +} + +export function activate({services, streams}) { function set(key, value) { localStorage.setItem(key, value); @@ -13,6 +20,10 @@ export function activate({services}) { return localStorage.removeItem(key); } + function exists(key) { + return localStorage.hasOwnProperty(key); + } + function getAllKeysFromNamespace(namespace) { let keys = []; for(let i = localStorage.length - 1; i >= 0 ; i--) { @@ -27,8 +38,10 @@ export function activate({services}) { function addListener(handler) { window.addEventListener('storage', handler, false); } + + addListener(() => streams.storage.update.next(Date.now)); services.storage = { - set, get, remove, addListener, getAllKeysFromNamespace + set, get, remove, addListener, getAllKeysFromNamespace, exists } } diff --git a/web/index.html b/web/index.html index 665d711d..21595262 100644 --- a/web/index.html +++ b/web/index.html @@ -8,6 +8,7 @@ +