working local file save

master
howard 2021-04-18 12:14:01 -07:00
parent 138d8ec091
commit 5d782cf9a9
20 changed files with 436 additions and 84 deletions

148
dist/fs-helpers.js vendored Normal file
View File

@ -0,0 +1,148 @@
/**
* Copyright 2019 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
/* exported getFileHandle, getNewFileHandle, readFile, verifyPermission,
writeFile */
/**
* Open a handle to an existing file on the local file system.
*
* @return {!Promise<FileSystemFileHandle>} Handle to the existing file.
*/
function getFileHandle() {
// For Chrome 86 and later...
if ('showOpenFilePicker' in window) {
return window.showOpenFilePicker().then((handles) => handles[0]);
}
// For Chrome 85 and earlier...
return window.chooseFileSystemEntries();
}
/**
* Create a handle to a new (text) file on the local file system.
*
* @return {!Promise<FileSystemFileHandle>} Handle to the new file.
*/
function getNewFileHandle() {
// For Chrome 86 and later...
if ('showSaveFilePicker' in window) {
const opts = {
types: [{
description: 'Text file',
accept: {'text/plain': ['.txt']},
}],
};
return window.showSaveFilePicker(opts);
}
// For Chrome 85 and earlier...
const opts = {
type: 'save-file',
accepts: [{
description: 'Text file',
extensions: ['txt'],
mimeTypes: ['text/plain'],
}],
};
return window.chooseFileSystemEntries(opts);
}
/**
* Reads the raw text from a file.
*
* @param {File} file
* @return {!Promise<string>} A promise that resolves to the parsed string.
*/
function readFile(file) {
// If the new .text() reader is available, use it.
if (file.text) {
return file.text();
}
// Otherwise use the traditional file reading technique.
return _readFileLegacy(file);
}
/**
* Reads the raw text from a file.
*
* @private
* @param {File} file
* @return {Promise<string>} A promise that resolves to the parsed string.
*/
function _readFileLegacy(file) {
return new Promise((resolve) => {
const reader = new FileReader();
reader.addEventListener('loadend', (e) => {
const text = e.srcElement.result;
resolve(text);
});
reader.readAsText(file);
});
}
/**
* Writes the contents to disk.
*
* @param {FileSystemFileHandle} fileHandle File handle to write to.
* @param {string} contents Contents to write.
*/
async function writeFile(fileHandle, contents) {
// Support for Chrome 82 and earlier.
if (fileHandle.createWriter) {
// Create a writer (request permission if necessary).
const writer = await fileHandle.createWriter();
// Write the full length of the contents
await writer.write(0, contents);
// Close the file and write the contents to disk
await writer.close();
return;
}
// For Chrome 83 and later.
// Create a FileSystemWritableFileStream to write to.
const writable = await fileHandle.createWritable();
// Write the contents of the file to the stream.
await writable.write(contents);
// Close the file and write the contents to disk.
await writable.close();
}
/**
* Verify the user has granted permission to read or write to the file, if
* permission hasn't been granted, request permission.
*
* @param {FileSystemFileHandle} fileHandle File handle to check.
* @param {boolean} withWrite True if write permission should be checked.
* @return {boolean} True if the user has granted read/write permission.
*/
async function verifyPermission(fileHandle, withWrite) {
const opts = {};
if (withWrite) {
opts.writable = true;
// For Chrome 86 and later...
opts.mode = 'readwrite';
}
// Check if we already have permission, if so, return true.
if (await fileHandle.queryPermission(opts) === 'granted') {
return true;
}
// Request permission to the file, if the user grants permission, return true.
if (await fileHandle.requestPermission(opts) === 'granted') {
return true;
}
// The user did nt grant permission, return false.
return false;
}

1
dist/index.html vendored
View File

@ -25,6 +25,7 @@
<script src="app.bundle.js"></script> <script src="app.bundle.js"></script>
<script src="scene.bundle.js"></script> <script src="scene.bundle.js"></script>
<script src="solver.js"></script> <script src="solver.js"></script>
<script src="fs-helpers.js"></script>
</body> </body>
</html> </html>

1
example_parts/qwre Normal file

File diff suppressed because one or more lines are too long

0
example_parts/test.json Normal file
View File

1
example_parts/test2.json Normal file

