@cloudron/pankow 3.5.17 → 3.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/Button.vue +11 -1
- package/components/Menu.vue +5 -6
- package/components/MultiSelect.vue +9 -5
- package/fetcher.js +2 -0
- package/package.json +13 -6
- package/tooltip.js +33 -23
- package/viewers/ThreeDViewer.vue +102 -0
- package/viewers.js +2 -0
package/components/Button.vue
CHANGED
|
@@ -12,6 +12,14 @@ const props = defineProps({
|
|
|
12
12
|
target: String,
|
|
13
13
|
iconRight: String,
|
|
14
14
|
menu: Array,
|
|
15
|
+
danger: {
|
|
16
|
+
type: Boolean,
|
|
17
|
+
default: false
|
|
18
|
+
},
|
|
19
|
+
success: {
|
|
20
|
+
type: Boolean,
|
|
21
|
+
default: false
|
|
22
|
+
},
|
|
15
23
|
disabled: {
|
|
16
24
|
type: Boolean,
|
|
17
25
|
default: false
|
|
@@ -43,7 +51,9 @@ function onClick(event) {
|
|
|
43
51
|
</script>
|
|
44
52
|
|
|
45
53
|
<template>
|
|
46
|
-
<component ref="elem" :is="href ? 'a' : 'div'" class="pankow-button" role="button" type="button" :class="{ 'pankow-button-disabled': disabled }"
|
|
54
|
+
<component ref="elem" :is="href ? 'a' : 'div'" class="pankow-button" role="button" type="button" :class="{ 'pankow-button-disabled': disabled }"
|
|
55
|
+
@click="onClick" @keydown.enter="onClick" :href="disabled ? null : href" :target="target" tabindex="0"
|
|
56
|
+
:success="success || undefined" :danger="danger || undefined">
|
|
47
57
|
<Menu ref="menuItem" :model="menu" v-if="menu"></Menu>
|
|
48
58
|
<Spinner v-show="loading" style="stroke: white;" :class="{ 'pankow-button-icon-with-text': $slots['default'] }"/>
|
|
49
59
|
<Icon v-show="!loading && icon" :icon="icon" :class="{ 'pankow-button-icon-with-text': $slots['default'] }" />
|
package/components/Menu.vue
CHANGED
|
@@ -69,7 +69,7 @@ const props = defineProps({
|
|
|
69
69
|
});
|
|
70
70
|
|
|
71
71
|
const openEventTimeStamp = ref(0);
|
|
72
|
-
|
|
72
|
+
let forElement = null;
|
|
73
73
|
let prevFocusElement = null;
|
|
74
74
|
const isOpen = ref(false);
|
|
75
75
|
let pageX = 0;
|
|
@@ -172,7 +172,7 @@ async function open(event, element = null) {
|
|
|
172
172
|
targetBottom = element ? element.getBoundingClientRect().bottom : event.pageY;
|
|
173
173
|
targetTop = element ? element.getBoundingClientRect().top : event.pageY;
|
|
174
174
|
offsetY.value = element ? (element.getBoundingClientRect().height + 2) : 0; // offset in case we roll up
|
|
175
|
-
forElement
|
|
175
|
+
forElement = element;
|
|
176
176
|
prevFocusElement = document.activeElement;
|
|
177
177
|
|
|
178
178
|
if (!container.value) return;
|
|
@@ -253,20 +253,19 @@ function position() {
|
|
|
253
253
|
if (!container.value) return;
|
|
254
254
|
|
|
255
255
|
const size = getHiddenElementSize(container.value);
|
|
256
|
+
const viewport = getViewport();
|
|
256
257
|
|
|
257
258
|
let left = pageX;
|
|
258
259
|
let top = targetBottom + 1;
|
|
259
260
|
let width = container.value.offsetParent ? container.value.offsetWidth : size.width;
|
|
260
261
|
let height = container.value.offsetParent ? container.value.offsetHeight : size.height;
|
|
261
|
-
let viewport = getViewport();
|
|
262
|
-
|
|
263
262
|
let bottom = viewport.height - targetTop + 1;
|
|
264
263
|
|
|
265
264
|
//flip
|
|
266
265
|
if (left + width - document.body.scrollLeft > viewport.width) {
|
|
267
266
|
// if this is like a dropdown right align instead of flip
|
|
268
|
-
if (forElement
|
|
269
|
-
left = forElement.
|
|
267
|
+
if (forElement) {
|
|
268
|
+
left = forElement.getBoundingClientRect().left + (forElement.getBoundingClientRect().width - width);
|
|
270
269
|
} else {
|
|
271
270
|
left -= width;
|
|
272
271
|
}
|
|
@@ -138,12 +138,16 @@ function onOpen(event) {
|
|
|
138
138
|
menu.value.open(event, elem.value);
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
-
function onClose(
|
|
141
|
+
function onClose() {
|
|
142
|
+
elem.value.focus();
|
|
143
|
+
emits('close');
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function onEscape(event) {
|
|
142
147
|
if (menu.value.isOpen) {
|
|
143
|
-
|
|
144
|
-
if (event) event.stopPropagation();
|
|
148
|
+
event.stopPropagation();
|
|
145
149
|
menu.value.close();
|
|
146
|
-
|
|
150
|
+
onClose();
|
|
147
151
|
}
|
|
148
152
|
}
|
|
149
153
|
|
|
@@ -154,7 +158,7 @@ onMounted(() => {
|
|
|
154
158
|
</script>
|
|
155
159
|
|
|
156
160
|
<template>
|
|
157
|
-
<div class="pankow-multiselect" :class="{ 'pankow-multiselect-disabled': disabled }" ref="elem" tabindex="0" @click="onClick" @keydown.enter="onOpen" @keydown.down.stop="onOpen" @keydown.up.stop="onOpen" @keydown.esc="
|
|
161
|
+
<div class="pankow-multiselect" :class="{ 'pankow-multiselect-disabled': disabled }" ref="elem" tabindex="0" @click="onClick" @keydown.enter="onOpen" @keydown.down.stop="onOpen" @keydown.up.stop="onOpen" @keydown.esc="onEscape">
|
|
158
162
|
<!-- native select for required and accessibility handling -->
|
|
159
163
|
<select ref="nativeSelect" :required="$attrs['required']" multiple style="display: none">
|
|
160
164
|
<option value=""></option>
|
package/fetcher.js
CHANGED
|
@@ -38,6 +38,8 @@ async function request(uri, method, headers, query, body, options) {
|
|
|
38
38
|
} catch (e) {
|
|
39
39
|
throw new Error(`Failed to parse response as json for content type ${contentType}.`, e);
|
|
40
40
|
}
|
|
41
|
+
} else if (contentType.indexOf('application/octet-stream') !== -1) {
|
|
42
|
+
responseBody = await response.blob();
|
|
41
43
|
} else {
|
|
42
44
|
responseBody = await response.text();
|
|
43
45
|
}
|
package/package.json
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cloudron/pankow",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "3.
|
|
4
|
+
"version": "3.6.0",
|
|
5
5
|
"description": "",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"types": "types/index.d.ts",
|
|
8
|
-
"files": [
|
|
8
|
+
"files": [
|
|
9
|
+
"*.css",
|
|
10
|
+
"*.js",
|
|
11
|
+
"components",
|
|
12
|
+
"viewers",
|
|
13
|
+
"types"
|
|
14
|
+
],
|
|
9
15
|
"scripts": {
|
|
10
16
|
"gallery": "cd gallery && vite",
|
|
11
17
|
"build": "cd gallery && vite build",
|
|
@@ -19,12 +25,13 @@
|
|
|
19
25
|
"@fontsource/inter": "^5.2.8",
|
|
20
26
|
"@fortawesome/fontawesome-free": "^7.1.0",
|
|
21
27
|
"filesize": "^11.0.13",
|
|
22
|
-
"monaco-editor": "^0.55.1"
|
|
28
|
+
"monaco-editor": "^0.55.1",
|
|
29
|
+
"online-3d-viewer": "^0.18.0"
|
|
23
30
|
},
|
|
24
31
|
"devDependencies": {
|
|
25
|
-
"@vitejs/plugin-vue": "^6.0.
|
|
32
|
+
"@vitejs/plugin-vue": "^6.0.3",
|
|
26
33
|
"typescript": "^5.9.3",
|
|
27
|
-
"vite": "^7.
|
|
28
|
-
"vue": "^3.5.
|
|
34
|
+
"vite": "^7.3.0",
|
|
35
|
+
"vue": "^3.5.26"
|
|
29
36
|
}
|
|
30
37
|
}
|
package/tooltip.js
CHANGED
|
@@ -38,9 +38,9 @@ function isElementHidden(element) {
|
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
function remove(key, target) {
|
|
41
|
-
if (tooltips[key]) tooltips[key].remove();
|
|
41
|
+
if (tooltips[key].element) tooltips[key].element.remove();
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
tooltips[key].element = null;
|
|
44
44
|
clearInterval(intervals[key]);
|
|
45
45
|
|
|
46
46
|
if (target) {
|
|
@@ -49,45 +49,49 @@ function remove(key, target) {
|
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
function update(target,
|
|
53
|
-
if (!
|
|
52
|
+
function update(target, modifiers, key) {
|
|
53
|
+
if (!tooltips[key].element) return;
|
|
54
54
|
|
|
55
|
-
|
|
55
|
+
tooltips[key].element.innerText = tooltips[key].value;
|
|
56
56
|
|
|
57
57
|
// BOTTOM is default
|
|
58
58
|
const pos = modifiers.top ? TOP : (modifiers.left ? LEFT : (modifiers.right ? RIGHT : BOTTOM));
|
|
59
59
|
|
|
60
60
|
const targetRect = target.getBoundingClientRect();
|
|
61
|
-
const tooltipRect =
|
|
61
|
+
const tooltipRect = tooltips[key].element.getBoundingClientRect();
|
|
62
62
|
|
|
63
|
-
if (pos === TOP || pos === BOTTOM)
|
|
64
|
-
else if (pos === LEFT)
|
|
65
|
-
else
|
|
63
|
+
if (pos === TOP || pos === BOTTOM) tooltips[key].element.style.left = (targetRect.left + targetRect.width/2) - (tooltipRect.width/2) + 'px';
|
|
64
|
+
else if (pos === LEFT) tooltips[key].element.style.left = (targetRect.left - tooltipRect.width - padding) + 'px';
|
|
65
|
+
else tooltips[key].element.style.left = (targetRect.left + targetRect.width + padding) + 'px';
|
|
66
66
|
|
|
67
|
-
if (pos === LEFT || pos === RIGHT)
|
|
68
|
-
else if (pos === TOP)
|
|
69
|
-
else
|
|
67
|
+
if (pos === LEFT || pos === RIGHT) tooltips[key].element.style.top = (targetRect.top + targetRect.height/2) - (tooltipRect.height/2) + 'px';
|
|
68
|
+
else if (pos === TOP) tooltips[key].element.style.top = (targetRect.top - tooltipRect.height - padding) + 'px';
|
|
69
|
+
else tooltips[key].element.style.top = (targetRect.bottom + padding) + 'px';
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
function mounted(el, binding, vnode) {
|
|
73
73
|
const key = vnode.ctx.uid;
|
|
74
|
+
tooltips[key] = {
|
|
75
|
+
element: null,
|
|
76
|
+
value: binding.value
|
|
77
|
+
};
|
|
74
78
|
|
|
75
79
|
el.addEventListener('mouseenter', () => {
|
|
76
|
-
if (!
|
|
80
|
+
if (!tooltips[key].value) return;
|
|
77
81
|
|
|
78
|
-
const
|
|
79
|
-
tooltips[key] =
|
|
82
|
+
const element = document.createElement('div');
|
|
83
|
+
tooltips[key].element = element;
|
|
80
84
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
window.document.body.appendChild(
|
|
85
|
+
element.setAttribute('id', key);
|
|
86
|
+
element.setAttribute('role', 'tooltip');
|
|
87
|
+
element.setAttribute('aria-hidden', 'false');
|
|
88
|
+
element.classList.add('pankow-tooltip');
|
|
89
|
+
window.document.body.appendChild(element);
|
|
86
90
|
|
|
87
91
|
el.setAttribute('aria-expanded', 'true');
|
|
88
92
|
el.setAttribute('aria-describedby', key);
|
|
89
93
|
|
|
90
|
-
update(el, binding.
|
|
94
|
+
update(el, binding.modifiers, key);
|
|
91
95
|
|
|
92
96
|
intervals[key] = setInterval(() => {
|
|
93
97
|
if (isElementHidden(el)) remove(key, el)
|
|
@@ -101,12 +105,18 @@ function mounted(el, binding, vnode) {
|
|
|
101
105
|
}
|
|
102
106
|
|
|
103
107
|
function updated(el, binding, vnode) {
|
|
104
|
-
if (!binding.value)
|
|
105
|
-
|
|
108
|
+
if (!binding.value) remove(vnode.ctx.uid, el);
|
|
109
|
+
|
|
110
|
+
tooltips[vnode.ctx.uid].value = binding.value;
|
|
111
|
+
|
|
112
|
+
update(el, binding.modifiers, vnode.ctx.uid);
|
|
106
113
|
}
|
|
107
114
|
|
|
108
115
|
function beforeUnmount(el, binding, vnode) {
|
|
116
|
+
if (!tooltips[vnode.ctx.uid]) return;
|
|
117
|
+
|
|
109
118
|
remove(vnode.ctx.uid, el);
|
|
119
|
+
delete tooltips[vnode.ctx.uid];
|
|
110
120
|
}
|
|
111
121
|
|
|
112
122
|
const tooltip = {
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
|
|
3
|
+
import { ref, useTemplateRef, onMounted } from 'vue';
|
|
4
|
+
import Button from '../components/Button.vue';
|
|
5
|
+
import MainLayout from '../components/MainLayout.vue';
|
|
6
|
+
import TopBar from '../components/TopBar.vue';
|
|
7
|
+
import utils from '../utils.js';
|
|
8
|
+
import { EmbeddedViewer, RGBAColor, RGBColor, EdgeSettings } from 'online-3d-viewer';
|
|
9
|
+
|
|
10
|
+
const emits = defineEmits([ 'close' ]);
|
|
11
|
+
const props = defineProps({
|
|
12
|
+
tr: {
|
|
13
|
+
type: Function,
|
|
14
|
+
default(id) { console.warn('Missing tr for ThreeDViewer'); return utils.translation(id); }
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
const entry = ref(null);
|
|
19
|
+
const container = useTemplateRef('container');
|
|
20
|
+
|
|
21
|
+
const SUPPORTED_EXTENSIONS = [
|
|
22
|
+
'3dm', '3ds', '3mf', 'amf', 'bim', 'brep',
|
|
23
|
+
'dae', 'fbx', 'fcstd', 'glb', 'gltf',
|
|
24
|
+
'ifc', 'igs', 'iges', 'stp', 'step',
|
|
25
|
+
'stl', 'obj', 'off', 'ply', 'wrl',
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
function canHandle(e) {
|
|
29
|
+
return SUPPORTED_EXTENSIONS.includes(e.extension.toLowerCase());
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
let viewer;
|
|
33
|
+
|
|
34
|
+
async function open(e, content) {
|
|
35
|
+
if (!e || e.isDirectory || !canHandle(e)) return;
|
|
36
|
+
|
|
37
|
+
entry.value = e;
|
|
38
|
+
|
|
39
|
+
viewer.LoadModelFromFileList([ new File([content], e.fileName) ], {
|
|
40
|
+
onModelLoaded: () => viewer.Resize()
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// setTimeout(() => viewer.Resize(), 1000);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function onClose() {
|
|
47
|
+
emits('close');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
onMounted(() => {
|
|
51
|
+
viewer = new EmbeddedViewer(container.value, {
|
|
52
|
+
backgroundColor: new RGBAColor(59, 68, 76, 0),
|
|
53
|
+
defaultColor: new RGBColor(65, 131, 196),
|
|
54
|
+
edgeSettings: new EdgeSettings(false, new RGBColor(0, 0, 0), 1),
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const resizeObserver = new ResizeObserver((entries) => {
|
|
58
|
+
viewer.Resize();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
resizeObserver.observe(container.value);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
defineExpose({
|
|
65
|
+
canHandle,
|
|
66
|
+
open,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
</script>
|
|
70
|
+
|
|
71
|
+
<template>
|
|
72
|
+
<MainLayout :gap="false" class="main-layout">
|
|
73
|
+
<template #header>
|
|
74
|
+
<TopBar class="navbar" :gap="false">
|
|
75
|
+
<template #left>
|
|
76
|
+
</template>
|
|
77
|
+
<template #center>
|
|
78
|
+
<div class="file-name">{{ entry ? entry.fileName : '' }}</div>
|
|
79
|
+
</template>
|
|
80
|
+
<template #right>
|
|
81
|
+
<Button icon="fa-solid fa-xmark" @click="onClose">{{ tr('main.dialog.close') }}</Button>
|
|
82
|
+
</template>
|
|
83
|
+
</TopBar>
|
|
84
|
+
</template>
|
|
85
|
+
<template #body>
|
|
86
|
+
<div ref="container" class="threed-object-container"></div>
|
|
87
|
+
</template>
|
|
88
|
+
</MainLayout>
|
|
89
|
+
</template>
|
|
90
|
+
|
|
91
|
+
<style scoped>
|
|
92
|
+
|
|
93
|
+
.threed-object-container {
|
|
94
|
+
width: 100%;
|
|
95
|
+
height: 100%;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.main-layout {
|
|
99
|
+
background-color: white;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
</style>
|
package/viewers.js
CHANGED
|
@@ -2,10 +2,12 @@ import GenericViewer from './viewers/GenericViewer.vue';
|
|
|
2
2
|
import ImageViewer from './viewers/ImageViewer.vue';
|
|
3
3
|
import PdfViewer from './viewers/PdfViewer.vue';
|
|
4
4
|
import TextViewer from './viewers/TextViewer.vue';
|
|
5
|
+
import ThreeDViewer from './viewers/ThreeDViewer.vue';
|
|
5
6
|
|
|
6
7
|
export {
|
|
7
8
|
GenericViewer,
|
|
8
9
|
ImageViewer,
|
|
9
10
|
PdfViewer,
|
|
10
11
|
TextViewer,
|
|
12
|
+
ThreeDViewer,
|
|
11
13
|
};
|