@cloudron/pankow 3.5.18 → 3.6.1

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.
@@ -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 }" @click="onClick" @keydown.enter="onClick" :href="disabled ? null : href" :target="target" tabindex="0">
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'] }" />
@@ -124,6 +124,26 @@ export default {
124
124
  type: Boolean,
125
125
  default: true
126
126
  },
127
+ showNewFile: {
128
+ type: Boolean,
129
+ default: true
130
+ },
131
+ showNewFolder: {
132
+ type: Boolean,
133
+ default: true
134
+ },
135
+ showCut: {
136
+ type: Boolean,
137
+ default: true
138
+ },
139
+ showCopy: {
140
+ type: Boolean,
141
+ default: true
142
+ },
143
+ showDelete: {
144
+ type: Boolean,
145
+ default: true
146
+ },
127
147
  showShare: {
128
148
  // if String its the name of the property for existing share indicator
129
149
  type: [ Boolean, String ],
@@ -299,25 +319,31 @@ export default {
299
319
  icon:'fa-solid fa-check-double',
300
320
  action: this.onSelectAll
301
321
  }, {
302
- separator:true
322
+ separator:true,
323
+ visible: () => { return this.showNewFile || this.showNewFolder; },
303
324
  }, {
304
325
  label: this.tr('filemanager.toolbar.newFile'),
305
326
  icon:'fa-solid fa-file-circle-plus',
306
- action: this.newFileHandler
327
+ action: this.newFileHandler,
328
+ visible: () => { return this.showNewFile; },
307
329
  }, {
308
330
  label: this.tr('filemanager.toolbar.newFolder'),
309
331
  icon:'fa-solid fa-folder-plus',
310
- action: this.newFolderHandler
332
+ action: this.newFolderHandler,
333
+ visible: () => { return this.showNewFolder; },
311
334
  }, {
312
- separator:true
335
+ separator:true,
336
+ visible: () => { return this.showUploadFile || this.showUploadFolder; },
313
337
  }, {
314
338
  label: this.tr('filemanager.toolbar.uploadFile'),
315
339
  icon:'fa-solid fa-file-arrow-up',
316
- action: this.uploadFileHandler
340
+ action: this.uploadFileHandler,
341
+ visible: () => { return this.showUploadFile; },
317
342
  }, {
318
343
  label: this.tr('filemanager.toolbar.uploadFolder'),
319
344
  icon:'fa-regular fa-folder-open',
320
- action: this.uploadFolderHandler
345
+ action: this.uploadFolderHandler,
346
+ visible: () => { return this.showUploadFolder; },
321
347
  }],
322
348
  contextMenuModel: [{
323
349
  label: this.tr('filemanager.list.menu.open'),
@@ -336,15 +362,17 @@ export default {
336
362
  icon:'fa-solid fa-share-nodes',
337
363
  action: this.onItemShare,
338
364
  disabled: () => { return this.selectedCount > 1; },
339
- visible: this.showShare
365
+ visible: () => { return this.showShare; },
340
366
  }, {
341
367
  label: this.tr('filemanager.list.menu.copy'),
342
368
  icon:'fa-regular fa-copy',
343
- action: this.onItemsCopy
369
+ action: this.onItemsCopy,
370
+ visible: () => { return this.showCopy; },
344
371
  }, {
345
372
  label: this.tr('filemanager.list.menu.cut'),
346
373
  icon:'fa-solid fa-scissors',
347
- action: this.onItemsCut
374
+ action: this.onItemsCut,
375
+ visible: () => { return this.showCut; },
348
376
  }, {
349
377
  label: this.tr('filemanager.list.menu.paste'),
350
378
  icon:'fa-regular fa-paste',
@@ -363,7 +391,7 @@ export default {
363
391
  label: this.tr('filemanager.list.menu.chown'),
364
392
  icon:'fa-solid fa-user-pen',
365
393
  action: this.onItemsChangeOwner,
366
- visible: this.showOwner
394
+ visible: () => { return this.showOwner; },
367
395
  }, {
368
396
  label: this.tr('filemanager.list.menu.extract'),
369
397
  action: this.onItemExtract,
@@ -381,11 +409,13 @@ export default {
381
409
  file.name.match(/\.tar\.bz2$/)));
382
410
  }
383
411
  }, {
384
- separator:true
412
+ separator:true,
413
+ visible: () => { return this.showDelete; },
385
414
  }, {
386
415
  label: this.tr('filemanager.list.menu.delete'),
387
416
  icon:'fa-regular fa-trash-can',
388
- action: () => { this.deleteHandler(this.getSelected()); }
417
+ action: () => { this.deleteHandler(this.getSelected()); },
418
+ visible: () => { return this.showDelete; },
389
419
  }],
390
420
  };
391
421
  },
@@ -69,7 +69,7 @@ const props = defineProps({
69
69
  });
70
70
 
71
71
  const openEventTimeStamp = ref(0);
72
- const forElement = ref(null);
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.value = element;
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.value) {
269
- left = forElement.value.getBoundingClientRect().left + (forElement.value.getBoundingClientRect().width - width);
267
+ if (forElement) {
268
+ left = forElement.getBoundingClientRect().left + (forElement.getBoundingClientRect().width - width);
270
269
  } else {
271
270
  left -= width;
272
271
  }
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.5.18",
4
+ "version": "3.6.1",
5
5
  "description": "",
6
6
  "main": "index.js",
7
7
  "types": "types/index.d.ts",
8
- "files": ["*.css", "*.js", "components", "viewers", "types"],
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.2",
32
+ "@vitejs/plugin-vue": "^6.0.3",
26
33
  "typescript": "^5.9.3",
27
- "vite": "^7.2.7",
28
- "vue": "^3.5.25"
34
+ "vite": "^7.3.1",
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
- delete tooltips[key];
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, value, modifiers, tooltip) {
53
- if (!tooltip) return;
52
+ function update(target, modifiers, key) {
53
+ if (!tooltips[key].element) return;
54
54
 
55
- tooltip.innerText = value;
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 = tooltip.getBoundingClientRect();
61
+ const tooltipRect = tooltips[key].element.getBoundingClientRect();
62
62
 
63
- if (pos === TOP || pos === BOTTOM) tooltip.style.left = (targetRect.left + targetRect.width/2) - (tooltipRect.width/2) + 'px';
64
- else if (pos === LEFT) tooltip.style.left = (targetRect.left - tooltipRect.width - padding) + 'px';
65
- else tooltip.style.left = (targetRect.left + targetRect.width + padding) + 'px';
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) tooltip.style.top = (targetRect.top + targetRect.height/2) - (tooltipRect.height/2) + 'px';
68
- else if (pos === TOP) tooltip.style.top = (targetRect.top - tooltipRect.height - padding) + 'px';
69
- else tooltip.style.top = (targetRect.bottom + padding) + 'px';
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 (!binding.value) return remove(key);
80
+ if (!tooltips[key].value) return;
77
81
 
78
- const tooltip = document.createElement('div');
79
- tooltips[key] = tooltip;
82
+ const element = document.createElement('div');
83
+ tooltips[key].element = element;
80
84
 
81
- tooltip.setAttribute('id', key);
82
- tooltip.setAttribute('role', 'tooltip');
83
- tooltip.setAttribute('aria-hidden', 'false');
84
- tooltip.classList.add('pankow-tooltip');
85
- window.document.body.appendChild(tooltip);
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.value, binding.modifiers, tooltip);
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) return remove(vnode.ctx.uid, el);
105
- update(el, binding.value, binding.modifiers, tooltips[vnode.ctx.uid]);
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
  };