File diff suppressed because one or more lines are too long

0
example_parts/testt.json Normal file
View File

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,10 @@
const { merge } = require('webpack-merge');
const common = require('./webpack.common.js');
module.exports = merge(common, {
mode: 'production',
});

28
localhost-key.pem Normal file
View File

@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDyMGwhoISvPn3Q
QBJbSHZSTM8mGgK4DwY0REvnzvZPycGOdqBRumPqDExUmDGZehDvPB9KS5rITIac
sDe/IkNB438tyvjxpwT959G0DaGf282/KsT1V7NlkHp6voP5NfzJLl4h2suzzTrF
bYiftcaTn86PhaTpHnInBOiNrDcOv3vJmDn0fUICiGD3m0q6i7G3dsNuJDCMOXMi
oCL46g4WIUiLU3uTfhHaKzDsUW+gKu6Q0sEwVd1GzTz7S4Xde77hUAjapHOFuFT8
D8WF4Ze7pZNfKx3CnQmY3dyHWjZu0GgQuypF2IXk93Q9IYts3U3phq0WzPcq4L3d
6qVsZsbbAgMBAAECggEAa5QUxA8gQROaIUIEpWWXoVEbBsqxAH8z+02HBg4JnUF6
Z8TLy+HmddVGpqEADzOIiCwFniPdOjG77afc61rV01Oxb27ki7rr3bj2jmsrqu2h
A9SErpJpTqkRrqonxzAy/E5LY/BjYZe9DmtsL7032uU2hMwRh7eNb0Wf4yZnQnXw
2aGajqF7i1R50B1uBlh/EeS+3+nR9O5YSi9abyHsYxGz8l+wSvA5uLv0dkP7xWBb
8PmMN87wEbZDV4jJyyJw4VhaFvMgjwwboTDcOgOnrGOAN31991LtBAt6ypSeOndo
Tvl4xfYA1Gs6mgCx+LtRnSoPVKj3ziLw/tdezgsT4QKBgQD4YizViNoD4HqwnDdQ
NopSMSw0gJGRw4NFvAm1Ci2WjOv1cBQaorRwu+nYfyaRlkaHcG3dHr24DW4VcUY8
VnZBRq6SvmmuXCenHXiXOykWFXFfxZSJT1svfllpAXdAPcQm5luIIYV/eGg8gBJt
OjECNFYObDMGrM03A2G3i+jxUQKBgQD5nZ8Lz5ANhZd9DEu7hJcIiBh6m7Z3JujW
U9DTDvZ51MzThgJRC2A6dU8mcJfZo3Wa+Pv1xsz0OjjG+83bMNJB0OVff5laaU50
eApOp5XsNHGzcjYoxSxxztCd7UFtf4Bu33yGDN91B8WQY1c++BpZQ4XCG8AKFzdI
HqQAAaDKawKBgQCzRvFDYxaxK6qCpQ6LmAI4lwNoFdB8HFk40SNUh7cl7is1qSLp
oryIjimYORZWiNf5VB4INvMK0K6/TVY7oNCUBvdkNYnD7wIz7eKnjWz3YpzFWq/+
d8fCPPk+AG/Zb3uP9D7mwANCYV8jI/Go4xKSm8HtgQ1HaRxp88fpGlQVMQKBgQD3
uAxR/VAZiz2WtPAnjWMR7XZVn1iKkQu7P/zaqFvE9oG7XZ/I7EA4Y5kELfMU4tpg
zL3H4N4fdfRIzTYzVBUliflIN+ppxl48ybB49Gmdu0Incq368gq0eymfwQgQcdt0
rMf4hKfyjZ7sNxorfK8xbQg+ZanEmdub8ASTmQoINwKBgHnT+FJyuhvrcnMMhS4Q
G8wvL1gbnQCBUd8+ouc9vbfE8eXFQVyGuqjToPQcKxl9rjha4X9gNw9yWa5zxOhX
5LEmewUMo1LfaryhBt08Mc2CeIbFTEveqj3Ajhn1t9wW03X7+8A8E0sPBLzpDnr0
yRyKZKdn91/5P6r1MztY2LQh
-----END PRIVATE KEY-----

24
localhost.pem Normal file
View File

