@geode/opengeodeweb-front 10.32.1 → 10.32.2-rc.2
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/app/components/DragAndDrop.vue +48 -2
- package/app/components/DragAndDropInternal/DragAndDropInline.vue +2 -2
- package/app/components/DragAndDropInternal/DragAndDropOverlay.vue +2 -2
- package/app/components/FileUploader.vue +1 -1
- package/app/components/Screenshot.vue +5 -3
- package/app/components/Viewer/ContextMenu/InfoCard.vue +4 -6
- package/app/components/Viewer/ObjectTree/Base/ItemLabel.vue +3 -1
- package/package.json +3 -3
- package/tests/unit/components/DragAndDrop.nuxt.test.js +89 -0
|
@@ -6,7 +6,7 @@ import DragAndDropOverlay from "./DragAndDropInternal/DragAndDropOverlay.vue";
|
|
|
6
6
|
const { multiple, accept, loading, showExtensions, fullscreen, inline, showOverlay, texts } =
|
|
7
7
|
defineProps({
|
|
8
8
|
multiple: { type: Boolean, default: false },
|
|
9
|
-
accept: { type: String, default: "" },
|
|
9
|
+
accept: { type: [String, Array], default: "" },
|
|
10
10
|
loading: { type: Boolean, default: false },
|
|
11
11
|
showExtensions: { type: Boolean, default: true },
|
|
12
12
|
fullscreen: { type: Boolean, default: false },
|
|
@@ -29,6 +29,52 @@ const isInternalDrag = ref(false);
|
|
|
29
29
|
const dragCounter = ref(0);
|
|
30
30
|
const fileInput = ref(undefined);
|
|
31
31
|
|
|
32
|
+
const WILDCARD_SUFFIX_LENGTH = 2;
|
|
33
|
+
|
|
34
|
+
function isFileAccepted(file, acceptValue) {
|
|
35
|
+
const fileName = (file.name || "").toLowerCase();
|
|
36
|
+
const fileType = (file.type || "").toLowerCase();
|
|
37
|
+
const isVext = fileName.endsWith(".vext");
|
|
38
|
+
|
|
39
|
+
if (!acceptValue) {
|
|
40
|
+
return !isVext;
|
|
41
|
+
}
|
|
42
|
+
let rules = [];
|
|
43
|
+
if (Array.isArray(acceptValue)) {
|
|
44
|
+
rules = acceptValue;
|
|
45
|
+
} else if (typeof acceptValue === "string") {
|
|
46
|
+
rules = acceptValue.split(",");
|
|
47
|
+
}
|
|
48
|
+
rules = rules.map((rule) => String(rule).trim()).filter(Boolean);
|
|
49
|
+
if (rules.length === 0) {
|
|
50
|
+
return !isVext;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return rules.some((rule) => {
|
|
54
|
+
let cleanRule = rule.toLowerCase();
|
|
55
|
+
if (!cleanRule) {
|
|
56
|
+
return !isVext;
|
|
57
|
+
}
|
|
58
|
+
if (cleanRule === "*" || cleanRule === "*.*") {
|
|
59
|
+
return !isVext;
|
|
60
|
+
}
|
|
61
|
+
if (cleanRule.startsWith("*.")) {
|
|
62
|
+
cleanRule = cleanRule.slice(1);
|
|
63
|
+
}
|
|
64
|
+
if (cleanRule.startsWith(".")) {
|
|
65
|
+
return fileName.endsWith(cleanRule);
|
|
66
|
+
}
|
|
67
|
+
if (cleanRule.includes("/")) {
|
|
68
|
+
if (cleanRule.endsWith("/*")) {
|
|
69
|
+
const typeGroup = cleanRule.slice(0, -WILDCARD_SUFFIX_LENGTH);
|
|
70
|
+
return fileType.startsWith(typeGroup);
|
|
71
|
+
}
|
|
72
|
+
return fileType === cleanRule;
|
|
73
|
+
}
|
|
74
|
+
return fileName.endsWith(`.${cleanRule}`);
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
32
78
|
function triggerFileDialog() {
|
|
33
79
|
fileInput.value?.click();
|
|
34
80
|
}
|
|
@@ -58,7 +104,7 @@ function onDrop(event) {
|
|
|
58
104
|
event.preventDefault();
|
|
59
105
|
dragCounter.value = 0;
|
|
60
106
|
isDragging.value = false;
|
|
61
|
-
const files = [...event.dataTransfer.files];
|
|
107
|
+
const files = [...event.dataTransfer.files].filter((file) => isFileAccepted(file, accept));
|
|
62
108
|
if (files.length > 0) {
|
|
63
109
|
emit("files-selected", files);
|
|
64
110
|
}
|
|
@@ -12,7 +12,7 @@ const { isDragging, loading, texts, accept, showExtensions } = defineProps({
|
|
|
12
12
|
loading: "Loading...",
|
|
13
13
|
}),
|
|
14
14
|
},
|
|
15
|
-
accept: { type: String, default: "" },
|
|
15
|
+
accept: { type: [String, Array], default: "" },
|
|
16
16
|
showExtensions: { type: Boolean, required: true },
|
|
17
17
|
});
|
|
18
18
|
|
|
@@ -51,7 +51,7 @@ const emit = defineEmits(["click"]);
|
|
|
51
51
|
v-if="accept && showExtensions"
|
|
52
52
|
class="text-body-2 text-white opacity-60 bg-transparent"
|
|
53
53
|
>
|
|
54
|
-
{{ accept }}
|
|
54
|
+
{{ Array.isArray(accept) ? accept.join(", ") : accept }}
|
|
55
55
|
</v-sheet>
|
|
56
56
|
</v-card-text>
|
|
57
57
|
</GlassCard>
|
|
@@ -14,7 +14,7 @@ const { isDragging, showOverlay, fullscreen, loading, texts, multiple, accept, s
|
|
|
14
14
|
}),
|
|
15
15
|
},
|
|
16
16
|
multiple: { type: Boolean, required: true },
|
|
17
|
-
accept: { type: String, default: "" },
|
|
17
|
+
accept: { type: [String, Array], default: "" },
|
|
18
18
|
showExtensions: { type: Boolean, required: true },
|
|
19
19
|
});
|
|
20
20
|
</script>
|
|
@@ -76,7 +76,7 @@ const { isDragging, showOverlay, fullscreen, loading, texts, multiple, accept, s
|
|
|
76
76
|
:size="fullscreen ? 'x-large' : 'large'"
|
|
77
77
|
class="px-8 glow-chip"
|
|
78
78
|
>
|
|
79
|
-
Accepted formats: {{ accept }}
|
|
79
|
+
Accepted formats: {{ Array.isArray(accept) ? accept.join(", ") : accept }}
|
|
80
80
|
</v-chip>
|
|
81
81
|
|
|
82
82
|
<v-sheet
|
|
@@ -8,7 +8,7 @@ const emit = defineEmits(["files_uploaded", "decrement_step", "reset_values"]);
|
|
|
8
8
|
|
|
9
9
|
const { multiple, accept, files, autoUpload, showOverlay, mini } = defineProps({
|
|
10
10
|
multiple: { type: Boolean, default: false },
|
|
11
|
-
accept: { type: String, default: "" },
|
|
11
|
+
accept: { type: [String, Array], default: "" },
|
|
12
12
|
files: { type: Array, default: () => [] },
|
|
13
13
|
autoUpload: { type: Boolean, default: true },
|
|
14
14
|
showOverlay: { type: Boolean, default: false },
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import ToolPanel from "@ogw_front/components/ToolPanel";
|
|
3
3
|
import fileDownload from "js-file-download";
|
|
4
|
-
import
|
|
5
|
-
|
|
4
|
+
import { useClipboardItems } from "@vueuse/core";
|
|
6
5
|
import { useFeedbackStore } from "@ogw_front/stores/feedback";
|
|
7
6
|
import { useViewerStore } from "@ogw_front/stores/viewer";
|
|
7
|
+
import viewer_schemas from "@geode/opengeodeweb-viewer/opengeodeweb_viewer_schemas.json";
|
|
8
8
|
|
|
9
9
|
const show = defineModel({ type: Boolean, default: false });
|
|
10
10
|
|
|
@@ -19,6 +19,8 @@ const output_extension = ref("png");
|
|
|
19
19
|
const include_background = ref(true);
|
|
20
20
|
const screenshot_type = ref("file");
|
|
21
21
|
|
|
22
|
+
const { copy } = useClipboardItems();
|
|
23
|
+
|
|
22
24
|
async function takeScreenshot() {
|
|
23
25
|
const viewerStore = useViewerStore();
|
|
24
26
|
const feedbackStore = useFeedbackStore();
|
|
@@ -42,7 +44,7 @@ async function takeScreenshot() {
|
|
|
42
44
|
} else {
|
|
43
45
|
try {
|
|
44
46
|
const pngBlob = new Blob([response.blob], { type: "image/png" });
|
|
45
|
-
await
|
|
47
|
+
await copy([new ClipboardItem({ "image/png": pngBlob })]);
|
|
46
48
|
feedbackStore.add_success("Screenshot copied to clipboard");
|
|
47
49
|
} catch (error) {
|
|
48
50
|
feedbackStore.add_error(
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import GlassCard from "@ogw_front/components/GlassCard";
|
|
3
3
|
import { middleTruncate } from "@ogw_front/utils/string";
|
|
4
|
+
import { useClipboard } from "@vueuse/core";
|
|
4
5
|
import { useDataStore } from "@ogw_front/stores/data";
|
|
5
6
|
import { useMenuStore } from "@ogw_front/stores/menu";
|
|
6
7
|
|
|
@@ -19,6 +20,8 @@ const TRUNCATE_MAX_LENGTH = 22;
|
|
|
19
20
|
const TRUNCATE_START_CHARS = 12;
|
|
20
21
|
const TRUNCATE_END_CHARS = 7;
|
|
21
22
|
|
|
23
|
+
const { copy, copied } = useClipboard({ copiedDuring: COPIED_TIMEOUT });
|
|
24
|
+
|
|
22
25
|
const menuStore = useMenuStore();
|
|
23
26
|
const dataStore = useDataStore();
|
|
24
27
|
|
|
@@ -78,17 +81,12 @@ const displayComponentTitle = computed(() => {
|
|
|
78
81
|
);
|
|
79
82
|
});
|
|
80
83
|
|
|
81
|
-
const copied = ref(false);
|
|
82
84
|
async function copyId(targetId) {
|
|
83
85
|
if (!targetId) {
|
|
84
86
|
return;
|
|
85
87
|
}
|
|
86
88
|
try {
|
|
87
|
-
await
|
|
88
|
-
copied.value = true;
|
|
89
|
-
setTimeout(() => {
|
|
90
|
-
copied.value = false;
|
|
91
|
-
}, COPIED_TIMEOUT);
|
|
89
|
+
await copy(targetId);
|
|
92
90
|
} catch (error) {
|
|
93
91
|
console.error("Failed to copy ID:", error);
|
|
94
92
|
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import { middleTruncate } from "@ogw_front/utils/string";
|
|
3
|
+
import { useClipboard } from "@vueuse/core";
|
|
3
4
|
import { useFeedbackStore } from "@ogw_front/stores/feedback";
|
|
4
5
|
|
|
5
6
|
const feedbackStore = useFeedbackStore();
|
|
7
|
+
const { copy } = useClipboard();
|
|
6
8
|
|
|
7
9
|
const { item, isLeaf } = defineProps({
|
|
8
10
|
item: { type: Object, required: true },
|
|
@@ -52,7 +54,7 @@ const tooltipDisabled = computed(() => {
|
|
|
52
54
|
});
|
|
53
55
|
|
|
54
56
|
async function copyToClipboard(text, label) {
|
|
55
|
-
await
|
|
57
|
+
await copy(text);
|
|
56
58
|
feedbackStore.add_success(`${label} copied to clipboard`);
|
|
57
59
|
}
|
|
58
60
|
</script>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@geode/opengeodeweb-front",
|
|
3
|
-
"version": "10.32.
|
|
3
|
+
"version": "10.32.2-rc.2",
|
|
4
4
|
"description": "OpenSource Vue/Nuxt/Pinia/Vuetify framework for web applications",
|
|
5
5
|
"homepage": "https://github.com/Geode-solutions/OpenGeodeWeb-Front",
|
|
6
6
|
"bugs": {
|
|
@@ -39,8 +39,8 @@
|
|
|
39
39
|
"build": ""
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@geode/opengeodeweb-back": "
|
|
43
|
-
"@geode/opengeodeweb-viewer": "
|
|
42
|
+
"@geode/opengeodeweb-back": "next",
|
|
43
|
+
"@geode/opengeodeweb-viewer": "next",
|
|
44
44
|
"@google-cloud/run": "3.2.0",
|
|
45
45
|
"@kitware/vtk.js": "33.3.0",
|
|
46
46
|
"@mdi/font": "7.4.47",
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// Third party imports
|
|
2
|
+
import { describe, expect, test } from "vitest";
|
|
3
|
+
import { flushPromises } from "@vue/test-utils";
|
|
4
|
+
import { mountSuspended } from "@nuxt/test-utils/runtime";
|
|
5
|
+
|
|
6
|
+
// Local imports
|
|
7
|
+
import { setupActivePinia, vuetify } from "@ogw_tests/utils";
|
|
8
|
+
import DragAndDrop from "@ogw_front/components/DragAndDrop";
|
|
9
|
+
|
|
10
|
+
describe("drag and drop", () => {
|
|
11
|
+
const pinia = setupActivePinia();
|
|
12
|
+
|
|
13
|
+
test("ignores dropped files that do not match accept prop", async () => {
|
|
14
|
+
const wrapper = await mountSuspended(DragAndDrop, {
|
|
15
|
+
global: {
|
|
16
|
+
plugins: [vuetify, pinia],
|
|
17
|
+
},
|
|
18
|
+
props: {
|
|
19
|
+
accept: ".vtp,.vts",
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const dropEvent = new Event("drop", { bubbles: true, cancelable: true });
|
|
24
|
+
Object.defineProperty(dropEvent, "dataTransfer", {
|
|
25
|
+
value: {
|
|
26
|
+
files: [new File(["extension_data"], "plugin.vext")],
|
|
27
|
+
types: ["Files"],
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
globalThis.dispatchEvent(dropEvent);
|
|
32
|
+
await flushPromises();
|
|
33
|
+
|
|
34
|
+
expect(wrapper.emitted("files-selected")).toBeUndefined();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("emits files-selected when dropped file matches accept prop", async () => {
|
|
38
|
+
const wrapper = await mountSuspended(DragAndDrop, {
|
|
39
|
+
global: {
|
|
40
|
+
plugins: [vuetify, pinia],
|
|
41
|
+
},
|
|
42
|
+
props: {
|
|
43
|
+
accept: ".vtp,.vts",
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const validFile = new File(["mesh_data"], "model.vtp");
|
|
48
|
+
const dropEvent = new Event("drop", { bubbles: true, cancelable: true });
|
|
49
|
+
Object.defineProperty(dropEvent, "dataTransfer", {
|
|
50
|
+
value: {
|
|
51
|
+
files: [validFile],
|
|
52
|
+
types: ["Files"],
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
globalThis.dispatchEvent(dropEvent);
|
|
57
|
+
await flushPromises();
|
|
58
|
+
|
|
59
|
+
expect(wrapper.emitted("files-selected")).toBeDefined();
|
|
60
|
+
expect(wrapper.emitted("files-selected")[0][0]).toStrictEqual([validFile]);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("accepts general files but excludes .vext when accept prop is empty", async () => {
|
|
64
|
+
const wrapper = await mountSuspended(DragAndDrop, {
|
|
65
|
+
global: {
|
|
66
|
+
plugins: [vuetify, pinia],
|
|
67
|
+
},
|
|
68
|
+
props: {
|
|
69
|
+
accept: "",
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const dataFile = new File(["any_data"], "data_file.txt");
|
|
74
|
+
const extensionFile = new File(["extension_data"], "plugin.vext");
|
|
75
|
+
const dropEvent = new Event("drop", { bubbles: true, cancelable: true });
|
|
76
|
+
Object.defineProperty(dropEvent, "dataTransfer", {
|
|
77
|
+
value: {
|
|
78
|
+
files: [dataFile, extensionFile],
|
|
79
|
+
types: ["Files"],
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
globalThis.dispatchEvent(dropEvent);
|
|
84
|
+
await flushPromises();
|
|
85
|
+
|
|
86
|
+
expect(wrapper.emitted("files-selected")).toBeDefined();
|
|
87
|
+
expect(wrapper.emitted("files-selected")[0][0]).toStrictEqual([dataFile]);
|
|
88
|
+
});
|
|
89
|
+
});
|