@cloudron/pankow 3.7.0 → 4.0.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.
@@ -474,7 +474,12 @@ export default {
474
474
  onDragExit(event) {
475
475
  this.dropTargetActive = false;
476
476
  },
477
- onItemDragStart(event) {
477
+ onItemDragStart(event, item) {
478
+ // auto-select the dragged item if it is not already part of the selection
479
+ if (item && !item.selected) {
480
+ this.onSelectAndFocus(item, {}, false);
481
+ }
482
+
478
483
  const dragHandle = document.getElementsByClassName('drag-handle')[0];
479
484
 
480
485
  // clear element
@@ -126,7 +126,7 @@ onMounted(() => {
126
126
  :style="{ 'text-align': columns[column].align || null, width: typeof columns[column].width === 'string' ? columns[column].width : 'auto' }"
127
127
  :class="{ 'pankow-table-cell-hide-mobile': columns[column].hideMobile, 'pankow-table-cell-nowrap': columns[column].nowrap }"
128
128
  >
129
- <slot :name="column" v-if="$slots[column]" v-bind="item"/>
129
+ <slot :name="column" v-if="$slots[column]" :item="item"/>
130
130
  <span v-if="!$slots[column]">{{ (column in item) ? (item[column].label || item[column]) : `TableView Error: item has no property '${column}' nor a template with that name` }}</span>
131
131
  </td>
132
132
  </tr>
@@ -0,0 +1,68 @@
1
+ <template>
2
+ <div class="tree-view">
3
+ <TreeViewNode
4
+ :entry="rootEntry"
5
+ :list-files="listFiles"
6
+ :active-path="activePath"
7
+ :depth="0"
8
+ :fallback-icon="fallbackIcon"
9
+ :expand-on-mount="expandRoot"
10
+ :drop-handler="dropHandler"
11
+ @navigate="$emit('navigate', $event)"
12
+ />
13
+ </div>
14
+ </template>
15
+
16
+ <script setup>
17
+
18
+ import { computed } from 'vue';
19
+ import TreeViewNode from './TreeViewNode.vue';
20
+
21
+ const props = defineProps({
22
+ listFiles: {
23
+ type: Function,
24
+ required: true,
25
+ },
26
+ activePath: {
27
+ type: String,
28
+ default: '/',
29
+ },
30
+ fallbackIcon: {
31
+ type: String,
32
+ default: '',
33
+ },
34
+ rootLabel: {
35
+ type: String,
36
+ default: '/',
37
+ },
38
+ expandRoot: {
39
+ type: Boolean,
40
+ default: true,
41
+ },
42
+ dropHandler: {
43
+ type: Function,
44
+ default: null,
45
+ },
46
+ });
47
+
48
+ defineEmits(['navigate']);
49
+
50
+ const rootEntry = computed(() => ({
51
+ name: props.rootLabel,
52
+ path: '/',
53
+ isDirectory: true,
54
+ icon: props.fallbackIcon,
55
+ }));
56
+
57
+ </script>
58
+
59
+ <style scoped>
60
+
61
+ .tree-view {
62
+ display: flex;
63
+ overflow: auto;
64
+ height: 100%;
65
+ padding: 4px;
66
+ }
67
+
68
+ </style>
@@ -0,0 +1,222 @@
1
+ <template>
2
+ <div class="tree-view-node">
3
+ <div
4
+ class="tree-view-node-row"
5
+ :class="{ 'active': isActive, 'drop-target': dropTargetActive }"
6
+ :style="{ paddingLeft: (props.depth * 16 + 4) + 'px' }"
7
+ @click="onRowClick"
8
+ @dragover.stop.prevent="onDragOver"
9
+ @dragenter.stop.prevent="onDragEnter"
10
+ @dragleave="onDragLeave"
11
+ @drop.stop.prevent="onDrop"
12
+ >
13
+ <span class="tree-view-node-chevron">
14
+ <i v-if="loading" class="fa-solid fa-spinner fa-spin" />
15
+ <i v-else-if="expanded" class="fa-solid fa-chevron-down" />
16
+ <i v-else class="fa-solid fa-chevron-right" />
17
+ </span>
18
+ <i class="fa-solid tree-view-node-folder-icon" :class="expanded ? 'fa-folder-open' : 'fa-folder'" />
19
+ <span :title="props.entry.name">{{ props.entry.name }}</span>
20
+ </div>
21
+ <div v-if="expanded && children.length > 0" class="tree-view-node-children">
22
+ <TreeViewNode
23
+ v-for="child in children"
24
+ :key="child.name"
25
+ :entry="child"
26
+ :list-files="props.listFiles"
27
+ :active-path="props.activePath"
28
+ :depth="props.depth + 1"
29
+ :fallback-icon="props.fallbackIcon"
30
+ :drop-handler="props.dropHandler"
31
+ @navigate="$emit('navigate', $event)"
32
+ />
33
+ </div>
34
+ </div>
35
+ </template>
36
+
37
+ <script setup>
38
+
39
+ import { ref, computed, watch, onMounted } from 'vue';
40
+
41
+ const props = defineProps({
42
+ entry: {
43
+ type: Object,
44
+ required: true,
45
+ },
46
+ listFiles: {
47
+ type: Function,
48
+ required: true,
49
+ },
50
+ activePath: {
51
+ type: String,
52
+ default: '/',
53
+ },
54
+ depth: {
55
+ type: Number,
56
+ default: 0,
57
+ },
58
+ fallbackIcon: {
59
+ type: String,
60
+ default: '',
61
+ },
62
+ expandOnMount: {
63
+ type: Boolean,
64
+ default: false,
65
+ },
66
+ dropHandler: {
67
+ type: Function,
68
+ default: null,
69
+ },
70
+ });
71
+
72
+ const emit = defineEmits(['navigate']);
73
+
74
+ const expanded = ref(false);
75
+ const children = ref([]);
76
+ const loading = ref(false);
77
+ const loaded = ref(false);
78
+ const dropTargetActive = ref(false);
79
+ let dragExpandTimer = null;
80
+
81
+ const isActive = computed(() => props.activePath === props.entry.path);
82
+
83
+ async function expand() {
84
+ if (loading.value) return;
85
+
86
+ loading.value = true;
87
+
88
+ try {
89
+ const entries = await props.listFiles(props.entry.path);
90
+ children.value = entries
91
+ .filter(item => item.isDirectory)
92
+ .map(item => ({
93
+ name: item.name || item.fileName,
94
+ path: props.entry.path === '/'
95
+ ? '/' + (item.name || item.fileName)
96
+ : props.entry.path + '/' + (item.name || item.fileName),
97
+ isDirectory: true,
98
+ icon: item.icon || props.fallbackIcon,
99
+ }));
100
+ loaded.value = true;
101
+ expanded.value = true;
102
+ } catch (e) {
103
+ console.error('TreeViewNode: failed to load children', e);
104
+ } finally {
105
+ loading.value = false;
106
+ }
107
+ }
108
+
109
+ async function onToggle() {
110
+ if (expanded.value) {
111
+ expanded.value = false;
112
+ } else if (loaded.value) {
113
+ expanded.value = true;
114
+ } else {
115
+ await expand();
116
+ }
117
+ }
118
+
119
+ function onRowClick() {
120
+ onToggle();
121
+ emit('navigate', { path: props.entry.path });
122
+ }
123
+
124
+ function onDragOver(event) {
125
+ if (!props.dropHandler) return;
126
+ event.dataTransfer.dropEffect = 'move';
127
+ dropTargetActive.value = true;
128
+ }
129
+
130
+ function onDragEnter(event) {
131
+ if (!props.dropHandler) return;
132
+ event.dataTransfer.dropEffect = 'move';
133
+ if (!expanded.value) dragExpandTimer = setTimeout(expand, 500);
134
+ dropTargetActive.value = true;
135
+ }
136
+
137
+ function onDragLeave() {
138
+ clearTimeout(dragExpandTimer);
139
+ dropTargetActive.value = false;
140
+ }
141
+
142
+ function onDrop(event) {
143
+ dropTargetActive.value = false;
144
+ if (!props.dropHandler) return;
145
+ clearTimeout(dragExpandTimer);
146
+ props.dropHandler(props.entry.path, event);
147
+ }
148
+
149
+ onMounted(() => {
150
+ if (props.expandOnMount) expand();
151
+ });
152
+
153
+ // Auto-expand if the active path is a descendant of this node
154
+ watch(() => props.activePath, (newPath) => {
155
+ const prefix = props.entry.path === '/' ? '/' : props.entry.path + '/';
156
+ if (newPath && newPath !== props.entry.path && newPath.startsWith(prefix)) {
157
+ if (!expanded.value) {
158
+ expand();
159
+ }
160
+ }
161
+ }, { immediate: true });
162
+
163
+ </script>
164
+
165
+ <style scoped>
166
+
167
+ .tree-view-node {
168
+ display: flex;
169
+ flex-direction: column;
170
+ flex: 1;
171
+ }
172
+
173
+ .tree-view-node-row {
174
+ display: flex;
175
+ align-items: center;
176
+ cursor: pointer;
177
+ white-space: nowrap;
178
+ border-radius: var(--pankow-border-radius);
179
+ padding: 6px 8px 6px 0;
180
+ margin: 2px 0;
181
+ }
182
+
183
+ .tree-view-node-row:hover {
184
+ background-color: var(--pankow-color-background-hover);
185
+ }
186
+
187
+ .tree-view-node-row.active {
188
+ background-color: #dbedfb;
189
+ }
190
+
191
+ @media (prefers-color-scheme: dark) {
192
+ .tree-view-node-row.active {
193
+ background-color: rgba(255, 255, 255, 0.2);
194
+ }
195
+ }
196
+
197
+ .tree-view-node-row.drop-target {
198
+ background-color: #dbedfb;
199
+ outline: 2px solid var(--pankow-color-primary, #3b82f6);
200
+ outline-offset: -2px;
201
+ }
202
+
203
+ @media (prefers-color-scheme: dark) {
204
+ .tree-view-node-row.drop-target {
205
+ background-color: rgba(255, 255, 255, 0.2);
206
+ }
207
+ }
208
+
209
+ .tree-view-node-chevron {
210
+ display: inline-flex;
211
+ align-items: center;
212
+ justify-content: center;
213
+ margin-right: 4px;
214
+ font-size: 10px;
215
+ }
216
+
217
+ .tree-view-node-folder-icon {
218
+ margin-right: 6px;
219
+ color: var(--pankow-color-secondary);
220
+ }
221
+
222
+ </style>
package/index.js CHANGED
@@ -43,6 +43,7 @@ import TagInput from './components/TagInput.vue';
43
43
  import TextInput from './components/TextInput.vue';
44
44
  import TextInputRaw from './components/TextInputRaw.vue';
45
45
  import TopBar from './components/TopBar.vue';
46
+ import TreeView from './components/TreeView.vue';
46
47
  import InputGroup from './components/InputGroup.vue'; // must be at the end for border-radius handling
47
48
 
48
49
  import fetcher from './fetcher.js';
@@ -90,6 +91,7 @@ export {
90
91
  TextInput,
91
92
  TextInputRaw,
92
93
  TopBar,
94
+ TreeView,
93
95
 
94
96
  fetcher,
95
97
  gestures,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@cloudron/pankow",
3
3
  "private": false,
4
- "version": "3.7.0",
4
+ "version": "4.0.1",
5
5
  "description": "",
6
6
  "main": "index.js",
7
7
  "types": "types/index.d.ts",
@@ -23,7 +23,7 @@
23
23
  "license": "ISC",
24
24
  "dependencies": {
25
25
  "@fontsource/inter": "^5.2.8",
26
- "@fortawesome/fontawesome-free": "^7.1.0",
26
+ "@fortawesome/fontawesome-free": "^7.2.0",
27
27
  "filesize": "^11.0.13",
28
28
  "monaco-editor": "^0.55.1",
29
29
  "online-3d-viewer": "^0.18.0"
package/types/index.d.ts CHANGED
@@ -3,4 +3,4 @@ import gestures from './gestures.js';
3
3
  import tooltip from './tooltip.js';
4
4
  import fallbackImage from './fallbackImage.js';
5
5
  import utils from './utils.js';
6
- export { BottomBar, Breadcrumb, Button, ButtonGroup, ClipboardAction, ClipboardButton, Checkbox, DateTimeInput, Dialog, DirectoryView, EmailInput, FileUploader, FormGroup, InputGroup, Icon, InputDialog, MainLayout, MaskedInput, Menu, MenuItem, SingleSelect, MultiSelect, Notification, NumberInput, OfflineBanner, PasswordInput, Popover, ProgressBar, Radiobutton, SideBar, Spinner, Switch, TableView, TabView, TagInput, TextInput, TextInputRaw, TopBar, fetcher, gestures, tooltip, fallbackImage, utils };
6
+ export { BottomBar, Breadcrumb, Button, ButtonGroup, ClipboardAction, ClipboardButton, Checkbox, DateTimeInput, Dialog, DirectoryView, EmailInput, FileUploader, FormGroup, InputGroup, Icon, InputDialog, MainLayout, MaskedInput, Menu, MenuItem, SingleSelect, MultiSelect, Notification, NumberInput, OfflineBanner, PasswordInput, Popover, ProgressBar, Radiobutton, SideBar, Spinner, Switch, TableView, TabView, TagInput, TextInput, TextInputRaw, TopBar, TreeView, fetcher, gestures, tooltip, fallbackImage, utils };