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