@ -0,0 +1,24 @@
-----BEGIN CERTIFICATE-----
MIID9DCCAlygAwIBAgIQKfKuWsppHxEm8FuAqgxhpjANBgkqhkiG9w0BAQsFADBP
MR4wHAYDVQQKExVta2NlcnQgZGV2ZWxvcG1lbnQgQ0ExEjAQBgNVBAsMCWhvd2Fy
ZEBocDEZMBcGA1UEAwwQbWtjZXJ0IGhvd2FyZEBocDAeFw0yMTA0MTgwNjIxMjZa
Fw0yMzA3MTgwNjIxMjZaMD0xJzAlBgNVBAoTHm1rY2VydCBkZXZlbG9wbWVudCBj
ZXJ0aWZpY2F0ZTESMBAGA1UECwwJaG93YXJkQGhwMIIBIjANBgkqhkiG9w0BAQEF
AAOCAQ8AMIIBCgKCAQEA8jBsIaCErz590EASW0h2UkzPJhoCuA8GNERL5872T8nB
jnagUbpj6gxMVJgxmXoQ7zwfSkuayEyGnLA3vyJDQeN/Lcr48acE/efRtA2hn9vN
vyrE9VezZZB6er6D+TX8yS5eIdrLs806xW2In7XGk5/Oj4Wk6R5yJwTojaw3Dr97
yZg59H1CAohg95tKuouxt3bDbiQwjDlzIqAi+OoOFiFIi1N7k34R2isw7FFvoCru
kNLBMFXdRs08+0uF3Xu+4VAI2qRzhbhU/A/FheGXu6WTXysdwp0JmN3ch1o2btBo
ELsqRdiF5Pd0PSGLbN1N6YatFsz3KuC93eqlbGbG2wIDAQABo14wXDAOBgNVHQ8B
Af8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwHwYDVR0jBBgwFoAU1LipvY+p
DhxjE8UP8QpCiAfmI3kwFAYDVR0RBA0wC4IJbG9jYWxob3N0MA0GCSqGSIb3DQEB
CwUAA4IBgQAaGHY+H63/Q7MQmRyKG3eTaW8al2pzd7MejiNSDt9R+2KeiMwmzvC8
OlslfNviatLfMqaOA5WrdpifblVRqjXZyblqeaswlA5S0gzx4JLr7yKtb4FVlwpa
Iv3/cOJB0HXEhJoWKTSKAFXgjqxUSaRbSX2tQRiRNE72shc9J5cYP3AMCloC3PUG
pbEyo+08cEHIUoZBMmjSFB2cB8JQijs3iDSCCGILCzjV0/33I0RfoFWIZZPIYFON
1zDYmaXWmpd7GAsl1rHE9o835HnyqGFy+IOovxVufSqzzRX8JlOzCzKioIfq4EjC
FLpNlZ0snDovZ6AmmH3UL1Nk2N/0WaLrrgnr58qtBqao1x1DdhcjGymjPFRyqItI
4yNnlKpaYHk5W/Z8zm8Pb1b/tCBmdlFyYyBmqkRSOqcwS/eGmV9BFZJrdU1XM4fH
0kyIOt9bDZcZWvwOcD94TUz9FNu18GDUhqBrqPAYSMlaJpe60pPiPcsE21/2usqi
H6s/L19rZxE=
-----END CERTIFICATE-----

View File

