mirror of
https://github.com/yeicor-3d/yet-another-cad-viewer.git
synced 2025-12-19 22:24:17 +01:00
misc improvements
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -6,6 +6,7 @@
|
|||||||
/.cache/
|
/.cache/
|
||||||
/.parcel-cache/
|
/.parcel-cache/
|
||||||
/.idea/
|
/.idea/
|
||||||
|
/parcel-bundle-reports/
|
||||||
|
|
||||||
# TODO: Figure out if we want to keep a big default skybox image in the repo
|
# TODO: Figure out if we want to keep a big default skybox image in the repo
|
||||||
/assets/st_peters_square_night_8k.jpg
|
/assets/st_peters_square_night_8k.jpg
|
||||||
|
|||||||
6
.vuerc.js
Normal file
6
.vuerc.js
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
// noinspection JSUnusedGlobalSymbols,JSUnresolvedReference
|
||||||
|
module.exports = {
|
||||||
|
compilerOptions: {
|
||||||
|
isCustomElement: tag => tag === 'model-viewer'
|
||||||
|
}
|
||||||
|
}
|
||||||
11
package.json
11
package.json
@@ -6,7 +6,7 @@
|
|||||||
"author": "Yeicor",
|
"author": "Yeicor",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "parcel src/index.html",
|
"start": "parcel src/index.html",
|
||||||
"build": "parcel build src/index.html"
|
"build": "parcel build src/index.html --reporter @parcel/reporter-bundle-analyzer --detailed-report"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@google/model-viewer": "^3.4.0",
|
"@google/model-viewer": "^3.4.0",
|
||||||
@@ -16,12 +16,19 @@
|
|||||||
"vuetify": "^3.5.3"
|
"vuetify": "^3.5.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@mdi/font": "^7.4.47",
|
||||||
"@parcel/optimizer-data-url": "2.11.0",
|
"@parcel/optimizer-data-url": "2.11.0",
|
||||||
|
"@parcel/reporter-bundle-analyzer": "^2.11.0",
|
||||||
"@parcel/transformer-inline-string": "2.11.0",
|
"@parcel/transformer-inline-string": "2.11.0",
|
||||||
"@parcel/transformer-sass": "^2.11.0",
|
"@parcel/transformer-sass": "^2.11.0",
|
||||||
"@parcel/transformer-vue": "2.11.0",
|
"@parcel/transformer-vue": "2.11.0",
|
||||||
"@types/three": "^0.160.0",
|
"@types/three": "^0.160.0",
|
||||||
"buffer": "^5.5.0||^6.0.0",
|
"buffer": "^5.5.0||^6.0.0",
|
||||||
"parcel": "^2.11.0"
|
"parcel": "^2.11.0"
|
||||||
}
|
},
|
||||||
|
"browserslist": [
|
||||||
|
"> 0.5%",
|
||||||
|
"last 2 versions",
|
||||||
|
"not dead"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
39
src/App.vue
39
src/App.vue
@@ -2,33 +2,34 @@
|
|||||||
import ModelViewer from './ModelViewer.vue'
|
import ModelViewer from './ModelViewer.vue'
|
||||||
|
|
||||||
import {ref} from "vue";
|
import {ref} from "vue";
|
||||||
|
import Loading from "./Loading.vue";
|
||||||
|
|
||||||
let modelsOpened = ref(false)
|
// Open models by default on wide screens
|
||||||
let toolsOpened = ref(true)
|
let modelsOpened = ref(window.innerWidth > 600);
|
||||||
</script>
|
</script>
|
||||||
<
|
|
||||||
<template>
|
<template>
|
||||||
<v-layout>
|
<v-layout full-height>
|
||||||
<v-btn @click="modelsOpened = !modelsOpened" max-width="22px" style="position:absolute; z-index: 1">></v-btn>
|
<v-btn icon="$menu" @click="modelsOpened = !modelsOpened" style="position: absolute; z-index: 1"/>
|
||||||
<v-navigation-drawer v-model="modelsOpened">
|
<v-navigation-drawer v-model="modelsOpened" permanent>
|
||||||
<v-list density="compact" nav > <!-- TODO: Accordion -->
|
<v-toolbar>
|
||||||
<v-list-item-title>Models <v-btn @click="modelsOpened = !modelsOpened" style="float: right" height="22px"><</v-btn></v-list-item-title>
|
<v-toolbar-title>Models</v-toolbar-title>
|
||||||
<v-list-item prepend-icon="mdi-view-dashboard" title="Home" value="home"></v-list-item>
|
<v-toolbar-items>
|
||||||
<v-list-item prepend-icon="mdi-forum" title="About" value="about"></v-list-item>
|
<v-btn icon="$close" @click="modelsOpened = !modelsOpened"/>
|
||||||
|
</v-toolbar-items>
|
||||||
|
</v-toolbar>
|
||||||
|
<v-list density="compact" nav> <!-- TODO: Accordion -->
|
||||||
|
<v-list-item><Loading/></v-list-item>
|
||||||
</v-list>
|
</v-list>
|
||||||
</v-navigation-drawer>
|
</v-navigation-drawer>
|
||||||
<v-main style="height: 100vh">
|
<v-main >
|
||||||
<ModelViewer/>
|
<ModelViewer/>
|
||||||
</v-main>
|
</v-main>
|
||||||
<v-navigation-drawer location="right" v-model="toolsOpened" opa>
|
|
||||||
<v-list density="compact" nav >
|
|
||||||
<v-list-item-title>Tools</v-list-item-title>
|
|
||||||
<v-list-item prepend-icon="mdi-view-dashboard" title="Home" value="home"></v-list-item>
|
|
||||||
<v-list-item prepend-icon="mdi-forum" title="About" value="about"></v-list-item>
|
|
||||||
</v-list>
|
|
||||||
</v-navigation-drawer>
|
|
||||||
</v-layout>
|
</v-layout>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style>
|
||||||
|
html, body {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
9
src/Loading.vue
Normal file
9
src/Loading.vue
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<template>
|
||||||
|
<v-container style="height: 100%">
|
||||||
|
<v-row justify="center" style="height: 100%">
|
||||||
|
<v-col align-self="center">
|
||||||
|
<v-progress-circular indeterminate style="display: block; margin: 0 auto;"/>
|
||||||
|
</v-col>
|
||||||
|
</v-row>
|
||||||
|
</v-container>
|
||||||
|
</template>
|
||||||
@@ -1,11 +1,19 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import ModelViewerWrapper from "./ModelViewerWrapper.vue";
|
|
||||||
import ModelViewerOverlay from "./ModelViewerOverlay.vue";
|
import ModelViewerOverlay from "./ModelViewerOverlay.vue";
|
||||||
|
import Loading from "./Loading.vue";
|
||||||
|
import {defineAsyncComponent} from "vue";
|
||||||
|
|
||||||
|
// NOTE: The ModelViewer library is big, so we split it and import it asynchronously
|
||||||
|
const ModelViewerWrapper = defineAsyncComponent({
|
||||||
|
loader: () => import('./ModelViewerWrapper.vue'),
|
||||||
|
loadingComponent: Loading,
|
||||||
|
delay: 0,
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<ModelViewerWrapper/>
|
<model-viewer-wrapper/>
|
||||||
<ModelViewerOverlay/>
|
<model-viewer-overlay/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {ModelScene} from "@google/model-viewer/lib/three-components/ModelScene";
|
|||||||
let _ = ModelViewerElement // HACK: Keep the import from being removed by the bundler
|
let _ = ModelViewerElement // HACK: Keep the import from being removed by the bundler
|
||||||
const viewer = ref(null);
|
const viewer = ref(null);
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
// TODO: Custom gizmo component inside Tools window
|
// TODO: Custom gizmo component inside tools sidebar
|
||||||
// Gizmo installation
|
// Gizmo installation
|
||||||
let scene: ModelScene = viewer.value[$scene];
|
let scene: ModelScene = viewer.value[$scene];
|
||||||
let gizmo = new OrientationGizmo(scene);
|
let gizmo = new OrientationGizmo(scene);
|
||||||
@@ -28,7 +28,7 @@ onMounted(() => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<model-viewer
|
<model-viewer
|
||||||
ref="viewer" style="width: 100%; height: 100%" :src="settings.preloadModel" alt="The 3D model(s)" camera-controls
|
ref="viewer" style="width: 100%; height: 100%" :src="settings.preloadModels[0]" alt="The 3D model(s)" camera-controls
|
||||||
camera-orbit="30deg 75deg auto" max-camera-orbit="Infinity 180deg auto" min-camera-orbit="-Infinity 0deg auto"
|
camera-orbit="30deg 75deg auto" max-camera-orbit="Infinity 180deg auto" min-camera-orbit="-Infinity 0deg auto"
|
||||||
:exposure="settings.exposure" :shadow-intensity="settings.shadowIntensity" interaction-prompt="none"
|
:exposure="settings.exposure" :shadow-intensity="settings.shadowIntensity" interaction-prompt="none"
|
||||||
:autoplay="settings.autoplay" :ar="settings.arModes.length > 0" :ar-modes="settings.arModes"
|
:autoplay="settings.autoplay" :ar="settings.arModes.length > 0" :ar-modes="settings.arModes"
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import App from './App.vue'
|
|||||||
|
|
||||||
import 'vuetify/lib/styles/main.sass';
|
import 'vuetify/lib/styles/main.sass';
|
||||||
import { createVuetify } from 'vuetify';
|
import { createVuetify } from 'vuetify';
|
||||||
|
import '@mdi/font/css/materialdesignicons.css'
|
||||||
|
|
||||||
// TODO: Only import the components and directives that are actually used
|
// TODO: Only import the components and directives that are actually used
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@@ -31,5 +32,4 @@ const vuetify = createVuetify({
|
|||||||
|
|
||||||
const app = createApp(App)
|
const app = createApp(App)
|
||||||
app.use(vuetify)
|
app.use(vuetify)
|
||||||
app.config.compilerOptions.isCustomElement = tag => tag === 'model-viewer'
|
|
||||||
app.mount('body')
|
app.mount('body')
|
||||||
@@ -1,31 +1,48 @@
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import skyboxUrl from '../assets/st_peters_square_night_8k.jpg';
|
// import logo from "url:../assets/fox.glb";
|
||||||
// @ts-ignore
|
|
||||||
import logo from "url:../assets/fox.glb";
|
|
||||||
|
|
||||||
|
// These are the default values for the settings, which are overridden below
|
||||||
export const settings = {
|
export const settings = {
|
||||||
|
// @ts-ignore
|
||||||
|
preloadModels: [new URL('../assets/fox.glb', import.meta.url).href, "ws://localhost:8080/api/updates"],
|
||||||
// ModelViewer settings
|
// ModelViewer settings
|
||||||
preloadModel: logo,
|
|
||||||
autoplay: true,
|
autoplay: true,
|
||||||
arModes: 'webxr scene-viewer quick-look',
|
arModes: 'webxr scene-viewer quick-look',
|
||||||
exposure: 1,
|
exposure: 1,
|
||||||
shadowIntensity: 0,
|
shadowIntensity: 0,
|
||||||
background: skyboxUrl,
|
background: '',
|
||||||
|
}
|
||||||
|
|
||||||
|
const firstTimeNames = []; // Needed for array values, which clear the array when overridden
|
||||||
|
function parseSetting(name: string, value: string): any {
|
||||||
|
let arrayElem = name.endsWith(".0")
|
||||||
|
if (arrayElem) name = name.slice(0, -2);
|
||||||
|
let prevValue = settings[name];
|
||||||
|
if (prevValue === undefined) throw new Error(`Unknown setting: ${name}`);
|
||||||
|
if (Array.isArray(prevValue) && !arrayElem) {
|
||||||
|
let toExtend = []
|
||||||
|
if (!firstTimeNames.includes(name)) {
|
||||||
|
firstTimeNames.push(name);
|
||||||
|
} else {
|
||||||
|
toExtend = prevValue;
|
||||||
|
}
|
||||||
|
toExtend.push(parseSetting(name+".0", value));
|
||||||
|
return toExtend;
|
||||||
|
}
|
||||||
|
switch (typeof prevValue) {
|
||||||
|
case 'boolean':
|
||||||
|
return value === 'true';
|
||||||
|
case 'number':
|
||||||
|
return Number(value);
|
||||||
|
case 'string':
|
||||||
|
return value;
|
||||||
|
default:
|
||||||
|
throw new Error(`Unknown setting type: ${typeof prevValue}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auto-override any settings from the URL
|
// Auto-override any settings from the URL
|
||||||
const url = new URL(window.location.href);
|
const url = new URL(window.location.href);
|
||||||
url.searchParams.forEach((value, key) => {
|
url.searchParams.forEach((value, key) => {
|
||||||
if (key in settings) {
|
if (key in settings) settings[key] = parseSetting(key, value);
|
||||||
switch (typeof settings[key]) {
|
|
||||||
case 'boolean':
|
|
||||||
settings[key] = value === 'true';
|
|
||||||
break;
|
|
||||||
case 'number':
|
|
||||||
settings[key] = Number(value);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
settings[key] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
6
tsconfig.json
Normal file
6
tsconfig.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"lib": ["ES2020", "DOM"],
|
||||||
|
"allowSyntheticDefaultImports": true
|
||||||
|
}
|
||||||
|
}
|
||||||
14
yarn.lock
14
yarn.lock
@@ -96,6 +96,11 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@lmdb/lmdb-win32-x64/-/lmdb-win32-x64-2.8.5.tgz#8233e8762440b0f4632c47a09b1b6f23de8b934c"
|
resolved "https://registry.yarnpkg.com/@lmdb/lmdb-win32-x64/-/lmdb-win32-x64-2.8.5.tgz#8233e8762440b0f4632c47a09b1b6f23de8b934c"
|
||||||
integrity sha512-4wvrf5BgnR8RpogHhtpCPJMKBmvyZPhhUtEwMJbXh0ni2BucpfF07jlmyM11zRqQ2XIq6PbC2j7W7UCCcm1rRQ==
|
integrity sha512-4wvrf5BgnR8RpogHhtpCPJMKBmvyZPhhUtEwMJbXh0ni2BucpfF07jlmyM11zRqQ2XIq6PbC2j7W7UCCcm1rRQ==
|
||||||
|
|
||||||
|
"@mdi/font@^7.4.47":
|
||||||
|
version "7.4.47"
|
||||||
|
resolved "https://registry.yarnpkg.com/@mdi/font/-/font-7.4.47.tgz#2ae522867da3a5c88b738d54b403eb91471903af"
|
||||||
|
integrity sha512-43MtGpd585SNzHZPcYowu/84Vz2a2g31TvPMTm9uTiCSWzaheQySUcSyUH/46fPnuPQWof2yd0pGBtzee/IQWw==
|
||||||
|
|
||||||
"@mischnic/json-sourcemap@^0.1.0":
|
"@mischnic/json-sourcemap@^0.1.0":
|
||||||
version "0.1.1"
|
version "0.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/@mischnic/json-sourcemap/-/json-sourcemap-0.1.1.tgz#0ef9b015a8f575dd9a8720d9a6b4dbc988425906"
|
resolved "https://registry.yarnpkg.com/@mischnic/json-sourcemap/-/json-sourcemap-0.1.1.tgz#0ef9b015a8f575dd9a8720d9a6b4dbc988425906"
|
||||||
@@ -471,6 +476,15 @@
|
|||||||
"@parcel/events" "2.11.0"
|
"@parcel/events" "2.11.0"
|
||||||
chrome-trace-event "^1.0.2"
|
chrome-trace-event "^1.0.2"
|
||||||
|
|
||||||
|
"@parcel/reporter-bundle-analyzer@^2.11.0":
|
||||||
|
version "2.11.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@parcel/reporter-bundle-analyzer/-/reporter-bundle-analyzer-2.11.0.tgz#3a754aa314f5298c2990e7be6d9e02ea49d377e2"
|
||||||
|
integrity sha512-Cqob9LbXTDmkw1hZZIMiHSJYTBBfz+DsD2m/arrBGgW20L9xJ9yZGP/+myPeoznuOBwkpST6KuU4o+zdV7JCGQ==
|
||||||
|
dependencies:
|
||||||
|
"@parcel/plugin" "2.11.0"
|
||||||
|
"@parcel/utils" "2.11.0"
|
||||||
|
nullthrows "^1.1.1"
|
||||||
|
|
||||||
"@parcel/reporter-cli@2.11.0":
|
"@parcel/reporter-cli@2.11.0":
|
||||||
version "2.11.0"
|
version "2.11.0"
|
||||||
resolved "https://registry.yarnpkg.com/@parcel/reporter-cli/-/reporter-cli-2.11.0.tgz#727ee271ee5af002d137fa89ca25ccdca0f797fe"
|
resolved "https://registry.yarnpkg.com/@parcel/reporter-cli/-/reporter-cli-2.11.0.tgz#727ee271ee5af002d137fa89ca25ccdca0f797fe"
|
||||||
|
|||||||
Reference in New Issue
Block a user