major
|
@ -0,0 +1,32 @@
|
|||
/**
|
||||
* Copyright 2020 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.
|
||||
*/
|
||||
// @license © 2020 Google LLC. Licensed under the Apache License, Version 2.0.
|
||||
|
||||
import supported from './supported.mjs';
|
||||
|
||||
const implementation = !supported
|
||||
? import('./legacy/directory-open.mjs')
|
||||
: supported === 'chooseFileSystemEntries'
|
||||
? import('./fs-access-legacy/directory-open.mjs')
|
||||
: import('./fs-access/directory-open.mjs');
|
||||
|
||||
/**
|
||||
* For opening directories, dynamically either loads the File System Access API
|
||||
* module or the legacy method.
|
||||
*/
|
||||
export async function directoryOpen(...args) {
|
||||
return (await implementation).default(...args);
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
/**
|
||||
* Copyright 2020 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.
|
||||
*/
|
||||
// @license © 2020 Google LLC. Licensed under the Apache License, Version 2.0.
|
||||
|
||||
import supported from './supported.mjs';
|
||||
|
||||
const implementation = !supported
|
||||
? import('./legacy/file-open.mjs')
|
||||
: supported === 'chooseFileSystemEntries'
|
||||
? import('./fs-access-legacy/file-open.mjs')
|
||||
: import('./fs-access/file-open.mjs');
|
||||
|
||||
/**
|
||||
* For opening files, dynamically either loads the File System Access API module
|
||||
* or the legacy method.
|
||||
*/
|
||||
export async function fileOpen(...args) {
|
||||
return (await implementation).default(...args);
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
/**
|
||||
* Copyright 2020 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.
|
||||
*/
|
||||
// @license © 2020 Google LLC. Licensed under the Apache License, Version 2.0.
|
||||
|
||||
import supported from './supported.mjs';
|
||||
|
||||
const implementation = !supported
|
||||
? import('./legacy/file-save.mjs')
|
||||
: supported === 'chooseFileSystemEntries'
|
||||
? import('./fs-access-legacy/file-save.mjs')
|
||||
: import('./fs-access/file-save.mjs');
|
||||
|
||||
/**
|
||||
* For saving files, dynamically either loads the File System Access API module
|
||||
* or the legacy method.
|
||||
*/
|
||||
export async function fileSave(...args) {
|
||||
return (await implementation).default(...args);
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
/**
|
||||
* Copyright 2020 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.
|
||||
*/
|
||||
// @license © 2020 Google LLC. Licensed under the Apache License, Version 2.0.
|
||||
|
||||
const getFiles = async (dirHandle, recursive, path = dirHandle.name) => {
|
||||
const dirs = [];
|
||||
const files = [];
|
||||
for await (const entry of dirHandle.getEntries()) {
|
||||
const nestedPath = `${path}/${entry.name}`;
|
||||
if (entry.isFile) {
|
||||
files.push(
|
||||
entry.getFile().then((file) =>
|
||||
Object.defineProperty(file, 'webkitRelativePath', {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
get: () => nestedPath,
|
||||
})
|
||||
)
|
||||
);
|
||||
} else if (entry.isDirectory && recursive) {
|
||||
dirs.push(getFiles(entry, recursive, nestedPath));
|
||||
}
|
||||
}
|
||||
return [...(await Promise.all(dirs)).flat(), ...(await Promise.all(files))];
|
||||
};
|
||||
|
||||
/**
|
||||
* Opens a directory from disk using the (legacy) File System Access API.
|
||||
* @type { typeof import("../../index").directoryOpen }
|
||||
*/
|
||||
export default async (options = {}) => {
|
||||
options.recursive = options.recursive || false;
|
||||
const handle = await window.chooseFileSystemEntries({
|
||||
type: 'open-directory',
|
||||
});
|
||||
return getFiles(handle, options.recursive);
|
||||
};
|
|
@ -0,0 +1,43 @@
|
|||
/**
|
||||
* Copyright 2020 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.
|
||||
*/
|
||||
// @license © 2020 Google LLC. Licensed under the Apache License, Version 2.0.
|
||||
|
||||
const getFileWithHandle = async (handle) => {
|
||||
const file = await handle.getFile();
|
||||
file.handle = handle;
|
||||
return file;
|
||||
};
|
||||
|
||||
/**
|
||||
* Opens a file from disk using the (legacy) File System Access API.
|
||||
* @type { typeof import("../../index").fileOpen }
|
||||
*/
|
||||
export default async (options = {}) => {
|
||||
const handleOrHandles = await window.chooseFileSystemEntries({
|
||||
accepts: [
|
||||
{
|
||||
description: options.description || '',
|
||||
mimeTypes: options.mimeTypes || ['*/*'],
|
||||
extensions: options.extensions || [''],
|
||||
},
|
||||
],
|
||||
multiple: options.multiple || false,
|
||||
});
|
||||
if (options.multiple) {
|
||||
return Promise.all(handleOrHandles.map(getFileWithHandle));
|
||||
}
|
||||
return getFileWithHandle(handleOrHandles);
|
||||
};
|
|
@ -0,0 +1,40 @@
|
|||
/**
|
||||
* Copyright 2020 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.
|
||||
*/
|
||||
// @license © 2020 Google LLC. Licensed under the Apache License, Version 2.0.
|
||||
|
||||
/**
|
||||
* Saves a file to disk using the (legacy) File System Access API.
|
||||
* @type { typeof import("../../index").fileSave }
|
||||
*/
|
||||
export default async (blob, options = {}, handle = null) => {
|
||||
options.fileName = options.fileName || 'Untitled';
|
||||
handle =
|
||||
handle ||
|
||||
(await window.chooseFileSystemEntries({
|
||||
type: 'save-file',
|
||||
accepts: [
|
||||
{
|
||||
description: options.description || '',
|
||||
mimeTypes: [blob.type],
|
||||
extensions: options.extensions || [''],
|
||||
},
|
||||
],
|
||||
}));
|
||||
const writable = await handle.createWritable();
|
||||
await writable.write(blob);
|
||||
await writable.close();
|
||||
return handle;
|
||||
};
|
|
@ -0,0 +1,48 @@
|
|||
/**
|
||||
* Copyright 2020 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.
|
||||
*/
|
||||
// @license © 2020 Google LLC. Licensed under the Apache License, Version 2.0.
|
||||
|
||||
const getFiles = async (dirHandle, recursive, path = dirHandle.name) => {
|
||||
const dirs = [];
|
||||
const files = [];
|
||||
for await (const entry of dirHandle.values()) {
|
||||
const nestedPath = `${path}/${entry.name}`;
|
||||
if (entry.kind === 'file') {
|
||||
files.push(
|
||||
entry.getFile().then((file) =>
|
||||
Object.defineProperty(file, 'webkitRelativePath', {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
get: () => nestedPath,
|
||||
})
|
||||
)
|
||||
);
|
||||
} else if (entry.kind === 'directory' && recursive) {
|
||||
dirs.push(getFiles(entry, recursive, nestedPath));
|
||||
}
|
||||
}
|
||||
return [...(await Promise.all(dirs)).flat(), ...(await Promise.all(files))];
|
||||
};
|
||||
|
||||
/**
|
||||
* Opens a directory from disk using the File System Access API.
|
||||
* @type { typeof import("../../index").directoryOpen }
|
||||
*/
|
||||
export default async (options = {}) => {
|
||||
options.recursive = options.recursive || false;
|
||||
const handle = await window.showDirectoryPicker();
|
||||
return getFiles(handle, options.recursive);
|
||||
};
|
|
@ -0,0 +1,51 @@
|
|||
/**
|
||||
* Copyright 2020 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.
|
||||
*/
|
||||
// @license © 2020 Google LLC. Licensed under the Apache License, Version 2.0.
|
||||
|
||||
const getFileWithHandle = async (handle) => {
|
||||
const file = await handle.getFile();
|
||||
file.handle = handle;
|
||||
return file;
|
||||
};
|
||||
|
||||
/**
|
||||
* Opens a file from disk using the File System Access API.
|
||||
* @type { typeof import("../../index").fileOpen }
|
||||
*/
|
||||
export default async (options = {}) => {
|
||||
const accept = {};
|
||||
if (options.mimeTypes) {
|
||||
options.mimeTypes.map((mimeType) => {
|
||||
accept[mimeType] = options.extensions || [];
|
||||
});
|
||||
} else {
|
||||
accept['*/*'] = options.extensions || [];
|
||||
}
|
||||
const handleOrHandles = await window.showOpenFilePicker({
|
||||
types: [
|
||||
{
|
||||
description: options.description || '',
|
||||
accept: accept,
|
||||
},
|
||||
],
|
||||
multiple: options.multiple || false,
|
||||
});
|
||||
const files = await Promise.all(handleOrHandles.map(getFileWithHandle));
|
||||
if (options.multiple) {
|
||||
return files;
|
||||
}
|
||||
return files[0];
|
||||
};
|
|
@ -0,0 +1,67 @@
|
|||
/**
|
||||
* Copyright 2020 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.
|
||||
*/
|
||||
// @license © 2020 Google LLC. Licensed under the Apache License, Version 2.0.
|
||||
|
||||
/**
|
||||
* Saves a file to disk using the File System Access API.
|
||||
* @type { typeof import("../../index").fileSave }
|
||||
*/
|
||||
export default async (
|
||||
blob,
|
||||
options = {},
|
||||
existingHandle = null,
|
||||
throwIfExistingHandleNotGood = false
|
||||
) => {
|
||||
options.fileName = options.fileName || 'Untitled';
|
||||
const accept = {};
|
||||
if (options.mimeTypes) {
|
||||
options.mimeTypes.push(blob.type);
|
||||
options.mimeTypes.map((mimeType) => {
|
||||
accept[mimeType] = options.extensions || [];
|
||||
});
|
||||
console.log(accept,'heeeee')
|
||||
} else {
|
||||
accept[blob.type] = options.extensions || [];
|
||||
}
|
||||
if (existingHandle) {
|
||||
try {
|
||||
// Check if the file still exists.
|
||||
await existingHandle.getFile();
|
||||
} catch (err) {
|
||||
existingHandle = null;
|
||||
if (throwIfExistingHandleNotGood) {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(accept)
|
||||
const handle =
|
||||
existingHandle ||
|
||||
(await window.showSaveFilePicker({
|
||||
suggestedName: options.fileName,
|
||||
types: [
|
||||
{
|
||||
description: options.description || '',
|
||||
accept: accept,
|
||||
},
|
||||
],
|
||||
}));
|
||||
const writable = await handle.createWritable();
|
||||
await writable.write(blob);
|
||||
await writable.close();
|
||||
return handle;
|
||||
};
|
|
@ -0,0 +1,24 @@
|
|||
/**
|
||||
* Copyright 2020 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.
|
||||
*/
|
||||
// @license © 2020 Google LLC. Licensed under the Apache License, Version 2.0.
|
||||
|
||||
/**
|
||||
* @module browser-fs-access
|
||||
*/
|
||||
export { fileOpen } from './file-open.mjs';
|
||||
export { directoryOpen } from './directory-open.mjs';
|
||||
export { fileSave } from './file-save.mjs';
|
||||
export { default as supported } from './supported.mjs';
|
|
@ -0,0 +1,58 @@
|
|||
/**
|
||||
* Copyright 2020 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.
|
||||
*/
|
||||
// @license © 2020 Google LLC. Licensed under the Apache License, Version 2.0.
|
||||
|
||||
/**
|
||||
* Opens a directory from disk using the legacy
|
||||
* `<input type="file" webkitdirectory>` method.
|
||||
* @type { typeof import("../../index").directoryOpen }
|
||||
*/
|
||||
export default async (options = {}) => {
|
||||
options.recursive = options.recursive || false;
|
||||
return new Promise((resolve, reject) => {
|
||||
const input = document.createElement('input');
|
||||
input.type = 'file';
|
||||
input.webkitdirectory = true;
|
||||
|
||||
// ToDo: Remove this workaround once
|
||||
// https://github.com/whatwg/html/issues/6376 is specified and supported.
|
||||
const rejectOnPageInteraction = () => {
|
||||
window.removeEventListener('pointermove', rejectOnPageInteraction);
|
||||
window.removeEventListener('pointerdown', rejectOnPageInteraction);
|
||||
window.removeEventListener('keydown', rejectOnPageInteraction);
|
||||
reject(new DOMException('The user aborted a request.', 'AbortError'));
|
||||
};
|
||||
|
||||
window.addEventListener('pointermove', rejectOnPageInteraction);
|
||||
window.addEventListener('pointerdown', rejectOnPageInteraction);
|
||||
window.addEventListener('keydown', rejectOnPageInteraction);
|
||||
|
||||
input.addEventListener('change', () => {
|
||||
window.removeEventListener('pointermove', rejectOnPageInteraction);
|
||||
window.removeEventListener('pointerdown', rejectOnPageInteraction);
|
||||
window.removeEventListener('keydown', rejectOnPageInteraction);
|
||||
let files = Array.from(input.files);
|
||||
if (!options.recursive) {
|
||||
files = files.filter((file) => {
|
||||
return file.webkitRelativePath.split('/').length === 2;
|
||||
});
|
||||
}
|
||||
resolve(files);
|
||||
});
|
||||
|
||||
input.click();
|
||||
});
|
||||
};
|
|
@ -0,0 +1,56 @@
|
|||
/**
|
||||
* Copyright 2020 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.
|
||||
*/
|
||||
// @license © 2020 Google LLC. Licensed under the Apache License, Version 2.0.
|
||||
|
||||
/**
|
||||
* Opens a file from disk using the legacy `<input type="file">` method.
|
||||
* @type { typeof import("../../index").fileOpen }
|
||||
*/
|
||||
export default async (options = {}) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const input = document.createElement('input');
|
||||
input.type = 'file';
|
||||
const accept = [
|
||||
...(options.mimeTypes ? options.mimeTypes : []),
|
||||
options.extensions ? options.extensions : [],
|
||||
].join();
|
||||
input.multiple = options.multiple || false;
|
||||
// Empty string allows everything.
|
||||
input.accept = accept || '';
|
||||
|
||||
// ToDo: Remove this workaround once
|
||||
// https://github.com/whatwg/html/issues/6376 is specified and supported.
|
||||
const rejectOnPageInteraction = () => {
|
||||
window.removeEventListener('pointermove', rejectOnPageInteraction);
|
||||
window.removeEventListener('pointerdown', rejectOnPageInteraction);
|
||||
window.removeEventListener('keydown', rejectOnPageInteraction);
|
||||
reject(new DOMException('The user aborted a request.', 'AbortError'));
|
||||
};
|
||||
|
||||
window.addEventListener('pointermove', rejectOnPageInteraction);
|
||||
window.addEventListener('pointerdown', rejectOnPageInteraction);
|
||||
window.addEventListener('keydown', rejectOnPageInteraction);
|
||||
|
||||
input.addEventListener('change', () => {
|
||||
window.removeEventListener('pointermove', rejectOnPageInteraction);
|
||||
window.removeEventListener('pointerdown', rejectOnPageInteraction);
|
||||
window.removeEventListener('keydown', rejectOnPageInteraction);
|
||||
resolve(input.multiple ? input.files : input.files[0]);
|
||||
});
|
||||
|
||||
input.click();
|
||||
});
|
||||
};
|
|
@ -0,0 +1,32 @@
|
|||
/**
|
||||
* Copyright 2020 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.
|
||||
*/
|
||||
// @license © 2020 Google LLC. Licensed under the Apache License, Version 2.0.
|
||||
|
||||
/**
|
||||
* Saves a file to disk using the legacy `<a download>` method.
|
||||
* @type { typeof import("../../index").fileSave }
|
||||
*/
|
||||
export default async (blob, options = {}) => {
|
||||
const a = document.createElement('a');
|
||||
a.download = options.fileName || 'Untitled';
|
||||
a.href = URL.createObjectURL(blob);
|
||||
a.addEventListener('click', () => {
|
||||
// `setTimeout()` due to
|
||||
// https://github.com/LLK/scratch-gui/issues/1783#issuecomment-426286393
|
||||
setTimeout(() => URL.revokeObjectURL(a.href), 30 * 1000);
|
||||
});
|
||||
a.click();
|
||||
};
|
|
@ -0,0 +1,43 @@
|
|||
/**
|
||||
* Copyright 2020 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.
|
||||
*/
|
||||
// @license © 2020 Google LLC. Licensed under the Apache License, Version 2.0.
|
||||
|
||||
/**
|
||||
* Returns whether the File System Access API is supported and usable in the
|
||||
* current context (for example cross-origin iframes).
|
||||
* @returns {boolean} Returns `true` if the File System Access API is supported and usable, else returns `false`.
|
||||
*/
|
||||
const supported = (() => {
|
||||
// ToDo: Remove this check once Permissions Policy integration
|
||||
// has happened, tracked in
|
||||
// https://github.com/WICG/file-system-access/issues/245.
|
||||
if ('top' in self && self !== top) {
|
||||
try {
|
||||
// This will succeed on same-origin iframes,
|
||||
// but fail on cross-origin iframes.
|
||||
top.location + '';
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
} else if ('chooseFileSystemEntries' in self) {
|
||||
return 'chooseFileSystemEntries';
|
||||
} else if ('showOpenFilePicker' in self) {
|
||||
return 'showOpenFilePicker';
|
||||
}
|
||||
return false;
|
||||
})();
|
||||
|
||||
export default supported;
|
BIN
icon/icon-16.png
Before Width: | Height: | Size: 569 B After Width: | Height: | Size: 550 B |
BIN
icon/icon-24.png
Before Width: | Height: | Size: 793 B After Width: | Height: | Size: 743 B |
BIN
icon/icon-32.png
Before Width: | Height: | Size: 1014 B After Width: | Height: | Size: 937 B |
BIN
icon/icon-64.png
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
|
@ -32,8 +32,8 @@
|
|||
showgrid="false"
|
||||
inkscape:window-width="1514"
|
||||
inkscape:window-height="1199"
|
||||
inkscape:window-x="962"
|
||||
inkscape:window-y="154"
|
||||
inkscape:window-x="705"
|
||||
inkscape:window-y="83"
|
||||
inkscape:window-maximized="0"
|
||||
units="px" />
|
||||
<metadata
|
||||
|
@ -62,17 +62,17 @@
|
|||
id="g921"
|
||||
transform="translate(-36.094691,-12.655916)">
|
||||
<path
|
||||
style="fill:#34d399;fill-opacity:0.698039;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
style="fill:#10b981;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 51.316614,84.370759 164.499996,-95.000008 164.5,95.000008 -164.5,95.000041 z"
|
||||
id="path2578"
|
||||
sodipodi:nodetypes="ccccc" />
|
||||
<path
|
||||
style="fill:#34d399;fill-opacity:0.698039;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
style="fill:#10b981;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 401.16781,121 0.0224,189.96118 -164.52241,94.96118 -0.0224,-189.96118 z"
|
||||
id="path2578-3"
|
||||
sodipodi:nodetypes="ccccc" />
|
||||
<path
|
||||
style="fill:#34d399;fill-opacity:0.698039;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
style="fill:#10b981;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="M 194.52241,405.92236 30,310.96118 30.022413,121 194.54483,215.96118 Z"
|
||||
id="path2578-6"
|
||||
sodipodi:nodetypes="ccccc" />
|
||||
|
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
|
@ -1,17 +1,17 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
svg=icon2
|
||||
svg=./svgr_raw/logo.svg
|
||||
|
||||
size=(16 24 32 64)
|
||||
out=""
|
||||
for i in ${size[@]}; do
|
||||
inkscape --export-filename="./icon-$i.png" $svg.svg -w $i -h $i
|
||||
inkscape --export-filename="./icon-$i.png" $svg -w $i -h $i
|
||||
out+="icon-$i.png "
|
||||
done
|
||||
|
||||
size=(192 512)
|
||||
for i in ${size[@]}; do
|
||||
inkscape --export-filename="./icon-$i.png" $svg.svg -w $i -h $i
|
||||
inkscape --export-filename="./icon-$i.png" $svg -w $i -h $i
|
||||
done
|
||||
|
||||
convert $out favicon.ico
|
||||
|
|
|
@ -0,0 +1,135 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16.000001"
|
||||
version="1.1"
|
||||
id="svg2112"
|
||||
inkscape:version="1.0.2 (1.0.2+r75+1)"
|
||||
sodipodi:docname="coincident_alt.svg">
|
||||
<defs
|
||||
id="defs2106">
|
||||
<marker
|
||||
style="overflow:visible"
|
||||
id="TriangleOutS"
|
||||
refX="0.0"
|
||||
refY="0.0"
|
||||
orient="auto"
|
||||
inkscape:stockid="TriangleOutS"
|
||||
inkscape:isstock="true">
|
||||
<path
|
||||
transform="scale(0.2)"
|
||||
style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
d="M 5.77,0.0 L -2.88,5.0 L -2.88,-5.0 L 5.77,0.0 z "
|
||||
id="path1030" />
|
||||
</marker>
|
||||
<marker
|
||||
style="overflow:visible"
|
||||
id="TriangleInS"
|
||||
refX="0.0"
|
||||
refY="0.0"
|
||||
orient="auto"
|
||||
inkscape:stockid="TriangleInS"
|
||||
inkscape:isstock="true">
|
||||
<path
|
||||
transform="scale(-0.2)"
|
||||
style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;stroke-opacity:1;fill:#000000;fill-opacity:1"
|
||||
d="M 5.77,0.0 L -2.88,5.0 L -2.88,-5.0 L 5.77,0.0 z "
|
||||
id="path1021" />
|
||||
</marker>
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="15.367513"
|
||||
inkscape:cx="4.3157376"
|
||||
inkscape:cy="3.7157884"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="path895"
|
||||
inkscape:document-rotation="0"
|
||||
showgrid="false"
|
||||
inkscape:window-width="1514"
|
||||
inkscape:window-height="964"
|
||||
inkscape:window-x="908"
|
||||
inkscape:window-y="252"
|
||||
inkscape:window-maximized="0"
|
||||
units="px"
|
||||
inkscape:snap-object-midpoints="true"
|
||||
inkscape:snap-text-baseline="true"
|
||||
inkscape:snap-center="true" />
|
||||
<metadata
|
||||
id="metadata2109">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1">
|
||||
<rect
|
||||
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect1542-6-5-6-2"
|
||||
width="2"
|
||||
height="2"
|
||||
x="5.8333607"
|
||||
y="12.5" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:0.999332;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 1.0001028,13.500335 3.2497586,-6.69e-4"
|
||||
id="path835"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:1.0012;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 9.2528059,13.500594 5.7443881,0.02967"
|
||||
id="path835-3"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<g
|
||||
id="path895"
|
||||
style="opacity:1">
|
||||
<path
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1.0;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;stop-color:#000000;stop-opacity:1;"
|
||||
d="M 10.673828 2.5820312 C 9.0842346 2.6455782 7.8436243 3.3443712 7.1621094 4.4101562 C 6.4805944 5.4759413 6.2633682 6.7498004 6.0722656 8.1503906 L 8.0527344 8.4199219 C 8.2343271 7.0890291 8.4774586 6.0641593 8.8457031 5.4882812 C 9.2139477 4.9124032 9.612704 4.6256998 10.753906 4.5800781 L 10.673828 2.5820312 z "
|
||||
id="path1410" />
|
||||
<g
|
||||
id="g1400">
|
||||
<g
|
||||
id="path1402"
|
||||
style="opacity:1">
|
||||
<path
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.4pt;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1.0;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;stop-color:#000000;stop-opacity:1;"
|
||||
d="M 6.7505661,10.571607 5.2366904,6.8729886 9.1999682,7.4137553 Z"
|
||||
id="path1406" />
|
||||
<path
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1.0;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;stop-color:#000000;stop-opacity:1;"
|
||||
d="M 4.8144531 6.546875 L 6.6777344 11.099609 L 6.9609375 10.734375 L 9.6933594 7.2128906 L 4.8144531 6.546875 z M 5.6601562 7.1992188 L 8.7050781 7.6152344 L 6.8222656 10.042969 L 5.6601562 7.1992188 z "
|
||||
id="path1408" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<rect
|
||||
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="rect1542-6-5-6-2-3"
|
||||
width="2"
|
||||
height="2"
|
||||
x="12.5"
|
||||
y="2.5" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 8.5 KiB |
|
@ -32,8 +32,8 @@
|
|||
showgrid="false"
|
||||
inkscape:window-width="1514"
|
||||
inkscape:window-height="1199"
|
||||
inkscape:window-x="962"
|
||||
inkscape:window-y="154"
|
||||
inkscape:window-x="705"
|
||||
inkscape:window-y="83"
|
||||
inkscape:window-maximized="0"
|
||||
units="px" />
|
||||
<metadata
|
||||
|
@ -62,17 +62,17 @@
|
|||
id="g921"
|
||||
transform="translate(-36.094691,-12.655916)">
|
||||
<path
|
||||
style="fill:#34d399;fill-opacity:0.698039;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
style="fill:#10b981;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 51.316614,84.370759 164.499996,-95.000008 164.5,95.000008 -164.5,95.000041 z"
|
||||
id="path2578"
|
||||
sodipodi:nodetypes="ccccc" />
|
||||
<path
|
||||
style="fill:#34d399;fill-opacity:0.698039;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
style="fill:#10b981;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 401.16781,121 0.0224,189.96118 -164.52241,94.96118 -0.0224,-189.96118 z"
|
||||
id="path2578-3"
|
||||
sodipodi:nodetypes="ccccc" />
|
||||
<path
|
||||
style="fill:#34d399;fill-opacity:0.698039;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
style="fill:#10b981;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="M 194.52241,405.92236 30,310.96118 30.022413,121 194.54483,215.96118 Z"
|
||||
id="path2578-6"
|
||||
sodipodi:nodetypes="ccccc" />
|
||||
|
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
39
src/Scene.js
|
@ -16,7 +16,7 @@ let stats
|
|||
if (process.env.NODE_ENV !== 'production') {
|
||||
const { default: d } = require('../extlib/stats.module.js')
|
||||
stats = new d();
|
||||
document.getElementById('stats').appendChild(stats.dom);
|
||||
// document.getElementById('stats').appendChild(stats.dom);
|
||||
}
|
||||
|
||||
|
||||
|
@ -137,14 +137,15 @@ export class Scene {
|
|||
helpersGroup.add(light2);
|
||||
|
||||
|
||||
this.render = render.bind(this);
|
||||
this.addSketch = addSketch.bind(this);
|
||||
this.extrude = this.extrude.bind(this);
|
||||
this.onHover = onHover.bind(this);
|
||||
this.onPick = onPick.bind(this);
|
||||
this.clearSelection = clearSelection.bind(this);
|
||||
this.setHover = setHover.bind(this);
|
||||
this.awaitSelection = awaitSelection.bind(this);
|
||||
this.render = render.bind(this)
|
||||
this.addSketch = addSketch.bind(this)
|
||||
this.onHover = onHover.bind(this)
|
||||
this.onPick = onPick.bind(this)
|
||||
this.clearSelection = clearSelection.bind(this)
|
||||
this.setHover = setHover.bind(this)
|
||||
this.awaitSelection = awaitSelection.bind(this)
|
||||
this.extrude = this.extrude.bind(this)
|
||||
|
||||
|
||||
this.obj3d.addEventListener('change', this.render);
|
||||
this.controls.addEventListener('change', this.render);
|
||||
|
@ -153,20 +154,32 @@ export class Scene {
|
|||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
this.stats = stats
|
||||
this.stats.showPanel(0); // 0: fps, 1: ms, 2: mb, 3+: custom
|
||||
document.getElementById('stats').appendChild(this.stats.dom);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
this.hovered = [];
|
||||
this.selected = [];
|
||||
this.activeSketch = null;
|
||||
|
||||
this.selected = [];
|
||||
this.mode = '';
|
||||
this.store.subscribe(this.reduxCallback.bind(this))
|
||||
|
||||
this.render();
|
||||
}
|
||||
|
||||
reduxCallback() {
|
||||
const currSelected = this.store.getState().ui.selectedList
|
||||
const currMode = this.store.getState().ui.mode
|
||||
if (currSelected !== this.selected) {
|
||||
this.selected = currSelected
|
||||
}
|
||||
if (currMode !== this.mode) {
|
||||
this.mode = currMode
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
resizeCanvas(renderer) {
|
||||
|
|
180
src/Sketch.js
|
@ -2,7 +2,7 @@
|
|||
|
||||
import * as THREE from '../node_modules/three/src/Three';
|
||||
|
||||
import { _vec2, _vec3, raycaster, awaitSelection, ptObj, setHover,custPtMat } from './shared'
|
||||
import { _vec2, _vec3, raycaster, awaitSelection, ptObj, setHover, custPtMat } from './shared'
|
||||
|
||||
import { drawOnClick1, drawOnClick2, drawPreClick2, drawOnClick3, drawPreClick3, drawClear, drawPoint } from './drawEvents'
|
||||
import { onHover, onDrag, onPick, onRelease, clearSelection } from './mouseEvents'
|
||||
|
@ -47,10 +47,11 @@ class Sketch {
|
|||
this.constraints = new Map()
|
||||
this.c_id = 1;
|
||||
|
||||
this.obj3d.add(new THREE.Group());
|
||||
this.dimGroup = new THREE.Group()
|
||||
this.dimGroup.name = 'dimensions'
|
||||
this.obj3d.add(this.dimGroup);
|
||||
this.geomStartIdx = this.obj3d.children.length
|
||||
this.obj3d.userData.geomStartIdx = this.geomStartIdx
|
||||
this.dimGroup = this.obj3d.children[this.geomStartIdx - 1]
|
||||
|
||||
this.labels = []
|
||||
|
||||
|
@ -101,9 +102,11 @@ class Sketch {
|
|||
|
||||
this.bindHandlers()
|
||||
|
||||
this.selected = []
|
||||
// this.selected = this.scene.selected
|
||||
// this.selected = []
|
||||
|
||||
this.hovered = []
|
||||
this.mode = ""
|
||||
this.scene.mode = ""
|
||||
this.subsequent = false;
|
||||
}
|
||||
|
||||
|
@ -155,7 +158,7 @@ class Sketch {
|
|||
this.c_idOnActivate = this.c_id
|
||||
|
||||
const changeDetector = (e) => {
|
||||
if (this.selected.length && e.buttons) {
|
||||
if (this.scene.selected.length && e.buttons) {
|
||||
this.canvas.removeEventListener('pointermove', changeDetector)
|
||||
this.hasChanged = true
|
||||
}
|
||||
|
@ -230,93 +233,110 @@ class Sketch {
|
|||
|
||||
|
||||
onKeyPress(e) {
|
||||
this.command(e.key)
|
||||
|
||||
if (e.isTrusted && e.key == 'Escape') {
|
||||
drawClear.call(this)
|
||||
document.activeElement.blur()
|
||||
|
||||
this.scene.store.dispatch({ type: 'set-mode', mode: '' })
|
||||
} else {
|
||||
const keyToMode = {
|
||||
l: 'line',
|
||||
a: 'arc',
|
||||
p: 'point',
|
||||
d: 'dimension',
|
||||
c: 'coincident',
|
||||
v: 'vertical',
|
||||
h: 'horizontal',
|
||||
t: 'tangent',
|
||||
'Delete': 'delete',
|
||||
'Backspace': 'delete'
|
||||
}
|
||||
this.command(keyToMode[e.key])
|
||||
console.log(e.key)
|
||||
}
|
||||
}
|
||||
|
||||
command(key) {
|
||||
|
||||
switch (key) {
|
||||
case 'Escape':
|
||||
drawClear.call(this)
|
||||
document.activeElement.blur()
|
||||
break;
|
||||
case 'l':
|
||||
if (this.mode == 'line') {
|
||||
drawClear.call(this)
|
||||
}
|
||||
this.mode = "line"
|
||||
this.snap = true
|
||||
this.canvas.addEventListener('pointerdown', this.drawOnClick1, { once: true })
|
||||
break;
|
||||
case 'a':
|
||||
this.mode = "arc"
|
||||
this.snap = true
|
||||
this.canvas.addEventListener('pointerdown', this.drawOnClick1, { once: true })
|
||||
// this.canvas.addEventListener('pointerdown', this.drawOnClick1, { once: true })
|
||||
break;
|
||||
case 'p':
|
||||
this.mode = "point"
|
||||
this.snap = true
|
||||
this.canvas.addEventListener('pointerdown', (e) => {
|
||||
if (this.mode !== 'point') return
|
||||
const pt = ptObj()
|
||||
|
||||
pt.matrixAutoUpdate = false;
|
||||
pt.userData.constraints = []
|
||||
command(com) {
|
||||
drawClear.call(this)
|
||||
document.activeElement.blur()
|
||||
|
||||
pt.geometry.attributes.position.set(
|
||||
this.getLocation(e).toArray()
|
||||
)
|
||||
pt.layers.enable(2)
|
||||
let mode;
|
||||
|
||||
this.obj3d.add(pt)
|
||||
this.updatePointsBuffer(this.obj3d.children.length - 1)
|
||||
this.scene.render()
|
||||
})
|
||||
break;
|
||||
case 'd':
|
||||
if (this.mode != 'dimension') {
|
||||
drawClear.call(this)
|
||||
this.mode = "dimension"
|
||||
this.drawDimension()
|
||||
}
|
||||
break;
|
||||
case 'c':
|
||||
drawClear.call(this)
|
||||
setCoincident.call(this)
|
||||
break;
|
||||
case 'v':
|
||||
drawClear.call(this)
|
||||
setOrdinate.call(this, 0)
|
||||
break;
|
||||
case 'h':
|
||||
drawClear.call(this)
|
||||
setOrdinate.call(this, 1)
|
||||
break;
|
||||
case 't':
|
||||
drawClear.call(this)
|
||||
setTangent.call(this)
|
||||
break;
|
||||
case 'Delete':
|
||||
switch (com) {
|
||||
case 'delete':
|
||||
this.deleteSelected()
|
||||
break;
|
||||
case 'Backspace':
|
||||
this.deleteSelected()
|
||||
break;
|
||||
case 'z':
|
||||
console.log('undo would be nice')
|
||||
case 'coincident':
|
||||
case 'vertical':
|
||||
case 'horizontal':
|
||||
case 'tangent':
|
||||
window.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' }));
|
||||
case 'line':
|
||||
case 'arc':
|
||||
case 'point':
|
||||
case 'dimension':
|
||||
if (this.scene.mode == com) {
|
||||
mode = ''
|
||||
} else {
|
||||
mode = com
|
||||
|
||||
switch (com) {
|
||||
case 'line':
|
||||
case 'arc':
|
||||
this.canvas.addEventListener('pointerdown', this.drawOnClick1, { once: true });
|
||||
break;
|
||||
case 'point':
|
||||
this.canvas.addEventListener('pointerdown', (e) => {
|
||||
if (this.scene.mode !== 'point') return
|
||||
const pt = ptObj()
|
||||
|
||||
pt.matrixAutoUpdate = false;
|
||||
pt.userData.constraints = []
|
||||
|
||||
pt.geometry.attributes.position.set(
|
||||
this.getLocation(e).toArray()
|
||||
)
|
||||
pt.layers.enable(2)
|
||||
|
||||
this.obj3d.add(pt)
|
||||
this.updatePointsBuffer(this.obj3d.children.length - 1)
|
||||
this.scene.render()
|
||||
});
|
||||
break;
|
||||
case 'dimension':
|
||||
this.drawDimension();
|
||||
break;
|
||||
case 'coincident':
|
||||
case 'vertical':
|
||||
case 'horizontal':
|
||||
case 'tangent':
|
||||
|
||||
setCoincident.call(this).then(
|
||||
() => this.scene.store.dispatch({ type: 'set-mode', mode: "" })
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
// console.log('this mode:', this.mode)
|
||||
|
||||
if (mode !== undefined) {
|
||||
this.scene.store.dispatch({ type: 'set-mode', mode })
|
||||
}
|
||||
|
||||
// console.log('this mode:', this.scene.mode)
|
||||
}
|
||||
|
||||
deleteSelected() {
|
||||
|
||||
this.selected
|
||||
this.scene.selected
|
||||
.filter(e => e.userData.type == 'dimension')
|
||||
.forEach(e => this.constraints.has(e.name) && this.deleteConstraints(e.name))
|
||||
|
||||
const toDelete = this.selected
|
||||
const toDelete = this.scene.selected
|
||||
.filter(e => e.userData.type == 'line')
|
||||
.sort((a, b) => b.id - a.id)
|
||||
.map(obj => {
|
||||
|
@ -329,7 +349,10 @@ class Sketch {
|
|||
|
||||
this.updateOtherBuffers()
|
||||
|
||||
this.selected = []
|
||||
// this.selected = []
|
||||
|
||||
this.scene.store.dispatch({ type: 'clear-selection' })
|
||||
|
||||
this.scene.render()
|
||||
}
|
||||
|
||||
|
@ -608,9 +631,12 @@ Object.assign(Sketch.prototype,
|
|||
h_dist: 34,
|
||||
v_dist: 35,
|
||||
},
|
||||
|
||||
|
||||
max_pts: 1000,
|
||||
max_links: 1000,
|
||||
max_constraints: 1000,
|
||||
|
||||
}
|
||||
)
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { color, setHover } from './shared'
|
||||
|
||||
export async function setCoincident(sel) {
|
||||
let selection
|
||||
|
@ -37,12 +36,15 @@ export async function setCoincident(sel) {
|
|||
this.solve()
|
||||
this.updateBoundingSpheres()
|
||||
|
||||
for (let x = 0; x < this.selected.length; x++) {
|
||||
const obj = this.selected[x]
|
||||
setHover(obj, 0)
|
||||
}
|
||||
|
||||
this.selected = []
|
||||
this.clearSelection()
|
||||
// for (let x = 0; x < this.scene.selected.length; x++) {
|
||||
// const obj = this.selected[x]
|
||||
// setHover(obj, 0)
|
||||
// }
|
||||
|
||||
// this.selected = []
|
||||
|
||||
this.scene.render()
|
||||
}
|
||||
|
||||
|
@ -52,7 +54,7 @@ export async function setOrdinate(dir = 0) {
|
|||
if (selection == null) return;
|
||||
|
||||
let arr
|
||||
if (this.selected.length == 1) {
|
||||
if (selection.length == 1) {
|
||||
arr = [-1, -1, selection[0].name, -1]
|
||||
} else {
|
||||
arr = [selection[0].name, selection[1].name, -1, -1]
|
||||
|
@ -72,12 +74,8 @@ export async function setOrdinate(dir = 0) {
|
|||
this.updateOtherBuffers()
|
||||
this.solve()
|
||||
this.updateBoundingSpheres()
|
||||
for (let x = 0; x < this.selected.length; x++) {
|
||||
const obj = this.selected[x]
|
||||
setHover(obj, 0)
|
||||
}
|
||||
|
||||
this.selected = []
|
||||
this.clearSelection()
|
||||
this.scene.render()
|
||||
}
|
||||
|
||||
|
@ -156,12 +154,8 @@ export async function setTangent() {
|
|||
this.solve()
|
||||
this.updateBoundingSpheres()
|
||||
|
||||
for (let x = 0; x < this.selected.length; x++) {
|
||||
const obj = this.selected[x]
|
||||
setHover(obj, 0)
|
||||
}
|
||||
|
||||
this.selected = []
|
||||
this.clearSelection()
|
||||
|
||||
this.scene.render()
|
||||
}
|
||||
|
||||
|
|
|
@ -157,7 +157,7 @@ export async function drawDimension() {
|
|||
this.labelContainer.removeChild(this.labelContainer.lastChild);
|
||||
this.scene.render()
|
||||
}
|
||||
if (this.mode == "dimension") {
|
||||
if (this.scene.mode == "dimension") {
|
||||
this.drawDimension()
|
||||
}
|
||||
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
|
||||
import { drawArc, drawArc2, drawArc3, drawArc4 } from './drawArc'
|
||||
import { drawLine, drawLine2 } from './drawLine'
|
||||
import { ptObj } from './shared'
|
||||
|
||||
export function drawOnClick1(e, loc) {
|
||||
if (!loc && e.buttons !== 1) return
|
||||
export function drawOnClick1(e) {
|
||||
if (e.buttons !== 1) return
|
||||
|
||||
// this.canvas.removeEventListener('pointerdown', this.drawOnClick1)
|
||||
|
||||
|
@ -14,9 +13,7 @@ export function drawOnClick1(e, loc) {
|
|||
|
||||
let mouseLoc
|
||||
|
||||
if (loc) {
|
||||
mouseLoc = loc
|
||||
} else if (this.hovered.length && !this.subsequent) {
|
||||
if (this.hovered.length && !this.subsequent) {
|
||||
mouseLoc = this.hovered[this.hovered.length - 1].geometry.attributes.position.array
|
||||
} else {
|
||||
mouseLoc = this.getLocation(e).toArray();
|
||||
|
@ -24,8 +21,8 @@ export function drawOnClick1(e, loc) {
|
|||
|
||||
|
||||
|
||||
// this.mode allow alow following modes to create new obj3ds
|
||||
if (this.mode == "line") {
|
||||
// this.scene.mode allow alow following modes to create new obj3ds
|
||||
if (this.scene.mode == "line") {
|
||||
this.toPush = drawLine(mouseLoc)
|
||||
if (this.subsequent) {
|
||||
// we pre-increment because we need to push the same c_id to the constraints
|
||||
|
@ -41,7 +38,7 @@ export function drawOnClick1(e, loc) {
|
|||
this.toPush[0].userData.constraints.push(this.c_id)
|
||||
|
||||
}
|
||||
} else if (this.mode == "arc") {
|
||||
} else if (this.scene.mode == "arc") {
|
||||
this.toPush = drawArc(mouseLoc)
|
||||
}
|
||||
|
||||
|
@ -59,7 +56,7 @@ export function drawOnClick1(e, loc) {
|
|||
|
||||
this.updatePoint = this.obj3d.children.length
|
||||
this.obj3d.add(...this.toPush)
|
||||
this.linkedObjs.set(this.l_id, [this.mode, this.toPush.map(e => e.name)])
|
||||
this.linkedObjs.set(this.l_id, [this.scene.mode, this.toPush.map(e => e.name)])
|
||||
for (let obj of this.toPush) {
|
||||
obj.userData.l_id = this.l_id
|
||||
}
|
||||
|
@ -71,9 +68,9 @@ export function drawOnClick1(e, loc) {
|
|||
export function drawPreClick2(e) {
|
||||
const mouseLoc = this.getLocation(e).toArray();
|
||||
|
||||
if (this.mode == "line") {
|
||||
if (this.scene.mode == "line") {
|
||||
drawLine2(mouseLoc, this.toPush)
|
||||
} else if (this.mode == 'arc') {
|
||||
} else if (this.scene.mode == 'arc') {
|
||||
drawArc2(mouseLoc, this.toPush)
|
||||
}
|
||||
|
||||
|
@ -88,7 +85,7 @@ export function drawOnClick2(e) {
|
|||
this.updatePointsBuffer(this.updatePoint)
|
||||
this.updateOtherBuffers()
|
||||
|
||||
// a this.mode == "" (set with esc) here will prevent event chain from persisisting
|
||||
// a this.scene.mode == "" (set with esc) here will prevent event chain from persisisting
|
||||
|
||||
this.toPush.forEach(element => { // make sure elements are selectable by sketch raycaster
|
||||
element.layers.enable(2)
|
||||
|
@ -109,16 +106,21 @@ export function drawOnClick2(e) {
|
|||
modLoc = this.hovered[this.hovered.length - 1].geometry.attributes.position.array
|
||||
}
|
||||
|
||||
if (this.mode == "line") {
|
||||
this.subsequent = true
|
||||
if (this.scene.mode == "line") {
|
||||
if (modLoc) {
|
||||
drawLine2(modLoc, this.toPush)
|
||||
this.drawOnClick1(null, modLoc)
|
||||
// this.drawOnClick1(null, modLoc)
|
||||
|
||||
this.subsequent = false
|
||||
this.updatePoint = this.obj3d.children.length
|
||||
this.canvas.addEventListener('pointerdown', this.drawOnClick1, { once: true })
|
||||
|
||||
} else {
|
||||
this.subsequent = true
|
||||
this.drawOnClick1(e)
|
||||
}
|
||||
|
||||
} else if (this.mode == "arc") {
|
||||
} else if (this.scene.mode == "arc") {
|
||||
if (modLoc) {
|
||||
drawArc2(modLoc, this.toPush)
|
||||
}
|
||||
|
@ -134,6 +136,7 @@ export function drawOnClick2(e) {
|
|||
|
||||
let ccw;
|
||||
export function drawPreClick3(e) {
|
||||
this.noHover = true
|
||||
const mouseLoc = this.getLocation(e);
|
||||
ccw = drawArc4(mouseLoc, this.toPush)
|
||||
this.scene.render()
|
||||
|
@ -141,6 +144,7 @@ export function drawPreClick3(e) {
|
|||
|
||||
export function drawOnClick3(e) {
|
||||
if (e.buttons !== 1) return;
|
||||
this.noHover = false
|
||||
this.canvas.removeEventListener('pointermove', this.drawPreClick3);
|
||||
|
||||
if (!ccw) {
|
||||
|
@ -164,9 +168,8 @@ export function drawOnClick3(e) {
|
|||
|
||||
|
||||
export function drawClear() {
|
||||
if (this.mode == "") return
|
||||
|
||||
if (['line', 'arc'].includes(this.mode)) {
|
||||
if (this.scene.mode == "") return
|
||||
if (['line', 'arc'].includes(this.scene.mode)) {
|
||||
this.delete(this.obj3d.children[this.updatePoint])
|
||||
}
|
||||
|
||||
|
@ -179,9 +182,7 @@ export function drawClear() {
|
|||
this.scene.render()
|
||||
this.subsequent = false
|
||||
this.toPush = []
|
||||
this.snap = false
|
||||
|
||||
this.mode = ""
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -5,8 +5,7 @@ import { onDimMoveEnd } from './drawDimension'
|
|||
let ptLoc
|
||||
|
||||
export function onHover(e) {
|
||||
// if ((this.mode && this.mode != 'dimension' && !this.snap) || e.buttons) return
|
||||
if (e.buttons) return
|
||||
if (e.buttons || this.noHover) return
|
||||
|
||||
raycaster.setFromCamera(
|
||||
new THREE.Vector2(
|
||||
|
@ -46,13 +45,14 @@ export function onHover(e) {
|
|||
}
|
||||
}
|
||||
|
||||
if (!idx.length && !this.snap) {
|
||||
if (!idx.length) {
|
||||
idx.push(0)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
const selected = this.selected || this.scene.selected
|
||||
if (idx.length) { // after filtering, if hovered objs still exists
|
||||
if (
|
||||
!this.hovered.length
|
||||
|
@ -63,7 +63,7 @@ export function onHover(e) {
|
|||
for (let x = 0; x < this.hovered.length; x++) { // first clear old hovers that are not selected
|
||||
|
||||
const obj = this.hovered[x]
|
||||
if (typeof obj == 'object' && !this.selected.includes(obj)) {
|
||||
if (typeof obj == 'object' && !selected.includes(obj)) {
|
||||
setHover(obj, 0)
|
||||
}
|
||||
}
|
||||
|
@ -107,7 +107,7 @@ export function onHover(e) {
|
|||
|
||||
if (typeof obj == 'number') {
|
||||
this.selpoints[0].visible = false
|
||||
} else if (!this.selected.includes(obj)) {
|
||||
} else if (!selected.includes(obj)) {
|
||||
setHover(obj, 0)
|
||||
}
|
||||
|
||||
|
@ -124,13 +124,19 @@ export function onHover(e) {
|
|||
|
||||
let draggedLabel;
|
||||
export function onPick(e) {
|
||||
if ((this.mode && this.mode != 'dimension') || e.buttons != 1) return
|
||||
// console.log('aa',this.scene.mode)
|
||||
// if ((this.scene && this.scene.mode.length && this.scene.mode != 'dimension') || e.buttons != 1) return
|
||||
// console.log('bb')
|
||||
|
||||
if ((this.scene && ['line', 'arc'].includes(this.scene.mode)) || e.buttons != 1) return
|
||||
|
||||
const store = this.store || this.scene.store
|
||||
const selected = this.selected || this.scene.selected
|
||||
|
||||
if (this.hovered.length) {
|
||||
let obj = this.hovered[this.hovered.length - 1]
|
||||
// if (sc.selected.includes(obj3d)) continue
|
||||
|
||||
if (typeof obj != 'object') { // special sketchplace define pts in feature mode
|
||||
if (typeof obj != 'object') { // special case define pts in feature mode
|
||||
|
||||
const pp = this.selpoints[this.fptIdx % 3 + 1]
|
||||
const p0 = this.selpoints[0]
|
||||
|
@ -142,73 +148,68 @@ export function onPick(e) {
|
|||
|
||||
obj = pp
|
||||
this.fptIdx++
|
||||
}
|
||||
|
||||
|
||||
const idx = this.selected.indexOf(obj)
|
||||
|
||||
store.dispatch({ type: 'on-pick', obj })
|
||||
|
||||
|
||||
// if (idx == -1) {
|
||||
// this.selected.push(
|
||||
// obj
|
||||
// )
|
||||
// } else if (obj.userData.type != 'selpoint') {
|
||||
// this.selected.splice(idx, 1)
|
||||
// }
|
||||
|
||||
const idx = selected.indexOf(obj)
|
||||
|
||||
if (obj.userData.type != 'selpoint') {
|
||||
if (idx == -1) {
|
||||
this.selected.push(
|
||||
obj
|
||||
)
|
||||
} else {
|
||||
this.selected.splice(idx, 1, obj)
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
const idx = this.selected.indexOf(obj)
|
||||
if (idx == -1) {
|
||||
this.selected.push(
|
||||
obj
|
||||
)
|
||||
this.setHover(obj, 1)
|
||||
|
||||
} else {
|
||||
|
||||
this.setHover(this.selected[idx], 0)
|
||||
|
||||
this.selected.splice(idx, 1)
|
||||
|
||||
this.setHover(selected[idx], 0)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
this.obj3d.dispatchEvent({ type: 'change' })
|
||||
|
||||
if (this.obj3d.userData.type != 'sketch') {
|
||||
return;
|
||||
}
|
||||
if (this.obj3d.userData.type == 'sketch') {
|
||||
switch (obj.userData.type) {
|
||||
case 'dimension':
|
||||
const idx = this.dimGroup.children.indexOf(this.hovered[0])
|
||||
if (idx % 2) { // we only allow tag point (odd idx) to be dragged
|
||||
this.onDragDim = this._onMoveDimension(
|
||||
this.dimGroup.children[idx],
|
||||
this.dimGroup.children[idx - 1],
|
||||
)
|
||||
this.canvas.addEventListener('pointermove', this.onDragDim);
|
||||
this.canvas.addEventListener('pointerup', () => {
|
||||
onDimMoveEnd(this.dimGroup.children[idx])
|
||||
this.onRelease()
|
||||
})
|
||||
}
|
||||
|
||||
switch (obj.userData.type) {
|
||||
case 'dimension':
|
||||
const idx = this.dimGroup.children.indexOf(this.hovered[0])
|
||||
if (idx % 2) { // we only allow tag point (odd idx) to be dragged
|
||||
this.onDragDim = this._onMoveDimension(
|
||||
this.dimGroup.children[idx],
|
||||
this.dimGroup.children[idx - 1],
|
||||
)
|
||||
this.canvas.addEventListener('pointermove', this.onDragDim);
|
||||
this.canvas.addEventListener('pointerup', () => {
|
||||
onDimMoveEnd(this.dimGroup.children[idx])
|
||||
this.onRelease()
|
||||
})
|
||||
}
|
||||
draggedLabel = this.dimGroup.children[idx].label
|
||||
draggedLabel.style.zIndex = -1;
|
||||
break;
|
||||
case 'point':
|
||||
|
||||
draggedLabel = this.dimGroup.children[idx].label
|
||||
draggedLabel.style.zIndex = -1;
|
||||
break;
|
||||
case 'point':
|
||||
this.canvas.addEventListener('pointermove', this.onDrag);
|
||||
this.canvas.addEventListener('pointerup', this.onRelease)
|
||||
break;
|
||||
|
||||
this.canvas.addEventListener('pointermove', this.onDrag);
|
||||
this.canvas.addEventListener('pointerup', this.onRelease)
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
for (let x = 0; x < this.selected.length; x++) {
|
||||
const obj = this.selected[x]
|
||||
const vis = store.getState().treeEntries.visible
|
||||
for (let x = 0, obj; x < selected.length; x++) {
|
||||
obj = selected[x]
|
||||
|
||||
|
||||
if (obj.userData.type == 'selpoint') {
|
||||
|
@ -219,15 +220,14 @@ export function onPick(e) {
|
|||
}
|
||||
|
||||
// dont think this would have been possible without redux
|
||||
// if (obj.userData.type == 'sketch' && !sc.store.getState().treeEntries.visible[obj.name]) {
|
||||
if (obj.userData.type == 'sketch' && !this.scene.store.getState().treeEntries.visible[obj.name]) {
|
||||
if (obj.userData.type == 'sketch' && !vis[obj.name]) {
|
||||
obj.visible = false
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
store.dispatch({ type: 'clear-selection' })
|
||||
|
||||
this.obj3d.dispatchEvent({ type: 'change' })
|
||||
this.selected = []
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -267,15 +267,17 @@ export function onRelease(e) {
|
|||
}
|
||||
|
||||
export function clearSelection() {
|
||||
for (let x = 0, obj; x < this.selected.length; x++) {
|
||||
obj = this.selected[x]
|
||||
const selected = this.selected || this.scene.selected
|
||||
for (let x = 0, obj; x < selected.length; x++) {
|
||||
obj = selected[x]
|
||||
if (obj.userData.type == 'selpoint') {
|
||||
obj.visible = false
|
||||
} else {
|
||||
setHover(obj, 0)
|
||||
}
|
||||
}
|
||||
this.selected = []
|
||||
|
||||
store.dispatch({ type: 'clear-selection' })
|
||||
|
||||
for (let x = 0; x < this.hovered.length; x++) {
|
||||
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
scrollbar-color: lightgray #262626;
|
||||
scrollbar-width: thin;
|
||||
}
|
||||
|
||||
body {
|
||||
|
@ -61,7 +63,7 @@ body {
|
|||
cursor: pointer;
|
||||
@apply fill-current
|
||||
bg-transparent text-gray-200
|
||||
hover:text-green-400;
|
||||
hover:text-green-500;
|
||||
}
|
||||
|
||||
|
||||
|
@ -95,3 +97,21 @@ input[type=number] {
|
|||
.drop-down-top {
|
||||
top: calc(var(--topNavH) + 6px);
|
||||
}
|
||||
|
||||
|
||||
.hide-scroll{
|
||||
scrollbar-width: none; /* Firefox */
|
||||
}
|
||||
|
||||
.hide-scroll::-webkit-scrollbar {
|
||||
display: none; /* Safari and Chrome */
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 0.375rem;
|
||||
background: #262626;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: lightgray;
|
||||
}
|
||||
|
|
|
@ -42,7 +42,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||
|
||||
const { Scene } = await import('../Scene')
|
||||
sce = new Scene(store)
|
||||
// window.sc = sce
|
||||
window.sc = sce
|
||||
|
||||
ReactDOM.render(<App store={store} />, document.getElementById('react'));
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ import { useDispatch, useSelector } from 'react-redux'
|
|||
import { MdDone, MdClose } from 'react-icons/md'
|
||||
import * as Icon from "./icons";
|
||||
|
||||
import {sce} from './app'
|
||||
import { sce } from './app'
|
||||
|
||||
|
||||
export const Dialog = () => {
|
||||
|
@ -17,19 +17,26 @@ export const Dialog = () => {
|
|||
|
||||
const ref = useRef()
|
||||
|
||||
const target = treeEntries.byId[dialog.target]
|
||||
|
||||
// console.log(dialog, treeEntries)
|
||||
|
||||
useEffect(() => {
|
||||
if (!ref.current) return
|
||||
|
||||
|
||||
|
||||
ref.current.focus()
|
||||
}, [dialog])
|
||||
|
||||
const extrude = () => {
|
||||
const mesh = sce.extrude(dialog.target, ref.current.value)
|
||||
const mesh = sce.extrude(target, ref.current.value)
|
||||
|
||||
dispatch({ type: 'rx-extrusion', mesh, sketchId: dialog.target.obj3d.name })
|
||||
dispatch({ type: 'rx-extrusion', mesh, sketchId: target.obj3d.name })
|
||||
|
||||
if (sce.activeSketch == dialog.target) {
|
||||
if (sce.activeSketch == target) {
|
||||
dispatch({ type: 'finish-sketch' })
|
||||
dialog.target.deactivate()
|
||||
target.deactivate()
|
||||
}
|
||||
|
||||
dispatch({ type: "clear-dialog" })
|
||||
|
@ -38,7 +45,7 @@ export const Dialog = () => {
|
|||
}
|
||||
|
||||
const extrudeCancel = () => {
|
||||
if (sce.activeSketch == dialog.target) { // if extrude dialog launched from sketch mode we set dialog back to the sketch dialog
|
||||
if (sce.activeSketch == target) { // if extrude dialog launched from sketch mode we set dialog back to the sketch dialog
|
||||
dispatch({ type: 'set-dialog', action: 'sketch' })
|
||||
} else {
|
||||
dispatch({ type: "clear-dialog" })
|
||||
|
@ -46,9 +53,9 @@ export const Dialog = () => {
|
|||
}
|
||||
|
||||
const extrudeEdit = () => {
|
||||
dialog.target.userData.featureInfo[1] = ref.current.value
|
||||
target.userData.featureInfo[1] = ref.current.value
|
||||
|
||||
sce.refreshNode(dialog.target.name, treeEntries)
|
||||
sce.refreshNode(target.name, treeEntries)
|
||||
dispatch({ type: 'set-modified', status: true })
|
||||
|
||||
dispatch({ type: "clear-dialog" })
|
||||
|
@ -76,17 +83,14 @@ export const Dialog = () => {
|
|||
}
|
||||
|
||||
const sketchCancel = () => {
|
||||
console.log(sce.activeSketch.hasChanged)
|
||||
if (sce.activeSketch.hasChanged
|
||||
if (sce.newSketch) {
|
||||
dispatch({ type: 'delete-node', id: sce.activeSketch.obj3d.name })
|
||||
sce.sid -= 1
|
||||
} else if (sce.activeSketch.hasChanged
|
||||
|| sce.activeSketch.idOnActivate != id
|
||||
|| sce.activeSketch.c_idOnActivate != sce.activeSketch.c_id
|
||||
) {
|
||||
if (sce.newSketch) {
|
||||
dispatch({ type: 'delete-node', id: sce.activeSketch.obj3d.name })
|
||||
sce.sid -= 1
|
||||
} else {
|
||||
dispatch({ type: "restore-sketch" })
|
||||
}
|
||||
dispatch({ type: "restore-sketch" })
|
||||
}
|
||||
|
||||
dispatch({ type: 'finish-sketch' })
|
||||
|
@ -113,7 +117,7 @@ export const Dialog = () => {
|
|||
</>
|
||||
case 'extrude-edit':
|
||||
return <>
|
||||
<input className='w-10 border-t-0 border-l-0 border-r-0 border-b border-gray-50 text-gray-50' type="number" defaultValue={dialog.target.userData.featureInfo[1]} step="0.1" ref={ref} />
|
||||
<input className='w-10 border-t-0 border-l-0 border-r-0 border-b border-gray-50 text-gray-50' type="number" defaultValue={target.userData.featureInfo[1]} step="0.1" ref={ref} />
|
||||
<Icon.Flip className="btn text-gray-200 w-auto h-full p-3.5"
|
||||
onClick={() => ref.current.value *= -1}
|
||||
/>
|
||||
|
@ -121,7 +125,7 @@ export const Dialog = () => {
|
|||
className="btn w-auto h-full p-3.5 text-green-500"
|
||||
onClick={extrudeEdit}
|
||||
/>
|
||||
<MdClose
|
||||
<MdClose
|
||||
className="btn w-auto h-full p-3.5 text-red-500"
|
||||
onClick={extrudeEditCancel}
|
||||
/>
|
||||
|
|
|
@ -10,7 +10,7 @@ const utf8decoder = new TextDecoder();
|
|||
|
||||
export const DropDown = () => {
|
||||
const arr = [
|
||||
['https://raw.githubusercontent.com/twpride/threeCAD/master/demo_parts/headphones-stand.json.gz', 'headphones stand'],
|
||||
['https://raw.githubusercontent.com/twpride/threeCAD/master/demo_parts/headphones-stand.json.gz', 'headphones-stand'],
|
||||
]
|
||||
|
||||
const dispatch = useDispatch()
|
||||
|
@ -53,7 +53,7 @@ export const DropDown = () => {
|
|||
)
|
||||
)
|
||||
|
||||
dispatch({ type: 'restore-state', state })
|
||||
dispatch({ type: 'restore-state', state, fileName:arr[idx][1]})
|
||||
sce.render()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
|
||||
|
||||
// import {
|
||||
// fileOpen,
|
||||
// fileSave,
|
||||
// } from '../../extlib/fs/index';
|
||||
|
||||
import {
|
||||
fileOpen,
|
||||
fileSave,
|
||||
} from 'browser-fs-access';
|
||||
} from '../../extlib/fs/index';
|
||||
|
||||
import {sce} from './app'
|
||||
// import {
|
||||
// fileOpen,
|
||||
// fileSave,
|
||||
// } from 'browser-fs-access';
|
||||
|
||||
import { sce } from './app'
|
||||
|
||||
// https://web.dev/file-system-access/
|
||||
|
||||
|
@ -32,13 +32,14 @@ export function STLExport(filename) {
|
|||
}
|
||||
|
||||
|
||||
export async function saveFile(fileHandle, file, dispatch) {
|
||||
export async function saveFile(fileHandle, file, dispatch, suggestedName) {
|
||||
try {
|
||||
if (!fileHandle) {
|
||||
return await saveFileAs(file, dispatch);
|
||||
return await saveFileAs(file, dispatch, suggestedName);
|
||||
}
|
||||
|
||||
await fileSave(new Blob([file], { type: 'application/json' }), undefined, fileHandle, true)
|
||||
const blob = new Blob([file], { type: 'application/json' })
|
||||
await fileSave(blob, undefined, fileHandle, true)
|
||||
|
||||
dispatch({ type: 'set-modified', status: false })
|
||||
} catch (ex) {
|
||||
|
@ -48,12 +49,22 @@ export async function saveFile(fileHandle, file, dispatch) {
|
|||
}
|
||||
};
|
||||
|
||||
export async function saveFileAs(file, dispatch) {
|
||||
const options = {
|
||||
mimeTypes: ['application/json'],
|
||||
extensions: ['.json'],
|
||||
multiple: false,
|
||||
description: 'Part files',
|
||||
};
|
||||
|
||||
export async function saveFileAs(file, dispatch, suggestedName) {
|
||||
|
||||
try {
|
||||
const fileHandle = await fileSave(new Blob([file], { type: 'application/json' }), {
|
||||
extensions: ['.json'],
|
||||
})
|
||||
|
||||
const blob = new Blob([file], { type: 'application/json' })
|
||||
|
||||
options.fileName = suggestedName + options.extensions[0]
|
||||
|
||||
const fileHandle = await fileSave(blob, options)
|
||||
|
||||
dispatch({ type: 'set-file-handle', fileHandle, modified: false })
|
||||
|
||||
|
@ -73,13 +84,7 @@ export async function openFile(dispatch) {
|
|||
|
||||
try {
|
||||
|
||||
const options = {
|
||||
mimeTypes: ['application/json'],
|
||||
extensions: ['.json'],
|
||||
multiple: false,
|
||||
description: 'Part files',
|
||||
};
|
||||
|
||||
|
||||
file = await fileOpen(options);
|
||||
|
||||
} catch (ex) {
|
||||
|
@ -92,10 +97,13 @@ export async function openFile(dispatch) {
|
|||
}
|
||||
|
||||
try {
|
||||
const text = await file.text();;
|
||||
const text = await file.text();
|
||||
console.log(file, file.handle)
|
||||
|
||||
dispatch({ type: 'restore-state', state: sce.loadState(text) })
|
||||
dispatch({ type: 'set-file-handle', fileHandle:file.handle })
|
||||
dispatch({ type: 'restore-state', state: sce.loadState(text), fileName: file.name })
|
||||
if (file.handle) {
|
||||
dispatch({ type: 'set-file-handle', fileHandle: file.handle })
|
||||
}
|
||||
|
||||
} catch (ex) {
|
||||
const msg = `An error occured reading ${fileHandle}`;
|
||||
|
|
|
@ -53,6 +53,137 @@ function Coincident(props) {
|
|||
);
|
||||
}
|
||||
|
||||
function Coincident_alt(props) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="1em"
|
||||
height="1em"
|
||||
viewBox="0 0 16 16"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
d="M5.833 12.5h2v2h-2z"
|
||||
/>
|
||||
<path
|
||||
d="M1 13.5h3.25"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth={0.999}
|
||||
/>
|
||||
<path
|
||||
d="M9.253 13.5l5.744.03"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth={1.001}
|
||||
/>
|
||||
<path
|
||||
style={{
|
||||
lineHeight: "normal",
|
||||
fontVariantLigatures: "normal",
|
||||
fontVariantPosition: "normal",
|
||||
fontVariantCaps: "normal",
|
||||
fontVariantNumeric: "normal",
|
||||
fontVariantAlternates: "normal",
|
||||
fontVariantEastAsian: "normal",
|
||||
fontFeatureSettings: "normal",
|
||||
fontVariationSettings: "normal",
|
||||
textIndent: 0,
|
||||
textAlign: "start",
|
||||
textDecorationLine: "none",
|
||||
textDecorationStyle: "solid",
|
||||
textDecorationColor: "#000",
|
||||
textTransform: "none",
|
||||
textOrientation: "mixed",
|
||||
whiteSpace: "normal",
|
||||
shapePadding: 0,
|
||||
shapeMargin: 0,
|
||||
inlineSize: 0,
|
||||
isolation: "auto",
|
||||
mixBlendMode: "normal",
|
||||
solidColor: "#000",
|
||||
solidOpacity: 1,
|
||||
}}
|
||||
d="M10.674 2.582c-1.59.064-2.83.762-3.512 1.828-.681 1.066-.899 2.34-1.09 3.74l1.98.27c.182-1.331.425-2.356.794-2.932.368-.576.767-.862 1.908-.908l-.08-1.998z"
|
||||
color="currentColor"
|
||||
fontWeight={400}
|
||||
fontFamily="sans-serif"
|
||||
overflow="visible"
|
||||
/>
|
||||
<g
|
||||
color="currentColor"
|
||||
fontWeight={400}
|
||||
fontFamily="sans-serif"
|
||||
fillRule="evenodd"
|
||||
>
|
||||
<path
|
||||
style={{
|
||||
lineHeight: "normal",
|
||||
fontVariantLigatures: "normal",
|
||||
fontVariantPosition: "normal",
|
||||
fontVariantCaps: "normal",
|
||||
fontVariantNumeric: "normal",
|
||||
fontVariantAlternates: "normal",
|
||||
fontVariantEastAsian: "normal",
|
||||
fontFeatureSettings: "normal",
|
||||
fontVariationSettings: "normal",
|
||||
textIndent: 0,
|
||||
textAlign: "start",
|
||||
textDecorationLine: "none",
|
||||
textDecorationStyle: "solid",
|
||||
textDecorationColor: "#000",
|
||||
textTransform: "none",
|
||||
textOrientation: "mixed",
|
||||
whiteSpace: "normal",
|
||||
shapePadding: 0,
|
||||
shapeMargin: 0,
|
||||
inlineSize: 0,
|
||||
isolation: "auto",
|
||||
mixBlendMode: "normal",
|
||||
solidColor: "#000",
|
||||
solidOpacity: 1,
|
||||
}}
|
||||
d="M6.75 10.572L5.238 6.873l3.963.54z"
|
||||
overflow="visible"
|
||||
/>
|
||||
<path
|
||||
style={{
|
||||
lineHeight: "normal",
|
||||
fontVariantLigatures: "normal",
|
||||
fontVariantPosition: "normal",
|
||||
fontVariantCaps: "normal",
|
||||
fontVariantNumeric: "normal",
|
||||
fontVariantAlternates: "normal",
|
||||
fontVariantEastAsian: "normal",
|
||||
fontFeatureSettings: "normal",
|
||||
fontVariationSettings: "normal",
|
||||
textIndent: 0,
|
||||
textAlign: "start",
|
||||
textDecorationLine: "none",
|
||||
textDecorationStyle: "solid",
|
||||
textDecorationColor: "#000",
|
||||
textTransform: "none",
|
||||
textOrientation: "mixed",
|
||||
whiteSpace: "normal",
|
||||
shapePadding: 0,
|
||||
shapeMargin: 0,
|
||||
inlineSize: 0,
|
||||
isolation: "auto",
|
||||
mixBlendMode: "normal",
|
||||
solidColor: "#000",
|
||||
solidOpacity: 1,
|
||||
}}
|
||||
d="M4.814 6.547L6.678 11.1l.283-.366 2.732-3.521-4.879-.666zm.846.652l3.045.416-1.883 2.428L5.66 7.199z"
|
||||
overflow="visible"
|
||||
/>
|
||||
</g>
|
||||
<path stroke="currentColor" strokeLinecap="round" d="M12.5 2.5h2v2h-2z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
function Dimension(props) {
|
||||
return (
|
||||
<svg
|
||||
|
@ -307,7 +438,7 @@ function Logo(props) {
|
|||
viewBox="0 0 512 512"
|
||||
{...props}
|
||||
>
|
||||
<g fill="#34d399" fillOpacity={0.698}>
|
||||
<g fill="#10b981">
|
||||
<path d="M73.722 130.313l182.524-105.41 182.524 105.41-182.524 105.409zM461.905 170.955l.025 210.775-182.548 105.366-.025-210.775zM232.618 487.096L50.07 381.73l.025-210.775 182.548 105.366z" />
|
||||
</g>
|
||||
</svg>
|
||||
|
@ -475,4 +606,4 @@ function Vertical(props) {
|
|||
</svg>
|
||||
);
|
||||
}
|
||||
export { Arc, Coincident, Dimension, Extrude, Flip, Horizontal, Intersect, Line, Logo, Stl, Subtract, Tangent, Union, Vertical };
|
||||
export { Arc, Coincident, Coincident_alt, Dimension, Extrude, Flip, Horizontal, Intersect, Line, Logo, Stl, Subtract, Tangent, Union, Vertical };
|
|
@ -10,16 +10,32 @@ import { MdSave, MdFolder, MdInsertDriveFile } from 'react-icons/md'
|
|||
import * as Icon from "./icons";
|
||||
import { Dialog } from './dialog'
|
||||
import { DropDown } from './dropDown'
|
||||
import { STLExport, saveFile, openFile, verifyPermission } from './fileHelpers'
|
||||
import { STLExport, saveFile, openFile } from './fileHelpers'
|
||||
|
||||
import { drawClear } from '../drawEvents'
|
||||
|
||||
import { sce } from './app'
|
||||
|
||||
|
||||
|
||||
const buttonIdx = {
|
||||
'line': 1,
|
||||
'arc': 2,
|
||||
'dimension': 3,
|
||||
'coincident': 4,
|
||||
'vertical': 5,
|
||||
'horizontal': 6,
|
||||
'tangent': 7,
|
||||
}
|
||||
|
||||
export const NavBar = () => {
|
||||
const dispatch = useDispatch()
|
||||
const sketchActive = useSelector(state => state.ui.sketchActive)
|
||||
const treeEntries = useSelector(state => state.treeEntries)
|
||||
const sketchActive = useSelector(state => state.ui.sketchActive)
|
||||
const fileHandle = useSelector(state => state.ui.fileHandle)
|
||||
const modified = useSelector(state => state.ui.modified)
|
||||
const fileName = useSelector(state => state.ui.fileName)
|
||||
const mode = useSelector(state => state.ui.mode)
|
||||
|
||||
const boolOp = (code) => {
|
||||
if (sce.selected.length != 2 || !sce.selected.every(e => e.userData.type == 'mesh')) {
|
||||
|
@ -62,8 +78,9 @@ export const NavBar = () => {
|
|||
sketch.activate()
|
||||
|
||||
sce.render()
|
||||
console.log(sketch)
|
||||
|
||||
dispatch({ type: 'set-dialog', action: 'sketch' })
|
||||
dispatch({ type: 'set-dialog', action: 'sketch', target: sketch.obj3d.name })
|
||||
|
||||
forceUpdate()
|
||||
}
|
||||
|
@ -105,20 +122,19 @@ export const NavBar = () => {
|
|||
|
||||
const sketchModeButtons = [
|
||||
[Icon.Extrude, () => {
|
||||
dispatch({ type: 'set-dialog', action: 'extrude', target: sce.activeSketch })
|
||||
|
||||
drawClear.call(sce.activeSketch)
|
||||
dispatch({ type: 'set-dialog', action: 'extrude', target: sce.activeSketch.obj3d.name })
|
||||
}, 'Extrude'],
|
||||
[Icon.Line, () => sce.activeSketch.command('l'), 'Line (L)'],
|
||||
[Icon.Arc, () => sce.activeSketch.command('a'), 'Arc (A)'],
|
||||
[Icon.Dimension, () => sce.activeSketch.command('d'), 'Dimension (D)'],
|
||||
[Icon.Coincident, () => sce.activeSketch.command('c'), 'Coincident (C)'],
|
||||
[Icon.Vertical, () => sce.activeSketch.command('v'), 'Vertical (V)'],
|
||||
[Icon.Horizontal, () => sce.activeSketch.command('h'), 'Horizontal (H)'],
|
||||
[Icon.Tangent, () => sce.activeSketch.command('t'), 'Tangent (T)'],
|
||||
[Icon.Line, () => sce.activeSketch.command('line'), 'Line (L)'], //1
|
||||
[Icon.Arc, () => sce.activeSketch.command('arc'), 'Arc (A)'],
|
||||
[Icon.Dimension, () => sce.activeSketch.command('dimension'), 'Dimension (D)'],
|
||||
[Icon.Coincident_alt, () => sce.activeSketch.command('coincident'), 'Coincident (C)'],
|
||||
[Icon.Vertical, () => sce.activeSketch.command('vertical'), 'Vertical (V)'],
|
||||
[Icon.Horizontal, () => sce.activeSketch.command('horizontal'), 'Horizontal (H)'],
|
||||
[Icon.Tangent, () => sce.activeSketch.command('tangent'), 'Tangent (T)'], //7
|
||||
[MdSave,
|
||||
async () => {
|
||||
saveFile(fileHandle, JSON.stringify([id, sce.sid, sce.mid, treeEntries]), dispatch)
|
||||
// saveFile(fileHandle, bson.serialize([id, sce.sid, sce.mid, treeEntries]), dispatch)
|
||||
saveFile(fileHandle, JSON.stringify([id, sce.sid, sce.mid, treeEntries]), dispatch, fileName)
|
||||
}
|
||||
, 'Save'],
|
||||
]
|
||||
|
@ -128,7 +144,7 @@ export const NavBar = () => {
|
|||
[FaEdit, addSketch, 'Sketch'],
|
||||
[Icon.Extrude, () => {
|
||||
try {
|
||||
dispatch({ type: 'set-dialog', action: 'extrude', target: treeEntries.byId[sce.selected[0].name] })
|
||||
dispatch({ type: 'set-dialog', action: 'extrude', target: sce.selected[0].name })
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
alert('please select a sketch from the left pane extrude')
|
||||
|
@ -147,8 +163,7 @@ export const NavBar = () => {
|
|||
}, 'New'],
|
||||
[MdSave,
|
||||
() => {
|
||||
saveFile(fileHandle, JSON.stringify([id, sce.sid, sce.mid, treeEntries]), dispatch)
|
||||
// saveFile(fileHandle, bson.serialize([id, sce.sid, sce.mid, treeEntries.toJson()]), dispatch)
|
||||
saveFile(fileHandle, JSON.stringify([id, sce.sid, sce.mid, treeEntries]), dispatch, fileName)
|
||||
}
|
||||
, 'Save'],
|
||||
[MdFolder, () => {
|
||||
|
@ -170,7 +185,6 @@ export const NavBar = () => {
|
|||
|
||||
return <div className='topNav flex justify-center items-center bg-gray-800'>
|
||||
|
||||
{/* <div className='w-auto h-full flex-1 flex items-center justify-end'> */}
|
||||
<div className='w-auto h-full flex-1 flex items-center justify-end md:justify-between'>
|
||||
<div className='w-100 h-full items-center font-mono text-lg text-gray-200 select-none hidden lg:flex mr-8'>
|
||||
<Icon.Logo className='w-auto h-6 mx-1' />
|
||||
|
@ -184,7 +198,8 @@ export const NavBar = () => {
|
|||
{(sketchActive ? sketchModeButtons : partModeButtons).map(
|
||||
([Icon, fcn, txt], idx) => (
|
||||
Icon !== undefined ?
|
||||
<Icon className="btn text-gray-200 w-auto h-full p-3.5" tooltip={txt}
|
||||
<Icon className={`cursor-pointer fill-current text-gray-200 w-auto h-full p-3.5
|
||||
${idx == buttonIdx[mode] ? 'bg-green-600' : 'hover:bg-gray-600 bg-transparent'}`} tooltip={txt}
|
||||
onClick={fcn} key={idx}
|
||||
/> :
|
||||
<div className="w-12 h-full"></div>
|
||||
|
|
|
@ -5,7 +5,7 @@ import update from 'immutability-helper'
|
|||
import { combineReducers } from 'redux';
|
||||
import { sce } from './app'
|
||||
|
||||
const defaultState = {
|
||||
const defaultTreeState = {
|
||||
byId: {},
|
||||
allIds: [],
|
||||
tree: {},
|
||||
|
@ -15,7 +15,7 @@ const defaultState = {
|
|||
|
||||
let cache
|
||||
|
||||
export function treeEntries(state = defaultState, action) {
|
||||
export function treeEntries(state = defaultTreeState, action) {
|
||||
switch (action.type) {
|
||||
case 'rx-sketch':
|
||||
return update(state, {
|
||||
|
@ -96,13 +96,22 @@ export function treeEntries(state = defaultState, action) {
|
|||
case 'restore-state':
|
||||
return action.state
|
||||
case 'new-part':
|
||||
return defaultState
|
||||
return defaultTreeState
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
export function ui(state = { dialog: {}, filePane: false }, action) {
|
||||
const defaultUIState = {
|
||||
dialog: {},
|
||||
fileHandle: null,
|
||||
fileName: 'Untitled',
|
||||
selectedList: [],
|
||||
selectedSet: {},
|
||||
|
||||
}
|
||||
|
||||
export function ui(state = defaultUIState, action) {
|
||||
switch (action.type) {
|
||||
case 'set-active-sketch':
|
||||
return update(state, {
|
||||
|
@ -119,10 +128,12 @@ export function ui(state = { dialog: {}, filePane: false }, action) {
|
|||
case 'set-dialog':
|
||||
return update(state, {
|
||||
dialog: { $set: { target: action.target, action: action.action } },
|
||||
mode: { $set: "" } // we clear the existing mode when entering dialog
|
||||
})
|
||||
case 'clear-dialog':
|
||||
return update(state, {
|
||||
dialog: { $set: {} },
|
||||
mode: { $set: "" }
|
||||
})
|
||||
case 'set-file-handle':
|
||||
return update(state, {
|
||||
|
@ -130,10 +141,7 @@ export function ui(state = { dialog: {}, filePane: false }, action) {
|
|||
modified: { $set: false },
|
||||
})
|
||||
case 'new-part':
|
||||
return update(state, {
|
||||
fileHandle: { $set: null },
|
||||
modified: { $set: false },
|
||||
})
|
||||
return defaultUIState
|
||||
case 'set-modified':
|
||||
return update(state, {
|
||||
modified: { $set: action.status },
|
||||
|
@ -146,6 +154,55 @@ export function ui(state = { dialog: {}, filePane: false }, action) {
|
|||
return update(state, {
|
||||
modified: { $set: true },
|
||||
})
|
||||
case 'restore-state':
|
||||
return update(state, {
|
||||
fileName: { $set: action.fileName },
|
||||
})
|
||||
case 'on-pick':
|
||||
|
||||
console.log(action.obj.userData.type)
|
||||
const idx = state.selectedList.indexOf(action.obj)
|
||||
|
||||
const setNeedsUpdate = action.obj.userData.type == 'mesh' || action.obj.userData.type == 'sketch'
|
||||
|
||||
if (idx == -1) {
|
||||
return update(state, {
|
||||
selectedList: { $push: [action.obj] },
|
||||
// selectedSet: { [action.obj.name]: { $set: true } }
|
||||
selectedSet: (curr) => setNeedsUpdate ? { ...curr, [action.obj.name]: true } : curr
|
||||
})
|
||||
|
||||
} else {
|
||||
|
||||
if (action.obj.userData.type != 'selpoint') {
|
||||
return update(state, {
|
||||
selectedList: { $splice: [[idx, 1]] },
|
||||
// selectedSet: { [action.obj.name]: { $set: false } }
|
||||
selectedSet: (curr) => setNeedsUpdate ? { ...curr, [action.obj.name]: false } : curr
|
||||
})
|
||||
} else {
|
||||
return state
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
case 'clear-selection':
|
||||
if (state.selectedList.length) {
|
||||
return update(state, {
|
||||
selectedList: { $set: [] },
|
||||
selectedSet: { $set: {} }
|
||||
})
|
||||
} else {
|
||||
return state
|
||||
}
|
||||
|
||||
case 'set-mode':
|
||||
|
||||
|
||||
return update(state, {
|
||||
mode: { $set: action.mode }
|
||||
})
|
||||
|
||||
default:
|
||||
return state
|
||||
}
|
||||
|
|
|
@ -2,23 +2,28 @@
|
|||
|
||||
import React, { useReducer, useState } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import { MdVisibilityOff, MdVisibility, MdDelete } from 'react-icons/md'
|
||||
|
||||
import { FaCube, FaEdit } from 'react-icons/fa'
|
||||
import { MdEdit, MdVisibility, MdDelete } from 'react-icons/md'
|
||||
import { FaCube, FaDrawPolygon } from 'react-icons/fa'
|
||||
|
||||
import { sce } from './app'
|
||||
|
||||
export const Tree = () => {
|
||||
const treeEntries = useSelector(state => state.treeEntries)
|
||||
const fileHandle = useSelector(state => state.ui.fileHandle)
|
||||
const fileName = useSelector(state => state.ui.fileName)
|
||||
|
||||
return <div className='sideNav flex flex-col bg-gray-800'>
|
||||
return <div className='sideNav flex flex-col bg-gray-800' >
|
||||
<div className='w-full text-gray-50 h-9 text-lg mx-1 border-0 flex items-center focus:outline-none bg-transparent'>
|
||||
{fileHandle ? fileHandle.name.replace(/\.[^/.]+$/, "") : 'Untitled'}
|
||||
{
|
||||
(fileHandle ? fileHandle.name : fileName).replace(/\.[^/.]+$/, "")
|
||||
}
|
||||
</div>
|
||||
<div className='overflow-auto'>
|
||||
{treeEntries.allIds.map((entId, idx) => (
|
||||
<TreeEntry key={idx} entId={entId} />
|
||||
))}
|
||||
</div>
|
||||
{treeEntries.allIds.map((entId, idx) => (
|
||||
<TreeEntry key={idx} entId={entId} />
|
||||
))}
|
||||
</div>
|
||||
|
||||
}
|
||||
|
@ -26,147 +31,145 @@ export const Tree = () => {
|
|||
|
||||
const treeIcons = {
|
||||
'mesh': FaCube,
|
||||
'sketch': FaEdit
|
||||
'sketch': FaDrawPolygon
|
||||
}
|
||||
|
||||
|
||||
const TreeEntry = ({ entId }) => {
|
||||
|
||||
|
||||
|
||||
const treeEntriesById = useSelector(state => state.treeEntries.byId)
|
||||
const dispatch = useDispatch()
|
||||
|
||||
|
||||
const entry = useSelector(state => state.treeEntries.byId[entId])
|
||||
const visible = useSelector(state => state.treeEntries.visible[entId])
|
||||
const selected = useSelector(state => state.ui.selectedSet[entId])
|
||||
const activeId = useSelector(state => state.ui.dialog.target)
|
||||
|
||||
let obj3d, sketch;
|
||||
// console.log(entId)
|
||||
|
||||
if (treeEntriesById[entId].obj3d) {
|
||||
obj3d = treeEntriesById[entId].obj3d
|
||||
sketch = treeEntriesById[entId]
|
||||
let obj, sketch;
|
||||
|
||||
if (entry.obj3d) {
|
||||
obj = entry.obj3d
|
||||
sketch = entry
|
||||
} else {
|
||||
obj3d = treeEntriesById[entId]
|
||||
obj = entry
|
||||
}
|
||||
let Icon = treeIcons[obj3d.userData.type]
|
||||
let Icon = treeIcons[obj.userData.type]
|
||||
|
||||
const [_, forceUpdate] = useReducer(x => x + 1, 0);
|
||||
const [mouseOn, setMouseOn] = useState(false)
|
||||
|
||||
return <div className='btn text-gray-200 select-none flex justify-start w-full h-7 items-center text-sm'
|
||||
onDoubleClick={() => {
|
||||
if (obj3d.userData.type == 'sketch') {
|
||||
if (sce.activeSketch) {
|
||||
dispatch({ type: 'finish-sketch' })
|
||||
sce.activeSketch.deactivate()
|
||||
}
|
||||
|
||||
|
||||
sketch.activate()
|
||||
dispatch({ type: 'set-active-sketch', sketch })
|
||||
|
||||
sce.clearSelection()
|
||||
sce.activeSketch = sketch;
|
||||
dispatch({ type: 'set-dialog', action: 'sketch' })
|
||||
sce.render()
|
||||
} else if (obj3d.userData.featureInfo.length == 2) {
|
||||
dispatch({ type: 'set-dialog', action: 'extrude-edit', target: treeEntriesById[entId] })
|
||||
const edit = (e) => {
|
||||
e.stopPropagation()
|
||||
if (obj.userData.type == 'sketch') {
|
||||
if (sce.activeSketch) {
|
||||
dispatch({ type: 'finish-sketch' })
|
||||
sce.activeSketch.deactivate()
|
||||
}
|
||||
|
||||
}}
|
||||
onPointerEnter={() => {
|
||||
|
||||
sketch.activate()
|
||||
dispatch({ type: 'set-active-sketch', sketch })
|
||||
|
||||
sce.clearSelection()
|
||||
sce.activeSketch = sketch;
|
||||
dispatch({ type: 'set-dialog', action: 'sketch', target: obj.name })
|
||||
sce.render()
|
||||
} else if (obj.userData.featureInfo.length == 2) {
|
||||
dispatch({ type: 'set-dialog', action: 'extrude-edit', target: obj.name })
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const del = (e) => {
|
||||
e.stopPropagation()
|
||||
dispatch({ type: 'delete-node', id: entId })
|
||||
sce.render()
|
||||
}
|
||||
|
||||
const toggleVis = (e) => {
|
||||
e.stopPropagation()
|
||||
dispatch({ type: "set-entry-visibility", obj: { [entId]: !visible } })
|
||||
obj.visible = !visible;
|
||||
if (obj.userData.type == 'mesh') {
|
||||
obj.traverse((e) => visible ? e.layers.disable(1) : e.layers.enable(1))
|
||||
}
|
||||
|
||||
sce.render()
|
||||
forceUpdate()
|
||||
}
|
||||
|
||||
const mouseHandlers = {
|
||||
onPointerEnter: () => {
|
||||
if (mouseOn) return
|
||||
setMouseOn(true)
|
||||
|
||||
if (obj3d.userData.type == 'sketch') {
|
||||
obj3d.visible = true
|
||||
if (obj.userData.type == 'sketch') {
|
||||
obj.visible = true
|
||||
}
|
||||
|
||||
sce.setHover(obj3d, 1)
|
||||
sce.setHover(obj, 1)
|
||||
sce.render()
|
||||
}}
|
||||
onPointerLeave={() => {
|
||||
},
|
||||
onPointerLeave: () => {
|
||||
if (!mouseOn) return
|
||||
setMouseOn(false)
|
||||
|
||||
if (obj3d.userData.type == 'sketch'
|
||||
&& !sce.selected.includes(obj3d)
|
||||
if (obj.userData.type == 'sketch'
|
||||
&& !sce.selected.includes(obj)
|
||||
&& !visible
|
||||
) {
|
||||
obj3d.visible = false
|
||||
obj.visible = false
|
||||
}
|
||||
|
||||
if (sce.selected.includes(obj3d)) return
|
||||
if (sce.selected.includes(obj)) return
|
||||
|
||||
sce.setHover(obj3d, 0)
|
||||
sce.setHover(obj, 0)
|
||||
|
||||
sce.render()
|
||||
}}
|
||||
onClick={() => {
|
||||
const idx = sce.selected.indexOf(obj3d)
|
||||
|
||||
},
|
||||
onClick: () => {
|
||||
const idx = sce.selected.indexOf(obj)
|
||||
if (idx == -1) {
|
||||
sce.selected.push(obj3d)
|
||||
sce.setHover(obj3d, 1)
|
||||
sce.setHover(obj, 1)
|
||||
} else {
|
||||
sce.setHover(sce.selected[idx], 0)
|
||||
sce.selected.splice(idx, 1)
|
||||
}
|
||||
|
||||
dispatch({ type: 'on-pick', obj })
|
||||
|
||||
|
||||
sce.render()
|
||||
}}
|
||||
|
||||
tooltip={obj3d.name[0] != '(' ? "double click to edit" : undefined}
|
||||
// tooltip= {obj3d.userData.name}
|
||||
}
|
||||
}
|
||||
|
||||
return <div className={`cursor-pointer select-none fill-current text-gray-200 w-full h-8
|
||||
flex justify-start items-center text-xs ${mouseOn || selected ? 'bg-gray-600' : 'bg-transparent'}`}
|
||||
{...mouseHandlers}
|
||||
tooltip='click to add to selection'
|
||||
>
|
||||
<Icon className='h-full w-auto p-1.5' />
|
||||
<div className="pl-1">
|
||||
<Icon className={`h-full w-auto p-2 flex-none fill-current ${entId == activeId ? 'text-green-500' : 'text-gray-200'}`} />
|
||||
<div className="pl-1 h-full flex justify-start items-center overflow-x-hidden whitespace-nowrap ">
|
||||
{entId}
|
||||
</div>
|
||||
<div className='flex h-full ml-auto'>
|
||||
|
||||
<MdDelete className='btn-green h-full w-auto p-1.5'
|
||||
onClick={(e) => {
|
||||
dispatch({ type: 'delete-node', id: entId })
|
||||
sce.render()
|
||||
e.stopPropagation()
|
||||
}}
|
||||
/>
|
||||
|
||||
{
|
||||
visible ?
|
||||
<MdVisibility className='btn-green h-full w-auto p-1.5'
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
console.log('hide')
|
||||
dispatch({ type: "set-entry-visibility", obj: { [entId]: false } })
|
||||
obj3d.visible = false;
|
||||
if (obj3d.userData.type == 'mesh') {
|
||||
obj3d.traverse((e) => e.layers.disable(1))
|
||||
}
|
||||
|
||||
sce.render()
|
||||
forceUpdate()
|
||||
}}
|
||||
/>
|
||||
:
|
||||
<MdVisibilityOff className='btn-green h-full w-auto p-1.5'
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
console.log('show')
|
||||
obj3d.visible = true;
|
||||
dispatch({ type: "set-entry-visibility", obj: { [entId]: true } })
|
||||
if (obj3d.userData.type == 'mesh') {
|
||||
obj3d.traverse((e) => {
|
||||
e.layers.enable(1)
|
||||
})
|
||||
}
|
||||
sce.render()
|
||||
forceUpdate()
|
||||
}}
|
||||
/>
|
||||
mouseOn && obj.name[0] != '(' && <MdEdit className='btn-green h-full w-auto p-1.5'
|
||||
onClick={edit}
|
||||
/>
|
||||
}
|
||||
{
|
||||
mouseOn && <MdDelete className='btn-green h-full w-auto p-1.5'
|
||||
onClick={del}
|
||||
/>
|
||||
}
|
||||
{
|
||||
(mouseOn || visible) && <MdVisibility className='btn-green h-full w-auto p-1.5'
|
||||
onClick={toggleVis}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
}
|
|
@ -30,11 +30,11 @@ const color = {
|
|||
|
||||
const hoverColor = {
|
||||
emissive: 0x343407,
|
||||
point: 0x00ff00,
|
||||
point: 0x10B981,
|
||||
selpoint: 0xff0000,
|
||||
line: 0x00ff00,
|
||||
line: 0x10B981,
|
||||
mesh: 0xFAB601,
|
||||
dimension: 0x00ff00,
|
||||
dimension: 0x10B981,
|
||||
|
||||
plane: 0xffff00,
|
||||
planeBorder: 0x919100,
|
||||
|
@ -105,7 +105,7 @@ async function awaitSelection(...criteria) {
|
|||
|
||||
const counter = {}
|
||||
|
||||
let references = this.selected.slice()
|
||||
let references = (this.selected || this.scene.selected).slice()
|
||||
|
||||
for (let ob of references) {
|
||||
const type = ob.userData.type
|
||||
|
@ -149,7 +149,7 @@ async function awaitSelection(...criteria) {
|
|||
window.removeEventListener('keydown', onKey)
|
||||
}
|
||||
|
||||
// console.log('fail')
|
||||
console.log('fail')
|
||||
return null
|
||||
}
|
||||
|
||||
|
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 4.7 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 13 KiB |