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 @@
+