working local file save
parent
138d8ec091
commit
5d782cf9a9
|
@ -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;
|
||||||
|
}
|
|
@ -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>
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,10 @@
|
||||||
|
const { merge } = require('webpack-merge');
|
||||||
|
|
||||||
|
const common = require('./webpack.common.js');
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = merge(common, {
|
||||||
|
|
||||||
|
mode: 'production',
|
||||||
|
|
||||||
|
});
|
|
@ -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-----
|
|
@ -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-----
|
65
src/Scene.js
65
src/Scene.js
|
@ -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()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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'));
|
||||||
|
|
|
@ -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}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
|
|
|
@ -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'],
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -123,4 +111,26 @@ export const NavBar = () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</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();
|
||||||
|
// };
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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} />
|
||||||
))}
|
))}
|
||||||
|
|
7
todo.txt
7
todo.txt
|
@ -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
|
||||||
|
|
|
@ -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'),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
})
|
})
|
Loading…
Reference in New Issue