mirror of
https://github.com/xibyte/jsketcher
synced 2025-12-06 16:33:15 +01:00
export and import project as a bundle
This commit is contained in:
parent
a1e2b80948
commit
c47d53cd86
11 changed files with 276 additions and 26 deletions
|
|
@ -57,6 +57,25 @@ 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',
|
||||
|
|
|
|||
|
|
@ -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 <Folder className={ls.folder} title={<span> <Fa fw icon={icon}/> {title}</span>}>
|
||||
<Component/>
|
||||
<div className={ls.folderContent}><Component/></div>
|
||||
</Folder>;
|
||||
}
|
||||
|
||||
|
|
@ -31,18 +37,19 @@ export default class FloatView extends React.Component {
|
|||
return <Icon />
|
||||
}
|
||||
|
||||
let selected = this.state.selected;
|
||||
|
||||
return <div className={ls.root}>
|
||||
<div className={ls.tabs}>
|
||||
{views.map(tabId => <ToolButton pressed={this.state.selected === tabId}
|
||||
{views.map(tabId => <ToolButton pressed={selected === tabId}
|
||||
key={tabId}
|
||||
onClick={() => this.setState({selected: this.state.selected === tabId ? null : tabId})}>
|
||||
onClick={() => this.setState({selected: selected === tabId ? null : tabId})}>
|
||||
{<Fa fw icon={getDescriptor(tabId).icon}/>}
|
||||
</ToolButton>)}
|
||||
</div>
|
||||
|
||||
{this.state.selected && <div className={ls.main}>
|
||||
{view(this.state.selected)}
|
||||
</div>}
|
||||
{selected && view(selected)}
|
||||
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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
|
||||
};
|
||||
}
|
||||
|
|
@ -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 = [
|
||||
|
|
|
|||
43
web/app/cad/projectManager/ProjectManager.jsx
Normal file
43
web/app/cad/projectManager/ProjectManager.jsx
Normal file
|
|
@ -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 <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)}/>
|
||||
</React.Fragment>
|
||||
}>
|
||||
<a href={'?' + p.id} target="_blank"><Fa icon='file'/> {p.id}</a>
|
||||
</ContextMenu>}>
|
||||
{p.sketches.length && <Section label={<span><Fa icon='image'/> Sketches</span>} defaultOpen={true}>
|
||||
{p.sketches.map(sketch => <Section key={sketch} label={sketch}/>)}
|
||||
</Section>}
|
||||
</Section>)}
|
||||
</div>
|
||||
</Folder>
|
||||
</div>
|
||||
}
|
||||
|
||||
}
|
||||
9
web/app/cad/projectManager/ProjectManager.less
Normal file
9
web/app/cad/projectManager/ProjectManager.less
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
.root a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.btn {
|
||||
width: 90%;
|
||||
margin: 2px;
|
||||
display: block;
|
||||
}
|
||||
148
web/app/cad/projectManager/projectManagerPlugin.js
Normal file
148
web/app/cad/projectManager/projectManagerPlugin.js
Normal file
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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--) {
|
||||
|
|
@ -28,7 +39,9 @@ export function activate({services}) {
|
|||
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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
</head>
|
||||
<body>
|
||||
<a id="downloader" style="display: none;" ></a>
|
||||
<input id="uploader" style="display: none;" type="file">
|
||||
<div id="app" style="height: 100%;"></div>
|
||||
<script src="static/index.bundle.js"></script>
|
||||
</body>
|
||||
|
|
|
|||
Loading…
Reference in a new issue