mirror of
https://github.com/xibyte/jsketcher
synced 2025-12-22 08:26:26 +01:00
implement project manager and bind with actions and menus
This commit is contained in:
parent
f43ff8debf
commit
f1ee1ba20a
8 changed files with 136 additions and 60 deletions
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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']
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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 <div className={ls.root}>
|
||||
<Button className={ls.btn}><Fa fw icon='download' /> Download Project</Button>
|
||||
<Button className={ls.btn}><Fa fw icon='upload' /> Import Project</Button>
|
||||
<Folder title='Project List'>
|
||||
<div className={cmn.scrollable}>
|
||||
{projects.map(p => <Section key={p.id}
|
||||
label={<ContextMenu items={
|
||||
<React.Fragment>
|
||||
<ContextMenuItem label='Download' icon={<Fa fw icon='download' />} onClick={() => alert(1)}/>
|
||||
<ContextMenuItem label='Clone' icon={<Fa fw icon='copy' />} onClick={() => alert(1)}/>
|
||||
<ContextMenuItem label='Delete' icon={<Fa className={cmn.dangerColor} fw icon='remove' />} onClick={() => alert(1)}/>
|
||||
<ContextMenuItem label='Download' icon={<Fa fw icon='download' />}
|
||||
onClick={() => this.props.download(p.id)}/>
|
||||
<ContextMenuItem label='Clone' icon={<Fa fw icon='copy' />}
|
||||
onClick={() => this.props.clone(p.id)}/>
|
||||
<ContextMenuItem label='Rename' icon={<Fa fw icon='pencil' />}
|
||||
onClick={() => this.props.rename(p.id)}/>
|
||||
<ContextMenuItem label='Delete' icon={<Fa className={cmn.dangerColor} fw icon='remove' />}
|
||||
onClick={() => this.props.remove(p.id)}/>
|
||||
</React.Fragment>
|
||||
}>
|
||||
<a href={'?' + p.id} target="_blank"><Fa icon='file'/> {p.id}</a>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,3 @@
|
|||
.root a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.btn {
|
||||
width: 90%;
|
||||
margin: 2px;
|
||||
display: block;
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue