@cloudron/pankow 4.2.3 → 4.3.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/ListItem.vue +188 -0
- package/components/Notification.vue +9 -61
- package/components/TabView.vue +26 -31
- package/fallbackImage.js +21 -17
- package/fetcher.js +47 -5
- package/gestures.js +15 -11
- package/index.js +7 -0
- package/notifyState.js +48 -0
- package/package.json +14 -2
- package/plugin.js +39 -0
- package/tooltip.js +34 -15
- package/types/fallbackImage.d.ts +5 -4
- package/types/fetcher.d.ts +60 -10
- package/types/gestures.d.ts +16 -1
- package/types/index.d.ts +4 -1
- package/types/notifyState.d.ts +8 -0
- package/types/plugin.d.ts +4 -0
- package/types/tooltip.d.ts +5 -8
- package/types/utils.d.ts +185 -25
- package/utils.js +154 -14
- package/viewers/GenericViewer.vue +20 -32
- package/viewers/ImageViewer.vue +150 -140
- package/viewers/PdfViewer.vue +27 -35
- package/viewers/TextViewer.vue +134 -136
- package/vite-plugin.js +16 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
|
|
3
|
+
import { computed, useTemplateRef, ref } from 'vue';
|
|
4
|
+
import Menu from './Menu.vue';
|
|
5
|
+
import Button from './Button.vue';
|
|
6
|
+
import ButtonGroup from './ButtonGroup.vue';
|
|
7
|
+
import Icon from './Icon.vue';
|
|
8
|
+
|
|
9
|
+
const props = defineProps({
|
|
10
|
+
label: String,
|
|
11
|
+
subtext: String,
|
|
12
|
+
href: String,
|
|
13
|
+
actions: {
|
|
14
|
+
type: Array,
|
|
15
|
+
default: () => [],
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const quickActions = computed(() => {
|
|
20
|
+
if (window.innerWidth <= 576) return [];
|
|
21
|
+
|
|
22
|
+
const visibleActions = props.actions.filter(a => !(typeof a.visible !== 'undefined' && !a.visible) && !a.separator);
|
|
23
|
+
if (visibleActions.length <= 2) return visibleActions;
|
|
24
|
+
|
|
25
|
+
return visibleActions.filter(a => a.quickAction);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const visibleActionCount = computed(() => {
|
|
29
|
+
return props.actions.filter(a => !(typeof a.visible !== 'undefined' && !a.visible) && !a.separator).length;
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const isMenuOpen = ref(false);
|
|
33
|
+
|
|
34
|
+
const menuElement = useTemplateRef('menuElement');
|
|
35
|
+
function onMenu(event) {
|
|
36
|
+
isMenuOpen.value = true;
|
|
37
|
+
menuElement.value.open(event, event.currentTarget);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
</script>
|
|
41
|
+
|
|
42
|
+
<template>
|
|
43
|
+
<component
|
|
44
|
+
:is="href ? 'a' : 'div'"
|
|
45
|
+
:href="href"
|
|
46
|
+
class="pankow-list-item"
|
|
47
|
+
:class="{ 'pankow-list-item-link': href }"
|
|
48
|
+
>
|
|
49
|
+
<div class="pankow-list-item-left">
|
|
50
|
+
<slot name="left" />
|
|
51
|
+
</div>
|
|
52
|
+
<div class="pankow-list-item-center">
|
|
53
|
+
<div class="pankow-list-item-label">{{ label }}</div>
|
|
54
|
+
<div v-if="subtext" class="pankow-list-item-subtext">{{ subtext }}</div>
|
|
55
|
+
</div>
|
|
56
|
+
<div class="pankow-list-item-right">
|
|
57
|
+
<Icon v-if="href" icon="fa-solid fa-chevron-right" class="pankow-list-item-chevron" />
|
|
58
|
+
<div v-else-if="visibleActionCount > 0" class="pankow-list-item-action-bar" :class="{ 'is-menu-open': isMenuOpen }">
|
|
59
|
+
<Menu ref="menuElement" :model="actions" @close="isMenuOpen = false" />
|
|
60
|
+
<ButtonGroup class="pankow-list-item-action-bar-quick-actions">
|
|
61
|
+
<Button tool v-for="quickAction in quickActions" :key="quickAction" :icon="quickAction.icon" @click="quickAction.action && quickAction.action()" :href="quickAction.href || null" :target="quickAction.target || null" v-tooltip.top="quickAction.label"/>
|
|
62
|
+
<Button tool @click.capture="onMenu($event)" icon="fa-solid fa-ellipsis" v-if="visibleActionCount > 0 && visibleActionCount !== quickActions.length"/>
|
|
63
|
+
</ButtonGroup>
|
|
64
|
+
<Button tool :plain="isMenuOpen ? null : true" secondary @click.capture="onMenu($event)" icon="fa-solid fa-ellipsis" v-if="visibleActionCount > 0" class="pankow-list-item-action-bar-menu-trigger"/>
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
</component>
|
|
68
|
+
</template>
|
|
69
|
+
|
|
70
|
+
<style scoped>
|
|
71
|
+
|
|
72
|
+
.pankow-list-item {
|
|
73
|
+
display: flex;
|
|
74
|
+
justify-content: space-between;
|
|
75
|
+
align-items: center;
|
|
76
|
+
gap: 10px;
|
|
77
|
+
padding: 12px 16px;
|
|
78
|
+
background: var(--pankow-body-background-color);
|
|
79
|
+
border-radius: var(--pankow-border-radius);
|
|
80
|
+
color: inherit;
|
|
81
|
+
transition: background-color 0.15s;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.pankow-list-item-link {
|
|
85
|
+
text-decoration: none;
|
|
86
|
+
cursor: pointer;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.pankow-list-item:hover {
|
|
90
|
+
background: var(--pankow-color-background-hover);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.pankow-list-item-left {
|
|
94
|
+
display: flex;
|
|
95
|
+
align-items: center;
|
|
96
|
+
flex-shrink: 0;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.pankow-list-item-center {
|
|
100
|
+
display: flex;
|
|
101
|
+
flex-direction: column;
|
|
102
|
+
min-width: 0;
|
|
103
|
+
flex: 1;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.pankow-list-item-label {
|
|
107
|
+
font-size: 15px;
|
|
108
|
+
font-weight: var(--pankow-font-weight-bold);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.pankow-list-item-subtext {
|
|
112
|
+
font-size: 13px;
|
|
113
|
+
color: var(--pankow-color-text-secondary);
|
|
114
|
+
overflow: hidden;
|
|
115
|
+
text-overflow: ellipsis;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.pankow-list-item-right {
|
|
119
|
+
flex-shrink: 0;
|
|
120
|
+
display: flex;
|
|
121
|
+
align-items: center;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.pankow-list-item-chevron {
|
|
125
|
+
font-size: 12px;
|
|
126
|
+
color: #999;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.pankow-list-item-action-bar {
|
|
130
|
+
display: flex;
|
|
131
|
+
gap: 5px;
|
|
132
|
+
justify-content: end;
|
|
133
|
+
min-height: 31px;
|
|
134
|
+
align-items: center;
|
|
135
|
+
min-width: 55px;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.pankow-list-item-action-bar-menu-trigger {
|
|
139
|
+
display: none;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.pankow-list-item-action-bar-quick-actions {
|
|
143
|
+
display: flex;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.pankow-list-item-action-bar .pankow-list-item-action-bar-quick-actions .pankow-button {
|
|
147
|
+
background-color: var(--pankow-input-background-color);
|
|
148
|
+
color: var(--pankow-color-text);
|
|
149
|
+
border: 1px solid transparent;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.pankow-list-item-action-bar .pankow-list-item-action-bar-quick-actions .pankow-button:hover {
|
|
153
|
+
color: var(--pankow-color-primary);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
@media (hover: hover) {
|
|
157
|
+
.pankow-list-item-action-bar-quick-actions {
|
|
158
|
+
display: none;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
.pankow-list-item-action-bar-menu-trigger {
|
|
162
|
+
display: flex;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.pankow-list-item:hover .pankow-list-item-action-bar-quick-actions {
|
|
166
|
+
display: flex;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.pankow-list-item:hover .pankow-list-item-action-bar-menu-trigger {
|
|
170
|
+
display: none;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
@media (hover: none) {
|
|
175
|
+
.pankow-list-item-action-bar-quick-actions {
|
|
176
|
+
display: flex;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
.pankow-list-item-action-bar-menu-trigger {
|
|
180
|
+
display: none;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
.pankow-list-item-action-bar.is-menu-open .pankow-list-item-action-bar-menu-trigger {
|
|
185
|
+
display: block !important;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
</style>
|
|
@@ -1,71 +1,19 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
3
|
-
<
|
|
4
|
-
<div
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
<Icon @click="onClose(message.id)" icon="fa-solid fa-xmark" class="pankow-notification-close"/>
|
|
8
|
-
</div>
|
|
2
|
+
<TransitionGroup name="pankow-notification-fade" :class="`pankow-notification-container ${position}`" tag="div">
|
|
3
|
+
<div v-for="message in messages" :key="message.id">
|
|
4
|
+
<div class="pankow-notification" :[message.type]="''">
|
|
5
|
+
{{ message.text }}
|
|
6
|
+
<Icon @click="removeNotify(message.id)" icon="fa-solid fa-xmark" class="pankow-notification-close"/>
|
|
9
7
|
</div>
|
|
10
|
-
</
|
|
11
|
-
</
|
|
8
|
+
</div>
|
|
9
|
+
</TransitionGroup>
|
|
12
10
|
</template>
|
|
13
11
|
|
|
14
|
-
<script>
|
|
15
|
-
|
|
16
|
-
import { uuidv4 } from '../utils.js';
|
|
12
|
+
<script setup>
|
|
17
13
|
|
|
14
|
+
import { messages, position, removeNotify } from '../notifyState.js';
|
|
18
15
|
import Icon from './Icon.vue';
|
|
19
16
|
|
|
20
|
-
export default {
|
|
21
|
-
name: 'Notification',
|
|
22
|
-
components: {
|
|
23
|
-
Icon
|
|
24
|
-
},
|
|
25
|
-
props: {
|
|
26
|
-
position: {
|
|
27
|
-
type: String,
|
|
28
|
-
default: 'top-center',
|
|
29
|
-
validator(value, props) {
|
|
30
|
-
return [ 'top-left', 'top-center', 'top-right', 'bottom-left', 'bottom-center', 'bottom-right' ].includes(value);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
},
|
|
34
|
-
data() {
|
|
35
|
-
return {
|
|
36
|
-
messages: []
|
|
37
|
-
}
|
|
38
|
-
},
|
|
39
|
-
methods: {
|
|
40
|
-
raise(options) {
|
|
41
|
-
const id = uuidv4();
|
|
42
|
-
const text = typeof options === 'object' && 'text' in options ? options.text : options;
|
|
43
|
-
const persistent = typeof options === 'object' && 'persistent' in options ? options.persistent : false;
|
|
44
|
-
const timeout = typeof options === 'object' && 'timeout' in options ? options.timeout : 2000;
|
|
45
|
-
const type = typeof options === 'object' && 'type' in options ? options.type : null;
|
|
46
|
-
|
|
47
|
-
this.messages.push({ id, text, persistent, type });
|
|
48
|
-
|
|
49
|
-
if (!persistent) {
|
|
50
|
-
setTimeout(() => {
|
|
51
|
-
const index = this.messages.findIndex((value) => value.id === id);
|
|
52
|
-
if (index > -1) this.messages.splice(index, 1);
|
|
53
|
-
}, timeout);
|
|
54
|
-
}
|
|
55
|
-
},
|
|
56
|
-
onClose(id) {
|
|
57
|
-
const index = this.messages.findIndex((value) => value.id === id);
|
|
58
|
-
if (index > -1) this.messages.splice(index, 1);
|
|
59
|
-
}
|
|
60
|
-
},
|
|
61
|
-
mounted() {
|
|
62
|
-
if (!window.pankow) window.pankow = {};
|
|
63
|
-
if (window.pankow.notify) return console.error('Pankow: Notification DOM node already exists! Only ever use a single one.');
|
|
64
|
-
|
|
65
|
-
window.pankow.notify = (options) => { this.raise(options); }
|
|
66
|
-
}
|
|
67
|
-
};
|
|
68
|
-
|
|
69
17
|
</script>
|
|
70
18
|
|
|
71
19
|
<style>
|
package/components/TabView.vue
CHANGED
|
@@ -14,39 +14,34 @@
|
|
|
14
14
|
</div>
|
|
15
15
|
</template>
|
|
16
16
|
|
|
17
|
-
<script>
|
|
18
|
-
|
|
19
|
-
export default {
|
|
20
|
-
name: 'TabView',
|
|
21
|
-
emits: [ 'changed' ],
|
|
22
|
-
props: {
|
|
23
|
-
tabs: {
|
|
24
|
-
type: Object,
|
|
25
|
-
default: {}
|
|
26
|
-
},
|
|
27
|
-
defaultActive: String,
|
|
28
|
-
tabPosition: {
|
|
29
|
-
type: String,
|
|
30
|
-
default: 'top',
|
|
31
|
-
}
|
|
32
|
-
},
|
|
33
|
-
data() {
|
|
34
|
-
return {
|
|
35
|
-
active: ''
|
|
36
|
-
};
|
|
37
|
-
},
|
|
38
|
-
mounted() {
|
|
39
|
-
this.active = this.defaultActive;
|
|
40
|
-
},
|
|
41
|
-
methods: {
|
|
42
|
-
open(key) {
|
|
43
|
-
if (this.active === key) return;
|
|
17
|
+
<script setup>
|
|
44
18
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
19
|
+
import { ref, onMounted } from 'vue';
|
|
20
|
+
|
|
21
|
+
const emit = defineEmits([ 'changed' ]);
|
|
22
|
+
const props = defineProps({
|
|
23
|
+
tabs: {
|
|
24
|
+
type: Object,
|
|
25
|
+
default: {}
|
|
26
|
+
},
|
|
27
|
+
defaultActive: String,
|
|
28
|
+
tabPosition: {
|
|
29
|
+
type: String,
|
|
30
|
+
default: 'top',
|
|
48
31
|
}
|
|
49
|
-
};
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const active = ref('');
|
|
35
|
+
|
|
36
|
+
onMounted(() => {
|
|
37
|
+
active.value = props.defaultActive;
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
function open(key) {
|
|
41
|
+
if (active.value === key) return;
|
|
42
|
+
active.value = key;
|
|
43
|
+
emit('changed', key);
|
|
44
|
+
}
|
|
50
45
|
|
|
51
46
|
</script>
|
|
52
47
|
|
package/fallbackImage.js
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
```
|
|
6
|
-
import fallbackImage from 'pankow/fallbackImage';
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
1
|
+
/**
|
|
2
|
+
* FallbackImage Directive
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* ```
|
|
6
|
+
* import fallbackImage from 'pankow/fallbackImage';
|
|
7
|
+
* app.directive('fallback-image', fallbackImage);
|
|
8
|
+
* // <img v-fallback-image="'/fallback.png'"/>
|
|
9
|
+
* ```
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Vue directive `mounted` hook. Sets fallback src on image load error.
|
|
14
|
+
* @param {HTMLImageElement} el
|
|
15
|
+
* @param {import('vue').DirectiveBinding} binding - binding.value is the fallback URL
|
|
16
|
+
* @param {import('vue').VNode} vnode
|
|
17
|
+
*/
|
|
18
18
|
function mounted(el, binding, vnode) {
|
|
19
19
|
el.addEventListener('error', () => {
|
|
20
20
|
console.log('image loading failed, try to load', binding.value);
|
|
@@ -22,6 +22,10 @@ function mounted(el, binding, vnode) {
|
|
|
22
22
|
});
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
/**
|
|
26
|
+
* Vue directive for image fallback. Register via `app.directive('fallback-image', fallbackImage)`.
|
|
27
|
+
* @type {import('vue').Directive}
|
|
28
|
+
*/
|
|
25
29
|
const fallbackImage = {
|
|
26
30
|
mounted
|
|
27
31
|
};
|
package/fetcher.js
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
|
|
2
|
+
/**
|
|
3
|
+
* Shared configuration for all HTTP requests.
|
|
4
|
+
* @typedef {Object} FetcherGlobalOptions
|
|
5
|
+
* @property {string} credentials - Fetch credentials mode
|
|
6
|
+
* @property {string} redirect - Fetch redirect mode
|
|
7
|
+
* @property {?function(Response|Error): void} errorHook - Called on non-2xx responses or network errors
|
|
8
|
+
*/
|
|
9
|
+
|
|
2
10
|
const globalOptions = {
|
|
3
11
|
credentials: 'same-origin',
|
|
4
12
|
redirect: 'error',
|
|
@@ -16,6 +24,7 @@ const globalOptions = {
|
|
|
16
24
|
* @param {Object} query Plain key/value map serialized as the URL query string
|
|
17
25
|
* @param {Object|FormData|null} body
|
|
18
26
|
* @param {RequestInit} [options] Extra fetch options; `signal` is supported for cancellation
|
|
27
|
+
* @returns {Promise<{ status: number, body: any }>}
|
|
19
28
|
*/
|
|
20
29
|
async function request(uri, method, headers, query, body, options) {
|
|
21
30
|
let response, responseBody;
|
|
@@ -64,27 +73,60 @@ async function request(uri, method, headers, query, body, options) {
|
|
|
64
73
|
return { status: response.status, body: responseBody };
|
|
65
74
|
}
|
|
66
75
|
|
|
67
|
-
/**
|
|
76
|
+
/**
|
|
77
|
+
* Sends a HEAD request.
|
|
78
|
+
* @param {string} uri
|
|
79
|
+
* @param {Object} [query={}] - Query parameters serialized as URL query string
|
|
80
|
+
* @param {RequestInit} [options={}] - Merged into `fetch()`; use `{ signal }` to abort
|
|
81
|
+
* @returns {Promise<{ status: number, body: * }>}
|
|
82
|
+
*/
|
|
68
83
|
async function head(uri, query = {}, options = {}) {
|
|
69
84
|
return await request(uri, 'HEAD', {}, query, null, options);
|
|
70
85
|
}
|
|
71
86
|
|
|
72
|
-
/**
|
|
87
|
+
/**
|
|
88
|
+
* Sends a GET request.
|
|
89
|
+
* @param {string} uri
|
|
90
|
+
* @param {Object} [query={}] - Query parameters serialized as URL query string
|
|
91
|
+
* @param {RequestInit} [options={}] - Merged into `fetch()`; use `{ signal }` to abort
|
|
92
|
+
* @returns {Promise<{ status: number, body: * }>}
|
|
93
|
+
*/
|
|
73
94
|
async function get(uri, query = {}, options = {}) {
|
|
74
95
|
return await request(uri, 'GET', {}, query, null, options);
|
|
75
96
|
}
|
|
76
97
|
|
|
77
|
-
/**
|
|
98
|
+
/**
|
|
99
|
+
* Sends a POST request with a JSON or FormData body.
|
|
100
|
+
* @param {string} uri
|
|
101
|
+
* @param {Object|FormData} body - Request body; objects are JSON-stringified
|
|
102
|
+
* @param {Object} [query={}] - Query parameters serialized as URL query string
|
|
103
|
+
* @param {RequestInit} [options={}] - Merged into `fetch()`; use `{ signal }` to abort
|
|
104
|
+
* @returns {Promise<{ status: number, body: * }>}
|
|
105
|
+
*/
|
|
78
106
|
async function post(uri, body, query = {}, options = {}) {
|
|
79
107
|
return await request(uri, 'POST', {}, query, body, options);
|
|
80
108
|
}
|
|
81
109
|
|
|
82
|
-
/**
|
|
110
|
+
/**
|
|
111
|
+
* Sends a PUT request with a JSON or FormData body.
|
|
112
|
+
* @param {string} uri
|
|
113
|
+
* @param {Object|FormData} body - Request body; objects are JSON-stringified
|
|
114
|
+
* @param {Object} [query={}] - Query parameters serialized as URL query string
|
|
115
|
+
* @param {RequestInit} [options={}] - Merged into `fetch()`; use `{ signal }` to abort
|
|
116
|
+
* @returns {Promise<{ status: number, body: * }>}
|
|
117
|
+
*/
|
|
83
118
|
async function put(uri, body, query = {}, options = {}) {
|
|
84
119
|
return await request(uri, 'PUT', {}, query, body, options);
|
|
85
120
|
}
|
|
86
121
|
|
|
87
|
-
/**
|
|
122
|
+
/**
|
|
123
|
+
* Sends a DELETE request with an optional body.
|
|
124
|
+
* @param {string} uri
|
|
125
|
+
* @param {Object|FormData} body - Request body; objects are JSON-stringified
|
|
126
|
+
* @param {Object} [query={}] - Query parameters serialized as URL query string
|
|
127
|
+
* @param {RequestInit} [options={}] - Merged into `fetch()`; use `{ signal }` to abort
|
|
128
|
+
* @returns {Promise<{ status: number, body: * }>}
|
|
129
|
+
*/
|
|
88
130
|
async function del(uri, body, query = {}, options = {}) {
|
|
89
131
|
return await request(uri, 'DELETE', {}, query, body, options);
|
|
90
132
|
}
|
package/gestures.js
CHANGED
|
@@ -1,15 +1,19 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Swipe handler
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* ```
|
|
6
|
+
* import { onSwipe } from 'pankow/gestures';
|
|
7
|
+
* onSwipe(element, (direction) => {});
|
|
8
|
+
* ```
|
|
9
|
+
*/
|
|
2
10
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
```
|
|
11
|
-
|
|
12
|
-
*/
|
|
11
|
+
/**
|
|
12
|
+
* Registers touch event listeners on an element and calls the callback on swipe.
|
|
13
|
+
* @param {HTMLElement} elem
|
|
14
|
+
* @param {function('left'|'right'|'up'|'down'): void} callback
|
|
15
|
+
* @param {number} [threshold=50] - Minimum pixel distance to trigger a swipe
|
|
16
|
+
*/
|
|
13
17
|
function onSwipe(elem, callback, threshold = 50) {
|
|
14
18
|
callback = typeof callback === 'function' ? callback : () => {};
|
|
15
19
|
|
package/index.js
CHANGED
|
@@ -5,6 +5,8 @@ import "@fontsource/inter/500.css";
|
|
|
5
5
|
import '@fortawesome/fontawesome-free/css/all.min.css';
|
|
6
6
|
import './style.css';
|
|
7
7
|
|
|
8
|
+
import pankowPlugin from './plugin.js';
|
|
9
|
+
|
|
8
10
|
// Order of import matters due to css rule matching!
|
|
9
11
|
import BottomBar from './components/BottomBar.vue';
|
|
10
12
|
import BreadCrumb from './components/BreadCrumb.vue';
|
|
@@ -21,6 +23,7 @@ import FileUploader from './components/FileUploader.vue';
|
|
|
21
23
|
import FormGroup from './components/FormGroup.vue';
|
|
22
24
|
import Icon from './components/Icon.vue';
|
|
23
25
|
import InputDialog from './components/InputDialog.vue';
|
|
26
|
+
import ListItem from './components/ListItem.vue';
|
|
24
27
|
import LoginView from './components/LoginView.vue';
|
|
25
28
|
import MainLayout from './components/MainLayout.vue';
|
|
26
29
|
import MaskedInput from './components/MaskedInput.vue';
|
|
@@ -72,6 +75,7 @@ export {
|
|
|
72
75
|
FormGroup,
|
|
73
76
|
InputGroup,
|
|
74
77
|
Icon,
|
|
78
|
+
ListItem,
|
|
75
79
|
InputDialog,
|
|
76
80
|
LoginView,
|
|
77
81
|
MainLayout,
|
|
@@ -111,3 +115,6 @@ export {
|
|
|
111
115
|
export { CheckBox as Checkbox }; /* deprecated */
|
|
112
116
|
export { RadioButton as Radiobutton }; /* deprecated */
|
|
113
117
|
export { BreadCrumb as Breadcrumb }; /* deprecated */
|
|
118
|
+
|
|
119
|
+
export { useNotify } from './notifyState.js';
|
|
120
|
+
export default pankowPlugin;
|
package/notifyState.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { reactive, ref } from 'vue';
|
|
2
|
+
import { uuidv4 } from './utils.js';
|
|
3
|
+
|
|
4
|
+
const messages = reactive([]);
|
|
5
|
+
const position = ref('top-center');
|
|
6
|
+
|
|
7
|
+
function pushNotify(options) {
|
|
8
|
+
const id = uuidv4();
|
|
9
|
+
const text = typeof options === 'object' && 'text' in options ? options.text : options;
|
|
10
|
+
const persistent = typeof options === 'object' && 'persistent' in options ? options.persistent : false;
|
|
11
|
+
const timeout = typeof options === 'object' && 'timeout' in options ? options.timeout : 2000;
|
|
12
|
+
const type = typeof options === 'object' && 'type' in options ? options.type : null;
|
|
13
|
+
|
|
14
|
+
messages.push({ id, text, persistent, type });
|
|
15
|
+
|
|
16
|
+
if (!persistent) {
|
|
17
|
+
setTimeout(() => {
|
|
18
|
+
const index = messages.findIndex((value) => value.id === id);
|
|
19
|
+
if (index > -1) messages.splice(index, 1);
|
|
20
|
+
}, timeout);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function removeNotify(id) {
|
|
25
|
+
const index = messages.findIndex((value) => value.id === id);
|
|
26
|
+
if (index > -1) messages.splice(index, 1);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function useNotify() {
|
|
30
|
+
return { notify: pushNotify };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function setNotifyPosition(pos) {
|
|
34
|
+
position.value = pos;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (typeof window !== 'undefined') {
|
|
38
|
+
if (!window.pankow) window.pankow = {};
|
|
39
|
+
if (window.pankow.notify) {
|
|
40
|
+
console.error('Pankow: Notification already registered! Only ever use a single one.');
|
|
41
|
+
} else {
|
|
42
|
+
window.pankow.notify = pushNotify;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
window.pankow.setNotifyPosition = setNotifyPosition;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export { messages, position, pushNotify, removeNotify, setNotifyPosition, useNotify };
|
package/package.json
CHANGED
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cloudron/pankow",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "4.
|
|
5
|
-
"description": "",
|
|
4
|
+
"version": "4.3.0",
|
|
5
|
+
"description": "Vue 3 UI component library for Cloudron apps",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"types": "types/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./index.js",
|
|
11
|
+
"types": "./types/index.d.ts"
|
|
12
|
+
},
|
|
13
|
+
"./*": "./*",
|
|
14
|
+
"./vite-plugin": "./vite-plugin.js"
|
|
15
|
+
},
|
|
16
|
+
"keywords": ["vue", "vue3", "cloudron", "ui", "components"],
|
|
8
17
|
"files": [
|
|
9
18
|
"*.css",
|
|
10
19
|
"*.js",
|
|
@@ -28,6 +37,9 @@
|
|
|
28
37
|
"monaco-editor": "^0.55.1",
|
|
29
38
|
"online-3d-viewer": "^0.18.0"
|
|
30
39
|
},
|
|
40
|
+
"peerDependencies": {
|
|
41
|
+
"vue": "^3.5.0"
|
|
42
|
+
},
|
|
31
43
|
"devDependencies": {
|
|
32
44
|
"@highlightjs/vue-plugin": "^2.1.0",
|
|
33
45
|
"@vitejs/plugin-vue": "^6.0.7",
|
package/plugin.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { createApp } from 'vue';
|
|
2
|
+
import tooltip from './tooltip.js';
|
|
3
|
+
import fallbackImage from './fallbackImage.js';
|
|
4
|
+
import fetcher from './fetcher.js';
|
|
5
|
+
import { setNotifyPosition } from './notifyState.js';
|
|
6
|
+
import Notification from './components/Notification.vue';
|
|
7
|
+
|
|
8
|
+
const CONTAINER_ID = 'pankow-notification-root';
|
|
9
|
+
|
|
10
|
+
export default {
|
|
11
|
+
install(app, options = {}) {
|
|
12
|
+
app.directive('tooltip', tooltip);
|
|
13
|
+
app.directive('fallback-image', fallbackImage);
|
|
14
|
+
|
|
15
|
+
if (options.fetcher) {
|
|
16
|
+
if (options.fetcher.credentials !== undefined) {
|
|
17
|
+
fetcher.globalOptions.credentials = options.fetcher.credentials;
|
|
18
|
+
}
|
|
19
|
+
if (options.fetcher.errorHook !== undefined) {
|
|
20
|
+
fetcher.globalOptions.errorHook = options.fetcher.errorHook;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (options.notification?.position) {
|
|
25
|
+
setNotifyPosition(options.notification.position);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (typeof document !== 'undefined') {
|
|
29
|
+
const existing = document.getElementById(CONTAINER_ID);
|
|
30
|
+
if (existing) existing.remove();
|
|
31
|
+
|
|
32
|
+
const container = document.createElement('div');
|
|
33
|
+
container.id = CONTAINER_ID;
|
|
34
|
+
document.body.appendChild(container);
|
|
35
|
+
|
|
36
|
+
createApp(Notification).mount(container);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
};
|