@ -24,8 +24,8 @@ window.loader = new THREE.ObjectLoader();
window.STLexp = new STLExporter(); window.STLexp = new STLExporter();
window.id = 0 window.id = 0
window.sid = 1 // window.sid = 1
window.mid = 1 // window.mid = 1
const pointMaterial = new THREE.PointsMaterial({ const pointMaterial = new THREE.PointsMaterial({
@ -62,7 +62,14 @@ export class Scene {
controls.target.set(0, 0, 0); controls.target.set(0, 0, 0);
controls.update(); controls.update();
this.obj3d = new THREE.Scene()
this.obj3d = new THREE.Scene() ///////
// this.obj3d.background = new THREE.Color(color.background); // this.obj3d.background = new THREE.Color(color.background);
const helpersGroup = new THREE.Group(); const helpersGroup = new THREE.Group();
@ -170,21 +177,30 @@ export class Scene {
return needResize; return needResize;
} }
saveState() {
localStorage.setItem( saveScene() {
'sv2', JSON.stringify([id, this.sid, this.mid, this.store.getState().treeEntries])
)
}
saveString() {
return JSON.stringify([id, this.sid, this.mid, this.store.getState().treeEntries]) return JSON.stringify([id, this.sid, this.mid, this.store.getState().treeEntries])
} }
loadState() { //uglyyy
clearScene() {
const deleted = this.obj3d.children.splice(1)
console.log(deleted)
for (let i = 0; i < deleted.length; i++) {
deleted[i].traverse((obj) => {
if (obj.geometry) obj.geometry.dispose()
if (obj.material) obj.material.dispose()
})
}
}
loadState(file) { //uglyyy
this.clearScene()
const [curid, cursid, curmid, state] = JSON.parse( const [curid, cursid, curmid, state] = JSON.parse(
localStorage.getItem('sv2') file
) )
window.id = curid window.id = curid
@ -234,27 +250,6 @@ export class Scene {
return entry return entry
} }
// clearSelection() {
// for (let x = 0, obj; x < this.selected.length; x++) {
// obj = this.selected[x]
// if (obj.userData.type == 'selpoint') {
// obj.visible = false
// } else {
// setHover(obj, 0)
// }
// }
// this.selected = []
// for (let x = 0; x < this.hovered.length; x++) {
// const obj = this.hovered[x]
// setHover(obj, 0)
// }
// }
boolOp(m1, m2, op, refresh = false) { boolOp(m1, m2, op, refresh = false) {
let bspA = CSG.fromMesh(m1) let bspA = CSG.fromMesh(m1)
@ -425,7 +420,7 @@ async function addSketch() {
} }
window.sc = new Scene(store) window.sc = new Scene(store)
sc.loadState() // sc.loadState()

View File

@ -3,7 +3,7 @@ import ReactDOM from 'react-dom'
import React, { } from 'react' import React, { } from 'react'
import { createStore, applyMiddleware } from 'redux' import { createStore, applyMiddleware } from 'redux'
import { Provider } from 'react-redux' import { Provider, useSelector } from 'react-redux'
import { reducer } from './reducer' import { reducer } from './reducer'
import logger from 'redux-logger' import logger from 'redux-logger'
@ -32,13 +32,13 @@ const store = createStore(reducer, {}, applyMiddleware(logger))
// const store = createStore(reducer, sc.loadState(), applyMiddleware(logger)) // const store = createStore(reducer, sc.loadState(), applyMiddleware(logger))
const App = ({ store }) => ( const App = ({ store }) => {
<Provider store={store}> return <Provider store={store}>
<NavBar /> <NavBar />
<Tree /> <Tree />
<ToolTip /> <ToolTip />
</Provider> </Provider>
) }
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
ReactDOM.render(<App store={store} />, document.getElementById('react')); ReactDOM.render(<App store={store} />, document.getElementById('react'));

View File

@ -44,7 +44,7 @@ export const Dialog = () => {
switch (dialog.action) { switch (dialog.action) {
case 'extrude': case 'extrude':
return <> return <>
<input className='w-16 border-t-0 border-l-0 border-r-0 border-b border-gray-50 text-gray-50 mr-6' type="number" defaultValue="1" step="0.1" ref={ref} /> <input className='w-16 border-t-0 border-l-0 border-r-0 border-b border-gray-50 text-gray-50 mr-2' type="number" defaultValue="1" step="0.1" ref={ref} />
<Icon.Flip className="btn w-auto h-full p-3.5" <Icon.Flip className="btn w-auto h-full p-3.5"
onClick={() => ref.current.value *= -1} onClick={() => ref.current.value *= -1}
/> />
@ -58,7 +58,7 @@ export const Dialog = () => {
</> </>
case 'extrude-edit': case 'extrude-edit':
return <> return <>
<input className='w-16 border-t-0 border-l-0 border-r-0 border-b border-gray-50 text-gray-50 mr-6' type="number" defaultValue={dialog.target.userData.featureInfo[1]} step="0.1" ref={ref} /> <input className='w-16 border-t-0 border-l-0 border-r-0 border-b border-gray-50 text-gray-50 mr-2' type="number" defaultValue={dialog.target.userData.featureInfo[1]} step="0.1" ref={ref} />
<Icon.Flip className="btn w-auto h-full p-3.5" <Icon.Flip className="btn w-auto h-full p-3.5"
onClick={() => ref.current.value *= -1} onClick={() => ref.current.value *= -1}
/> />

View File

@ -1,6 +1,6 @@
const link = document.createElement( 'a' ); const link = document.createElement('a');
link.style.display = 'none'; link.style.display = 'none';
document.body.appendChild( link ); document.body.appendChild(link);
function save(blob, filename) { function save(blob, filename) {
@ -11,30 +11,147 @@ function save(blob, filename) {
} }
function saveArrayBuffer( buffer, filename ) { function saveArrayBuffer(buffer, filename) {
// save( new Blob( [ buffer ], { type: 'application/octet-stream' } ), filename ); // save( new Blob( [ buffer ], { type: 'application/octet-stream' } ), filename );
save( new Blob( [ buffer ], { type: 'model/stl' } ), filename ); save(new Blob([buffer], { type: 'model/stl' }), filename);
} }
function saveString( text, filename ) { function saveString(text, filename) {
// save( new Blob( [ text ], { type: 'text/plain' } ), filename ); // save( new Blob( [ text ], { type: 'text/plain' } ), filename );
save( new Blob( [ text ], { type: 'application/json' } ), filename ); save(new Blob([text], { type: 'application/json' }), filename);
} }
export function STLExport() { export function STLExport() {
if (sc.selected[0] && sc.selected[0].userData.type == 'mesh') { if (sc.selected[0] && sc.selected[0].userData.type == 'mesh') {
const result = STLexp.parse( sc.selected[0], { binary: true } ); const result = STLexp.parse(sc.selected[0], { binary: true });
saveArrayBuffer( result, 'box.stl' ); saveArrayBuffer(result, 'box.stl');
} }
} }
export function savePart() { export async function saveFile(fileHandle, file, dispatch) {
try {
if (!fileHandle) {
return await saveFileAs(file, dispatch);
}
await writeFile(fileHandle, file);
saveString( sc.saveString(), 'uncomp.json' ); dispatch({ type: 'set-modified', status: false })
} catch (ex) {
const msg = 'Unable to save file';
console.error(msg, ex);
alert(msg);
}
// app.setFocus();
};
export async function saveFileAs(file, dispatch) {
let fileHandle;
try {
const opts = {
types: [{
// description: 'Text file',
accept: { 'application/json': ['.json'] },
}],
};
fileHandle = await showSaveFilePicker(opts)
} catch (ex) {
if (ex.name === 'AbortError') {
return;
}
const msg = 'An error occured trying to open the file.';
console.error(msg, ex);
alert(msg);
return;
}
try {
const writable = await fileHandle.createWritable();
// Write the contents of the file to the stream.
await writable.write(file);
// Close the file and write the contents to disk.
await writable.close()
dispatch({ type: 'set-file-handle', fileHandle, modified: false })
} catch (ex) {
const msg = 'Unable to save file.';
console.error(msg, ex);
alert(msg);
return;
}
// app.setFocus();
};
async function verifyPermission(fileHandle, withWrite) {
const opts = {};
if (withWrite) {
opts.writable = true;
// For Chrome 86 and later...
opts.mode = 'readwrite';
}
// Check if we already have permission, if so, return true.
if (await fileHandle.queryPermission(opts) === 'granted') {
return true;
}
// Request permission to the file, if the user grants permission, return true.
if (await fileHandle.requestPermission(opts) === 'granted') {
return true;
}
// The user did nt grant permission, return false.
return false;
} }
export async function openFile(dispatch) {
// if (!app.confirmDiscard()) {
// return;
// }
let fileHandle
// If a fileHandle is provided, verify we have permission to read/write it,
// otherwise, show the file open prompt and allow the user to select the file.
try {
fileHandle = await getFileHandle();
} catch (ex) {
if (ex.name === 'AbortError') {
return;
}
const msg = 'An error occured trying to open the file.';
console.error(msg, ex);
alert(msg);
}
if (!fileHandle) {
return;
}
const file = await fileHandle.getFile();
readFile(file, fileHandle, dispatch);
try {
const text = await readFile(file);
sc.loadState(text)
dispatch({ type: 'set-file-handle', fileHandle })
// app.setModified(false);
// app.setFocus(true);
} catch (ex) {
const msg = `An error occured reading ${fileHandle}`;
console.error(msg, ex);
alert(msg);
}
};

View File

@ -4,33 +4,13 @@ import React, { useEffect, useReducer } from 'react';
import { useDispatch, useSelector } from 'react-redux' import { useDispatch, useSelector } from 'react-redux'
import { FaEdit } from 'react-icons/fa' import { FaEdit, FaFileDownload } from 'react-icons/fa'
import { MdSave } from 'react-icons/md' import { MdSave, MdFolder, MdFileUpload, MdInsertDriveFile } from 'react-icons/md'
import { FaFolderOpen } from 'react-icons/fa' import { FaRegFolderOpen, FaFile } from 'react-icons/fa'
import * as Icon from "./icons"; import * as Icon from "./icons";
import { Dialog } from './dialog' import { Dialog } from './dialog'
import { STLExport, savePart } from './fileExporter' import { STLExport, savePart, saveFile, openFile } from './fileExporter'
const link = document.createElement('a');
link.style.display = 'none';
document.body.appendChild(link);
function save(blob, filename) {
link.href = URL.createObjectURL(blob);
link.download = filename;
link.click();
}
function saveArrayBuffer(buffer, filename) {
save(new Blob([buffer], { type: 'application/octet-stream' }), filename);
}
@ -39,6 +19,7 @@ export const NavBar = () => {
const dispatch = useDispatch() const dispatch = useDispatch()
const activeSketchId = useSelector(state => state.treeEntries.activeSketchId) const activeSketchId = useSelector(state => state.treeEntries.activeSketchId)
const treeEntriesById = useSelector(state => state.treeEntries.byId) const treeEntriesById = useSelector(state => state.treeEntries.byId)
const fileHandle = useSelector(state => state.ui.fileHandle)
const boolOp = (code) => { const boolOp = (code) => {
if (sc.selected.length != 2 || !sc.selected.every(e => e.userData.type == 'mesh')) return if (sc.selected.length != 2 || !sc.selected.every(e => e.userData.type == 'mesh')) return
@ -55,6 +36,9 @@ export const NavBar = () => {
forceUpdate() forceUpdate()
} }
useEffect(() => { // hacky way to handle mounting and unmounting mouse listeners for feature mode useEffect(() => { // hacky way to handle mounting and unmounting mouse listeners for feature mode
if (!activeSketchId) { if (!activeSketchId) {
sc.canvas.addEventListener('pointermove', sc.onHover) sc.canvas.addEventListener('pointermove', sc.onHover)
@ -79,6 +63,7 @@ export const NavBar = () => {
[Icon.Vertical, () => sc.activeSketch.command('v'), 'Vertical [v]'], [Icon.Vertical, () => sc.activeSketch.command('v'), 'Vertical [v]'],
[Icon.Horizontal, () => sc.activeSketch.command('h'), 'Horizontal [h]'], [Icon.Horizontal, () => sc.activeSketch.command('h'), 'Horizontal [h]'],
[Icon.Tangent, () => sc.activeSketch.command('t'), 'Tangent [t]'], [Icon.Tangent, () => sc.activeSketch.command('t'), 'Tangent [t]'],
[Icon.Tangent, () => sc.activeSketch.command('t'), 'Tangent [t]'],
] ]
@ -91,8 +76,11 @@ export const NavBar = () => {
[Icon.Union, () => boolOp('u'), 'Union'], [Icon.Union, () => boolOp('u'), 'Union'],
[Icon.Subtract, () => boolOp('s'), 'Subtract'], [Icon.Subtract, () => boolOp('s'), 'Subtract'],
[Icon.Intersect, () => boolOp('i'), 'Intersect'], [Icon.Intersect, () => boolOp('i'), 'Intersect'],
[MdSave, savePart, 'Save [ctrl+s]'], [MdInsertDriveFile, savePart, 'New [ctrl+n]'],
[FaFolderOpen, () => boolOp('i'), 'Load'], [MdSave, () => {
saveFile(fileHandle, sc.saveScene(), dispatch)
}, 'Save [ctrl+s]'],
[MdFolder, () => openFile(dispatch), 'Open'],
[Icon.Stl, STLExport, 'Export STL'], [Icon.Stl, STLExport, 'Export STL'],
] ]
@ -124,3 +112,25 @@ export const NavBar = () => {
</div> </div>
} }
// app.saveFile = async () => {
// try {
// if (!app.file.handle) {
// return await app.saveFileAs();
// }
// gaEvent('FileAction', 'Save');
// await writeFile(app.file.handle, app.getText());
// app.setModified(false);
// } catch (ex) {
// gaEvent('Error', 'FileSave', ex.name);
// const msg = 'Unable to save file';
// console.error(msg, ex);
// alert(msg);
// }
// app.setFocus();
// };

View File

@ -105,7 +105,7 @@ export function treeEntries(state = defaultState, action) {
} }
} }
export function ui(state = { dialog: {} }, action) { export function ui(state = { dialog: {}, filePane: false }, action) {
switch (action.type) { switch (action.type) {
case 'set-dialog': case 'set-dialog':
@ -116,6 +116,11 @@ export function ui(state = { dialog: {} }, action) {
return update(state, { return update(state, {
dialog: { $set: {} }, dialog: { $set: {} },
}) })
case 'set-file-handle':
return update(state, {
fileHandle: { $set: action.fileHandle },
modified: { $set: false },
})
default: default:
return state return state
} }

View File

@ -1,6 +1,6 @@
import React, { useReducer, useState } from 'react'; import React, { useReducer, useState, useRef } from 'react';
import { useDispatch, useSelector } from 'react-redux' import { useDispatch, useSelector } from 'react-redux'
import { MdVisibilityOff, MdVisibility, MdDelete, MdRefresh } from 'react-icons/md' import { MdVisibilityOff, MdVisibility, MdDelete, MdRefresh } from 'react-icons/md'
@ -8,8 +8,10 @@ import { FaCube, FaEdit } from 'react-icons/fa'
export const Tree = () => { export const Tree = () => {
const treeEntries = useSelector(state => state.treeEntries) const treeEntries = useSelector(state => state.treeEntries)
const ref = useRef()
return <div className='sideNav flex flex-col bg-gray-800'> return <div className='sideNav flex flex-col bg-gray-800'>
<input className='w-16 text-gray-50 h-7 mx-1 border-0 focus:outline-none bg-transparent' type="text" defaultValue="untitled" step="0.1" ref={ref} />
{treeEntries.allIds.map((entId, idx) => ( {treeEntries.allIds.map((entId, idx) => (
<TreeEntry key={idx} entId={entId} /> <TreeEntry key={idx} entId={entId} />
))} ))}

View File

@ -34,17 +34,19 @@ loopfind especially arc, // fixed for single looop, good enough, maybe stretch g
dim tag delete //resolved dim tag delete //resolved
auto update extrude // done auto update extrude // done
extrude edit dialog // done extrude edit dialog // done
file save, stl export// done
-sometimes unable to hit return and change dimensionk -sometimes unable to hit return and change dimensionk
-unable to delete arc -unable to delete arc
hover not clearing sometimes in sketch hover not clearing sometimes in sketch
0.000 artifact 0.000 artifact
lighting messed up
seperate scene from init logic only init cam and rendere
file save, stl export
reattach sketch reattach sketch
auto snap
highlight button to indicate active mode highlight button to indicate active mode
@ -53,7 +55,6 @@ highlight button to indicate active mode
add cancle soft button for line arc add cancle soft button for line arc
auto snap
constraint labels,equal constraint labels,equal
add download button, different from save button add download button, different from save button

View File

@ -2,6 +2,7 @@ const { merge } = require('webpack-merge');
const common = require('./webpack.common.js'); const common = require('./webpack.common.js');
const path = require('path'); const path = require('path');
const fs = require('fs');
module.exports = merge(common, { module.exports = merge(common, {
@ -12,5 +13,11 @@ module.exports = merge(common, {
contentBase: path.join(__dirname, 'dist'), contentBase: path.join(__dirname, 'dist'),
compress: true, compress: true,
port: 9000, port: 9000,
https: {
key: fs.readFileSync('./localhost-key.pem'),
cert: fs.readFileSync('./localhost.pem'),
ca: fs.readFileSync('/home/howard/.local/share/mkcert/rootCA.pem'),
},
}, },
}) })