@dragonmastery/dragoncore-vue 0.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.
- package/LICENSE +75 -0
- package/dist/AppLink-CHMMrSFI.js +54 -0
- package/dist/AppLink-CHMMrSFI.js.map +1 -0
- package/dist/Appearance-BfPdKMXw.js +70 -0
- package/dist/Appearance-BfPdKMXw.js.map +1 -0
- package/dist/Appearance-C3WguxT-.js +3 -0
- package/dist/ChangePasswordPage-Btu5lf-r.js +86 -0
- package/dist/ChangePasswordPage-Btu5lf-r.js.map +1 -0
- package/dist/ChangePasswordPage-mBBuQMkT.js +6 -0
- package/dist/CreateTeamForm-n2ut93vM.js +43 -0
- package/dist/CreateTeamMemberForm-CcH3AxNL.js +43 -0
- package/dist/CreateUserPage-CDrGuW9B.js +6 -0
- package/dist/CreateUserPage-Cmx8xjjv.js +76 -0
- package/dist/CreateUserPage-Cmx8xjjv.js.map +1 -0
- package/dist/CreditBalanceDashboard-DLz0ioP3.js +43 -0
- package/dist/CreditManagement-D3q5S-qc.js +43 -0
- package/dist/CustomerCreateSupportTicketForm-Ci7QYkG-.js +43 -0
- package/dist/CustomerEditSupportTicketForm-Dd5ZB74k.js +159 -0
- package/dist/CustomerEditSupportTicketForm-Dd5ZB74k.js.map +1 -0
- package/dist/CustomerEditSupportTicketForm-lLchVjnw.js +9 -0
- package/dist/CustomerSupportTicketAttachmentsTab-gBrVO97t.js +43 -0
- package/dist/CustomerSupportTicketCustomerNotesTab-D0jhzbOY.js +8 -0
- package/dist/CustomerSupportTicketCustomerNotesTab-D1aa9It7.js +23 -0
- package/dist/CustomerSupportTicketCustomerNotesTab-D1aa9It7.js.map +1 -0
- package/dist/CustomerSupportTicketHistoryTab-BNTf8EZq.js +6 -0
- package/dist/CustomerSupportTicketHistoryTab-CFYN_Sa4.js +17 -0
- package/dist/CustomerSupportTicketHistoryTab-CFYN_Sa4.js.map +1 -0
- package/dist/CustomerSupportTicketList-BkOzFxMP.js +6 -0
- package/dist/CustomerSupportTicketList-C2nUPawb.js +166 -0
- package/dist/CustomerSupportTicketList-C2nUPawb.js.map +1 -0
- package/dist/CustomerSupportTicketParent-2mONd9kL.js +66 -0
- package/dist/CustomerSupportTicketParent-2mONd9kL.js.map +1 -0
- package/dist/CustomerSupportTicketParent-N8ko1yFE.js +7 -0
- package/dist/CustomerSupportTicketSuccess-w_-9NXT4.js +43 -0
- package/dist/CustomerViewSupportTicket-CVwNH0lS.js +11 -0
- package/dist/CustomerViewSupportTicket-tZkxragu.js +363 -0
- package/dist/CustomerViewSupportTicket-tZkxragu.js.map +1 -0
- package/dist/EditTeamForm-BioqiTWE.js +43 -0
- package/dist/EditTeamMemberForm-DCq0Gsn_.js +7 -0
- package/dist/EditTeamMemberForm-ru4WgLz-.js +169 -0
- package/dist/EditTeamMemberForm-ru4WgLz-.js.map +1 -0
- package/dist/EditUserPage-BxJ5QvIM.js +112 -0
- package/dist/EditUserPage-BxJ5QvIM.js.map +1 -0
- package/dist/EditUserPage-XOBuxUxd.js +7 -0
- package/dist/FieldsetSection-CsHN38_o.js +27 -0
- package/dist/FieldsetSection-CsHN38_o.js.map +1 -0
- package/dist/ForgotPassword-CpqvcSFg.js +7 -0
- package/dist/ForgotPassword-CqhenzUG.js +73 -0
- package/dist/ForgotPassword-CqhenzUG.js.map +1 -0
- package/dist/InlineAttachments-I39rOvip.js +1351 -0
- package/dist/InlineAttachments-I39rOvip.js.map +1 -0
- package/dist/LoginForm-AM0qkfbU.js +7 -0
- package/dist/LoginForm-_PZ51Uwe.js +116 -0
- package/dist/LoginForm-_PZ51Uwe.js.map +1 -0
- package/dist/Logout-BMjiqHnS.js +38 -0
- package/dist/Logout-BMjiqHnS.js.map +1 -0
- package/dist/Logout-BfiBjlaH.js +6 -0
- package/dist/NoteList-C0hRPNMO.js +497 -0
- package/dist/NoteList-C0hRPNMO.js.map +1 -0
- package/dist/NotificationEmailsPage-BjRqtW95.js +141 -0
- package/dist/NotificationEmailsPage-BjRqtW95.js.map +1 -0
- package/dist/NotificationEmailsPage-bx-9rg3x.js +7 -0
- package/dist/ResetPassword-BQLkR9TZ.js +43 -0
- package/dist/Signup-CnCcQlB8.js +7 -0
- package/dist/Signup-c2-_yMOM.js +106 -0
- package/dist/Signup-c2-_yMOM.js.map +1 -0
- package/dist/StaffCreateSupportTicketForm-ChVFDJdA.js +43 -0
- package/dist/StaffEditSupportTicketForm-DY1Zkf5k.js +9 -0
- package/dist/StaffEditSupportTicketForm-DuUKuIGg.js +263 -0
- package/dist/StaffEditSupportTicketForm-DuUKuIGg.js.map +1 -0
- package/dist/StaffSupportTicketAttachmentsTab-DpDXsHXP.js +43 -0
- package/dist/StaffSupportTicketCustomerNotesTab-CusqQV2-.js +23 -0
- package/dist/StaffSupportTicketCustomerNotesTab-CusqQV2-.js.map +1 -0
- package/dist/StaffSupportTicketCustomerNotesTab-rbJHJ0_V.js +8 -0
- package/dist/StaffSupportTicketHistoryTab-D24myEm3.js +17 -0
- package/dist/StaffSupportTicketHistoryTab-D24myEm3.js.map +1 -0
- package/dist/StaffSupportTicketHistoryTab-nmVma5vp.js +6 -0
- package/dist/StaffSupportTicketInternalNotesTab-D8HM--dp.js +23 -0
- package/dist/StaffSupportTicketInternalNotesTab-D8HM--dp.js.map +1 -0
- package/dist/StaffSupportTicketInternalNotesTab-DihYd5XI.js +8 -0
- package/dist/StaffSupportTicketList-DelptSmK.js +43 -0
- package/dist/StaffSupportTicketParent-BCrj3ckV.js +7 -0
- package/dist/StaffSupportTicketParent-Cx1buQZw.js +66 -0
- package/dist/StaffSupportTicketParent-Cx1buQZw.js.map +1 -0
- package/dist/StaffSupportTicketSuccess-BYxtY5wZ.js +43 -0
- package/dist/StaffSupportTicketWorkflowTab-BrDDBeK9.js +9 -0
- package/dist/StaffSupportTicketWorkflowTab-DmVTPzxS.js +1234 -0
- package/dist/StaffSupportTicketWorkflowTab-DmVTPzxS.js.map +1 -0
- package/dist/SupportTicketHistoryTab-CLMopA7a.js +220 -0
- package/dist/SupportTicketHistoryTab-CLMopA7a.js.map +1 -0
- package/dist/SupportTicketStatusBadge-YdZzjvkh.js +163 -0
- package/dist/SupportTicketStatusBadge-YdZzjvkh.js.map +1 -0
- package/dist/TeamAttachmentsTab-BxUpTWYh.js +43 -0
- package/dist/TeamHistoryTab-CUCT9MRG.js +5 -0
- package/dist/TeamHistoryTab-gB3H2KZv.js +219 -0
- package/dist/TeamHistoryTab-gB3H2KZv.js.map +1 -0
- package/dist/TeamList-By6pzWm5.js +43 -0
- package/dist/TeamMemberList-CYV9fWEb.js +43 -0
- package/dist/TeamMemberParent-CVvGqpxD.js +43 -0
- package/dist/TeamMembersTab-4gmnP9sD.js +21 -0
- package/dist/TeamMembersTab-4gmnP9sD.js.map +1 -0
- package/dist/TeamMembersTab-CpE9BaCi.js +3 -0
- package/dist/TeamNotesTab-pfXTDhg6.js +23 -0
- package/dist/TeamNotesTab-pfXTDhg6.js.map +1 -0
- package/dist/TeamNotesTab-u4cDC67X.js +8 -0
- package/dist/TeamParent-BxT1KubK.js +43 -0
- package/dist/UserListPage-DsQdH2Sm.js +4 -0
- package/dist/UserListPage-WU56KiWj.js +153 -0
- package/dist/UserListPage-WU56KiWj.js.map +1 -0
- package/dist/UserProfilePage-B73JhjUu.js +7 -0
- package/dist/UserProfilePage-BtLUY1kt.js +125 -0
- package/dist/UserProfilePage-BtLUY1kt.js.map +1 -0
- package/dist/ViewTeam-DzX-obEl.js +43 -0
- package/dist/ViewTeamMember-PF6S_4Pb.js +43 -0
- package/dist/ZiniaContainer-C7c7Vwkh.js +18 -0
- package/dist/ZiniaContainer-C7c7Vwkh.js.map +1 -0
- package/dist/convertToLocalDateTime-D4IoNvRj.js +111 -0
- package/dist/convertToLocalDateTime-D4IoNvRj.js.map +1 -0
- package/dist/creditValueFormatter-DftEzu8d.js +128 -0
- package/dist/creditValueFormatter-DftEzu8d.js.map +1 -0
- package/dist/displayIdFormatter-Dz900Awr.js +13 -0
- package/dist/displayIdFormatter-Dz900Awr.js.map +1 -0
- package/dist/index.d.ts +6068 -0
- package/dist/index.js +45 -0
- package/dist/src-o5fMIo5_.js +6649 -0
- package/dist/src-o5fMIo5_.js.map +1 -0
- package/dist/useBreadcrumbs-DmgSucoe.js +41 -0
- package/dist/useBreadcrumbs-DmgSucoe.js.map +1 -0
- package/dist/useMutation-CFwe7H9j.js +50 -0
- package/dist/useMutation-CFwe7H9j.js.map +1 -0
- package/dist/useQuery-p7oJO7OD.js +107 -0
- package/dist/useQuery-p7oJO7OD.js.map +1 -0
- package/dist/useQueryCache-ByayvZgZ.js +254 -0
- package/dist/useQueryCache-ByayvZgZ.js.map +1 -0
- package/dist/useRpcAuth-BLlRSHy8.js +722 -0
- package/dist/useRpcAuth-BLlRSHy8.js.map +1 -0
- package/package.json +62 -0
- package/src/daisyui.css +63 -0
|
@@ -0,0 +1,1351 @@
|
|
|
1
|
+
import { l as useUserSessionStore, m as useEnv } from "./useRpcAuth-BLlRSHy8.js";
|
|
2
|
+
import { t as useMutation } from "./useMutation-CFwe7H9j.js";
|
|
3
|
+
import { t as useQuery } from "./useQuery-p7oJO7OD.js";
|
|
4
|
+
import { Fragment, computed, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, nextTick, normalizeClass, normalizeStyle, onMounted, onUnmounted, openBlock, ref, renderList, renderSlot, toDisplayString, unref, watch, withModifiers } from "vue";
|
|
5
|
+
|
|
6
|
+
//#region src/components/ConfirmDialog.vue
|
|
7
|
+
const _hoisted_1$2 = { class: "font-bold text-lg mb-4" };
|
|
8
|
+
const _hoisted_2$2 = { class: "py-4" };
|
|
9
|
+
const _hoisted_3$2 = { class: "modal-action" };
|
|
10
|
+
const _hoisted_4$2 = ["disabled"];
|
|
11
|
+
const _hoisted_5$2 = ["disabled"];
|
|
12
|
+
const _sfc_main$2 = /* @__PURE__ */ defineComponent({
|
|
13
|
+
__name: "ConfirmDialog",
|
|
14
|
+
props: {
|
|
15
|
+
modelValue: { type: Boolean },
|
|
16
|
+
title: { default: "Confirm" },
|
|
17
|
+
message: { default: "Are you sure?" },
|
|
18
|
+
confirmText: { default: "Confirm" },
|
|
19
|
+
cancelText: { default: "Cancel" },
|
|
20
|
+
processingText: { default: "Processing..." },
|
|
21
|
+
confirmButtonClass: { default: "btn-primary" },
|
|
22
|
+
isProcessing: {
|
|
23
|
+
type: Boolean,
|
|
24
|
+
default: false
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
emits: [
|
|
28
|
+
"update:modelValue",
|
|
29
|
+
"confirm",
|
|
30
|
+
"cancel"
|
|
31
|
+
],
|
|
32
|
+
setup(__props, { emit: __emit }) {
|
|
33
|
+
const props = __props;
|
|
34
|
+
const emit = __emit;
|
|
35
|
+
const dialogRef = ref(null);
|
|
36
|
+
const isOpen = ref(props.modelValue);
|
|
37
|
+
watch(() => props.modelValue, (newValue) => {
|
|
38
|
+
isOpen.value = newValue;
|
|
39
|
+
if (newValue && dialogRef.value) dialogRef.value.showModal();
|
|
40
|
+
else if (dialogRef.value) dialogRef.value.close();
|
|
41
|
+
}, { immediate: true });
|
|
42
|
+
watch(isOpen, (newValue) => {
|
|
43
|
+
if (newValue && dialogRef.value) dialogRef.value.showModal();
|
|
44
|
+
else if (dialogRef.value) dialogRef.value.close();
|
|
45
|
+
});
|
|
46
|
+
const handleConfirm = () => {
|
|
47
|
+
emit("confirm");
|
|
48
|
+
};
|
|
49
|
+
const handleCancel = () => {
|
|
50
|
+
isOpen.value = false;
|
|
51
|
+
emit("update:modelValue", false);
|
|
52
|
+
emit("cancel");
|
|
53
|
+
};
|
|
54
|
+
const handleBackdropClick = (event) => {
|
|
55
|
+
if (event.target === dialogRef.value) handleCancel();
|
|
56
|
+
};
|
|
57
|
+
return (_ctx, _cache) => {
|
|
58
|
+
return openBlock(), createElementBlock("dialog", {
|
|
59
|
+
ref_key: "dialogRef",
|
|
60
|
+
ref: dialogRef,
|
|
61
|
+
class: normalizeClass(["modal", { "modal-open": isOpen.value }]),
|
|
62
|
+
onClick: handleBackdropClick
|
|
63
|
+
}, [createElementVNode("div", {
|
|
64
|
+
class: "modal-box",
|
|
65
|
+
onClick: _cache[0] || (_cache[0] = withModifiers(() => {}, ["stop"]))
|
|
66
|
+
}, [
|
|
67
|
+
createElementVNode("h3", _hoisted_1$2, toDisplayString(__props.title), 1),
|
|
68
|
+
createElementVNode("div", _hoisted_2$2, [renderSlot(_ctx.$slots, "message", {}, () => [createElementVNode("p", null, toDisplayString(__props.message), 1)])]),
|
|
69
|
+
createElementVNode("div", _hoisted_3$2, [createElementVNode("button", {
|
|
70
|
+
class: "btn btn-outline",
|
|
71
|
+
onClick: withModifiers(handleCancel, ["prevent"]),
|
|
72
|
+
disabled: __props.isProcessing,
|
|
73
|
+
type: "button"
|
|
74
|
+
}, toDisplayString(__props.cancelText), 9, _hoisted_4$2), createElementVNode("button", {
|
|
75
|
+
class: normalizeClass(["btn", __props.confirmButtonClass]),
|
|
76
|
+
onClick: withModifiers(handleConfirm, ["prevent"]),
|
|
77
|
+
disabled: __props.isProcessing,
|
|
78
|
+
type: "button"
|
|
79
|
+
}, toDisplayString(__props.isProcessing ? __props.processingText : __props.confirmText), 11, _hoisted_5$2)])
|
|
80
|
+
])], 2);
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
var ConfirmDialog_default = _sfc_main$2;
|
|
85
|
+
|
|
86
|
+
//#endregion
|
|
87
|
+
//#region src/components/ImageModal.vue
|
|
88
|
+
const _hoisted_1$1 = { class: "modal-box w-full max-w-full h-full max-h-full p-0 flex flex-col bg-base-100" };
|
|
89
|
+
const _hoisted_2$1 = { class: "flex items-center justify-between p-3 sm:p-4 border-b border-base-300 flex-shrink-0 bg-base-100/95 backdrop-blur" };
|
|
90
|
+
const _hoisted_3$1 = { class: "flex-1 min-w-0 mr-2" };
|
|
91
|
+
const _hoisted_4$1 = { class: "font-semibold text-sm sm:text-base truncate" };
|
|
92
|
+
const _hoisted_5$1 = {
|
|
93
|
+
key: 0,
|
|
94
|
+
class: "text-xs text-base-content/60 mt-0.5"
|
|
95
|
+
};
|
|
96
|
+
const _hoisted_6$1 = { class: "flex items-center gap-1 sm:gap-2 flex-shrink-0" };
|
|
97
|
+
const _hoisted_7$1 = {
|
|
98
|
+
key: 0,
|
|
99
|
+
class: "absolute inset-0 flex items-center justify-center"
|
|
100
|
+
};
|
|
101
|
+
const _hoisted_8$1 = {
|
|
102
|
+
key: 1,
|
|
103
|
+
class: "absolute inset-0 flex flex-col items-center justify-center p-4 text-center"
|
|
104
|
+
};
|
|
105
|
+
const _hoisted_9$1 = ["src", "alt"];
|
|
106
|
+
const _hoisted_10$1 = ["disabled"];
|
|
107
|
+
const _hoisted_11$1 = ["disabled"];
|
|
108
|
+
const _hoisted_12$1 = {
|
|
109
|
+
method: "dialog",
|
|
110
|
+
class: "modal-backdrop"
|
|
111
|
+
};
|
|
112
|
+
const minScale = .5;
|
|
113
|
+
const maxScale = 5;
|
|
114
|
+
const zoomStep = .05;
|
|
115
|
+
const _sfc_main$1 = /* @__PURE__ */ defineComponent({
|
|
116
|
+
__name: "ImageModal",
|
|
117
|
+
props: {
|
|
118
|
+
isOpen: { type: Boolean },
|
|
119
|
+
imageSrc: { default: "" },
|
|
120
|
+
imageName: { default: "" },
|
|
121
|
+
imageIndex: { default: null },
|
|
122
|
+
totalImages: { default: null },
|
|
123
|
+
onDownload: {
|
|
124
|
+
type: Function,
|
|
125
|
+
default: void 0
|
|
126
|
+
},
|
|
127
|
+
onPrevious: {
|
|
128
|
+
type: Function,
|
|
129
|
+
default: void 0
|
|
130
|
+
},
|
|
131
|
+
onNext: {
|
|
132
|
+
type: Function,
|
|
133
|
+
default: void 0
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
emits: ["close"],
|
|
137
|
+
setup(__props, { emit: __emit }) {
|
|
138
|
+
const props = __props;
|
|
139
|
+
const emit = __emit;
|
|
140
|
+
const dialogRef = ref(null);
|
|
141
|
+
const imageRef = ref(null);
|
|
142
|
+
const imageContainerRef = ref(null);
|
|
143
|
+
const isOpen = ref(props.isOpen);
|
|
144
|
+
const isLoading = ref(false);
|
|
145
|
+
const hasError = ref(false);
|
|
146
|
+
const scale = ref(1);
|
|
147
|
+
const translateX = ref(0);
|
|
148
|
+
const translateY = ref(0);
|
|
149
|
+
const rotation = ref(0);
|
|
150
|
+
const isDragging = ref(false);
|
|
151
|
+
const isPinching = ref(false);
|
|
152
|
+
const isResetting = ref(false);
|
|
153
|
+
const touchState = ref(null);
|
|
154
|
+
const mouseDragState = ref(null);
|
|
155
|
+
const isMobile = ref(false);
|
|
156
|
+
const imageStyle = computed(() => ({
|
|
157
|
+
transform: `scale(${scale.value}) translate(${translateX.value}px, ${translateY.value}px) rotate(${rotation.value}deg)`,
|
|
158
|
+
cursor: mouseDragState.value?.isDragging ? "grabbing" : scale.value > 1 ? "grab" : "default",
|
|
159
|
+
touchAction: "none",
|
|
160
|
+
userSelect: "none",
|
|
161
|
+
willChange: isDragging.value || isPinching.value ? "transform" : "auto"
|
|
162
|
+
}));
|
|
163
|
+
const showNavigation = computed(() => {
|
|
164
|
+
return props.totalImages !== null && props.totalImages > 1 && (props.onPrevious || props.onNext);
|
|
165
|
+
});
|
|
166
|
+
watch(() => props.isOpen, (newValue) => {
|
|
167
|
+
isOpen.value = newValue;
|
|
168
|
+
if (newValue && dialogRef.value) {
|
|
169
|
+
dialogRef.value.showModal();
|
|
170
|
+
isResetting.value = true;
|
|
171
|
+
scale.value = 1;
|
|
172
|
+
translateX.value = 0;
|
|
173
|
+
translateY.value = 0;
|
|
174
|
+
rotation.value = 0;
|
|
175
|
+
touchState.value = null;
|
|
176
|
+
mouseDragState.value = null;
|
|
177
|
+
isDragging.value = false;
|
|
178
|
+
isPinching.value = false;
|
|
179
|
+
isLoading.value = true;
|
|
180
|
+
hasError.value = false;
|
|
181
|
+
nextTick(() => {
|
|
182
|
+
setTimeout(() => {
|
|
183
|
+
isResetting.value = false;
|
|
184
|
+
}, 50);
|
|
185
|
+
});
|
|
186
|
+
nextTick(() => {
|
|
187
|
+
if (imageContainerRef.value) {
|
|
188
|
+
imageContainerRef.value.addEventListener("wheel", handleWheel, { passive: false });
|
|
189
|
+
imageContainerRef.value.addEventListener("mousedown", handleMouseDown);
|
|
190
|
+
imageContainerRef.value.addEventListener("touchstart", handleTouchStart, { passive: false });
|
|
191
|
+
imageContainerRef.value.addEventListener("touchmove", handleTouchMove, { passive: false });
|
|
192
|
+
imageContainerRef.value.addEventListener("touchend", handleTouchEnd, { passive: true });
|
|
193
|
+
imageContainerRef.value.addEventListener("touchcancel", handleTouchEnd, { passive: true });
|
|
194
|
+
}
|
|
195
|
+
document.addEventListener("mousemove", handleMouseMove);
|
|
196
|
+
document.addEventListener("mouseup", handleMouseUp);
|
|
197
|
+
});
|
|
198
|
+
} else if (dialogRef.value) {
|
|
199
|
+
dialogRef.value.close();
|
|
200
|
+
if (imageContainerRef.value) {
|
|
201
|
+
imageContainerRef.value.removeEventListener("wheel", handleWheel);
|
|
202
|
+
imageContainerRef.value.removeEventListener("mousedown", handleMouseDown);
|
|
203
|
+
imageContainerRef.value.removeEventListener("touchstart", handleTouchStart);
|
|
204
|
+
imageContainerRef.value.removeEventListener("touchmove", handleTouchMove);
|
|
205
|
+
imageContainerRef.value.removeEventListener("touchend", handleTouchEnd);
|
|
206
|
+
imageContainerRef.value.removeEventListener("touchcancel", handleTouchEnd);
|
|
207
|
+
}
|
|
208
|
+
document.removeEventListener("mousemove", handleMouseMove);
|
|
209
|
+
document.removeEventListener("mouseup", handleMouseUp);
|
|
210
|
+
touchState.value = null;
|
|
211
|
+
mouseDragState.value = null;
|
|
212
|
+
}
|
|
213
|
+
}, { immediate: true });
|
|
214
|
+
watch(() => props.imageSrc, () => {
|
|
215
|
+
if (props.isOpen && props.imageSrc) {
|
|
216
|
+
isLoading.value = true;
|
|
217
|
+
hasError.value = false;
|
|
218
|
+
scale.value = 1;
|
|
219
|
+
translateX.value = 0;
|
|
220
|
+
translateY.value = 0;
|
|
221
|
+
rotation.value = 0;
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
watch(() => props.imageIndex, () => {
|
|
225
|
+
if (props.isOpen) {
|
|
226
|
+
resetZoom();
|
|
227
|
+
isLoading.value = true;
|
|
228
|
+
hasError.value = false;
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
onMounted(() => {
|
|
232
|
+
isMobile.value = window.innerWidth < 768;
|
|
233
|
+
window.addEventListener("resize", () => {
|
|
234
|
+
isMobile.value = window.innerWidth < 768;
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
const handleKeydown = (event) => {
|
|
238
|
+
if (!props.isOpen) return;
|
|
239
|
+
switch (event.key) {
|
|
240
|
+
case "Escape":
|
|
241
|
+
handleClose();
|
|
242
|
+
break;
|
|
243
|
+
case "ArrowLeft":
|
|
244
|
+
if (showNavigation.value && props.imageIndex !== null && props.imageIndex > 0) {
|
|
245
|
+
event.preventDefault();
|
|
246
|
+
goToPrevious();
|
|
247
|
+
}
|
|
248
|
+
break;
|
|
249
|
+
case "ArrowRight":
|
|
250
|
+
if (showNavigation.value && props.imageIndex !== null && props.imageIndex < (props.totalImages || 0) - 1) {
|
|
251
|
+
event.preventDefault();
|
|
252
|
+
goToNext();
|
|
253
|
+
}
|
|
254
|
+
break;
|
|
255
|
+
case "+":
|
|
256
|
+
case "=":
|
|
257
|
+
event.preventDefault();
|
|
258
|
+
zoomIn();
|
|
259
|
+
break;
|
|
260
|
+
case "-":
|
|
261
|
+
event.preventDefault();
|
|
262
|
+
zoomOut();
|
|
263
|
+
break;
|
|
264
|
+
case "0":
|
|
265
|
+
event.preventDefault();
|
|
266
|
+
resetZoom();
|
|
267
|
+
break;
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
const zoomIn = () => {
|
|
271
|
+
if (scale.value < maxScale) {
|
|
272
|
+
const newScale = scale.value + zoomStep;
|
|
273
|
+
scale.value = Math.min(maxScale, newScale);
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
const zoomOut = () => {
|
|
277
|
+
if (scale.value > minScale) {
|
|
278
|
+
const newScale = scale.value - zoomStep;
|
|
279
|
+
scale.value = Math.max(minScale, newScale);
|
|
280
|
+
if (scale.value <= 1) {
|
|
281
|
+
translateX.value = 0;
|
|
282
|
+
translateY.value = 0;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
const resetZoom = () => {
|
|
287
|
+
isResetting.value = true;
|
|
288
|
+
scale.value = 1;
|
|
289
|
+
translateX.value = 0;
|
|
290
|
+
translateY.value = 0;
|
|
291
|
+
rotation.value = 0;
|
|
292
|
+
touchState.value = null;
|
|
293
|
+
mouseDragState.value = null;
|
|
294
|
+
isDragging.value = false;
|
|
295
|
+
isPinching.value = false;
|
|
296
|
+
nextTick(() => {
|
|
297
|
+
setTimeout(() => {
|
|
298
|
+
isResetting.value = false;
|
|
299
|
+
}, 50);
|
|
300
|
+
});
|
|
301
|
+
};
|
|
302
|
+
const rotateImage = () => {
|
|
303
|
+
rotation.value = rotation.value + 90;
|
|
304
|
+
};
|
|
305
|
+
const handleWheel = (event) => {
|
|
306
|
+
if (!imageRef.value || !props.isOpen) return;
|
|
307
|
+
event.preventDefault();
|
|
308
|
+
const scrollDirection = (event.deltaY !== 0 ? event.deltaY : event.deltaX) > 0 ? -1 : 1;
|
|
309
|
+
const oldScale = scale.value;
|
|
310
|
+
const newScale = scrollDirection > 0 ? oldScale + zoomStep : oldScale - zoomStep;
|
|
311
|
+
const clampedScale = Math.max(minScale, Math.min(maxScale, newScale));
|
|
312
|
+
if (Math.abs(clampedScale - oldScale) > .001) {
|
|
313
|
+
const containerRect = imageContainerRef.value?.getBoundingClientRect();
|
|
314
|
+
if (!containerRect) return;
|
|
315
|
+
const mouseX = event.clientX - containerRect.left - containerRect.width / 2;
|
|
316
|
+
const mouseY = event.clientY - containerRect.top - containerRect.height / 2;
|
|
317
|
+
const imagePointX = (mouseX - translateX.value) / oldScale;
|
|
318
|
+
const imagePointY = (mouseY - translateY.value) / oldScale;
|
|
319
|
+
scale.value = clampedScale;
|
|
320
|
+
translateX.value = mouseX - imagePointX * clampedScale;
|
|
321
|
+
translateY.value = mouseY - imagePointY * clampedScale;
|
|
322
|
+
if (scale.value <= 1) {
|
|
323
|
+
translateX.value = 0;
|
|
324
|
+
translateY.value = 0;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
};
|
|
328
|
+
const handleMouseDown = (event) => {
|
|
329
|
+
if (!imageRef.value || !props.isOpen || scale.value <= 1) return;
|
|
330
|
+
if (event.button !== 0) return;
|
|
331
|
+
event.preventDefault();
|
|
332
|
+
isDragging.value = true;
|
|
333
|
+
mouseDragState.value = {
|
|
334
|
+
isDragging: true,
|
|
335
|
+
startX: event.clientX,
|
|
336
|
+
startY: event.clientY,
|
|
337
|
+
startTranslateX: translateX.value,
|
|
338
|
+
startTranslateY: translateY.value
|
|
339
|
+
};
|
|
340
|
+
if (imageRef.value) imageRef.value.style.cursor = "grabbing";
|
|
341
|
+
};
|
|
342
|
+
const handleMouseMove = (event) => {
|
|
343
|
+
if (!mouseDragState.value || !mouseDragState.value.isDragging || !props.isOpen) return;
|
|
344
|
+
event.preventDefault();
|
|
345
|
+
requestAnimationFrame(() => {
|
|
346
|
+
if (!mouseDragState.value) return;
|
|
347
|
+
const deltaX = event.clientX - mouseDragState.value.startX;
|
|
348
|
+
const deltaY = event.clientY - mouseDragState.value.startY;
|
|
349
|
+
translateX.value = mouseDragState.value.startTranslateX + deltaX / scale.value;
|
|
350
|
+
translateY.value = mouseDragState.value.startTranslateY + deltaY / scale.value;
|
|
351
|
+
const maxTranslate = 300;
|
|
352
|
+
translateX.value = Math.max(-maxTranslate, Math.min(maxTranslate, translateX.value));
|
|
353
|
+
translateY.value = Math.max(-maxTranslate, Math.min(maxTranslate, translateY.value));
|
|
354
|
+
});
|
|
355
|
+
};
|
|
356
|
+
const handleMouseUp = () => {
|
|
357
|
+
if (mouseDragState.value) mouseDragState.value.isDragging = false;
|
|
358
|
+
mouseDragState.value = null;
|
|
359
|
+
isDragging.value = false;
|
|
360
|
+
if (imageRef.value) imageRef.value.style.cursor = scale.value > 1 ? "grab" : "default";
|
|
361
|
+
};
|
|
362
|
+
const getTouchDistance = (touches) => {
|
|
363
|
+
if (touches.length < 2) return 0;
|
|
364
|
+
const touch1 = touches[0];
|
|
365
|
+
const touch2 = touches[1];
|
|
366
|
+
if (!touch1 || !touch2) return 0;
|
|
367
|
+
const dx = touch1.clientX - touch2.clientX;
|
|
368
|
+
const dy = touch1.clientY - touch2.clientY;
|
|
369
|
+
return Math.sqrt(dx * dx + dy * dy);
|
|
370
|
+
};
|
|
371
|
+
const getTouchCenter = (touches) => {
|
|
372
|
+
if (touches.length === 0) return null;
|
|
373
|
+
if (touches.length === 1) {
|
|
374
|
+
const touch = touches[0];
|
|
375
|
+
if (!touch) return null;
|
|
376
|
+
return {
|
|
377
|
+
x: touch.clientX,
|
|
378
|
+
y: touch.clientY
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
const touch1 = touches[0];
|
|
382
|
+
const touch2 = touches[1];
|
|
383
|
+
if (!touch1 || !touch2) return null;
|
|
384
|
+
return {
|
|
385
|
+
x: (touch1.clientX + touch2.clientX) / 2,
|
|
386
|
+
y: (touch1.clientY + touch2.clientY) / 2
|
|
387
|
+
};
|
|
388
|
+
};
|
|
389
|
+
const handleImageTouchStart = (event) => {
|
|
390
|
+
if (!imageRef.value || !props.isOpen) return;
|
|
391
|
+
event.preventDefault();
|
|
392
|
+
event.stopPropagation();
|
|
393
|
+
const touches = Array.from(event.touches);
|
|
394
|
+
if (touches.length === 2) {
|
|
395
|
+
isPinching.value = true;
|
|
396
|
+
const distance = getTouchDistance(touches);
|
|
397
|
+
if (getTouchCenter(touches)) touchState.value = {
|
|
398
|
+
type: "pinch",
|
|
399
|
+
initialDistance: distance,
|
|
400
|
+
initialScale: scale.value,
|
|
401
|
+
initialTranslateX: translateX.value,
|
|
402
|
+
initialTranslateY: translateY.value,
|
|
403
|
+
initialTouches: touches
|
|
404
|
+
};
|
|
405
|
+
} else if (touches.length === 1 && scale.value > 1) {
|
|
406
|
+
isDragging.value = true;
|
|
407
|
+
const touch = touches[0];
|
|
408
|
+
if (touch) touchState.value = {
|
|
409
|
+
type: "pan",
|
|
410
|
+
lastPanX: touch.clientX,
|
|
411
|
+
lastPanY: touch.clientY
|
|
412
|
+
};
|
|
413
|
+
} else {
|
|
414
|
+
touchState.value = null;
|
|
415
|
+
isDragging.value = false;
|
|
416
|
+
isPinching.value = false;
|
|
417
|
+
}
|
|
418
|
+
};
|
|
419
|
+
const handleImageTouchMove = (event) => {
|
|
420
|
+
if (!imageRef.value || !props.isOpen || !touchState.value) return;
|
|
421
|
+
event.preventDefault();
|
|
422
|
+
event.stopPropagation();
|
|
423
|
+
handleTouchMove(event);
|
|
424
|
+
};
|
|
425
|
+
const handleImageTouchEnd = (event) => {
|
|
426
|
+
if (!imageRef.value || !props.isOpen) return;
|
|
427
|
+
event.preventDefault();
|
|
428
|
+
event.stopPropagation();
|
|
429
|
+
handleTouchEnd();
|
|
430
|
+
};
|
|
431
|
+
const handleTouchStart = (event) => {
|
|
432
|
+
if (!imageRef.value || !props.isOpen) return;
|
|
433
|
+
const target = event.target;
|
|
434
|
+
if (target.closest(".zoom-controls")) return;
|
|
435
|
+
if (target === imageRef.value || imageRef.value.contains(target)) return;
|
|
436
|
+
event.preventDefault();
|
|
437
|
+
const touches = Array.from(event.touches);
|
|
438
|
+
if (touches.length === 2) {
|
|
439
|
+
isPinching.value = true;
|
|
440
|
+
const distance = getTouchDistance(touches);
|
|
441
|
+
if (getTouchCenter(touches)) touchState.value = {
|
|
442
|
+
type: "pinch",
|
|
443
|
+
initialDistance: distance,
|
|
444
|
+
initialScale: scale.value,
|
|
445
|
+
initialTranslateX: translateX.value,
|
|
446
|
+
initialTranslateY: translateY.value,
|
|
447
|
+
initialTouches: touches
|
|
448
|
+
};
|
|
449
|
+
} else if (touches.length === 1 && scale.value > 1) {
|
|
450
|
+
isDragging.value = true;
|
|
451
|
+
const touch = touches[0];
|
|
452
|
+
if (touch) touchState.value = {
|
|
453
|
+
type: "pan",
|
|
454
|
+
lastPanX: touch.clientX,
|
|
455
|
+
lastPanY: touch.clientY
|
|
456
|
+
};
|
|
457
|
+
} else {
|
|
458
|
+
touchState.value = null;
|
|
459
|
+
isDragging.value = false;
|
|
460
|
+
isPinching.value = false;
|
|
461
|
+
}
|
|
462
|
+
};
|
|
463
|
+
const handleTouchMove = (event) => {
|
|
464
|
+
if (!imageRef.value || !props.isOpen || !touchState.value) return;
|
|
465
|
+
const touches = Array.from(event.touches);
|
|
466
|
+
requestAnimationFrame(() => {
|
|
467
|
+
if (!touchState.value) return;
|
|
468
|
+
if (touchState.value.type === "pinch" && touches.length === 2) {
|
|
469
|
+
event.preventDefault();
|
|
470
|
+
const distance = getTouchDistance(touches);
|
|
471
|
+
if (touchState.value.initialDistance && touchState.value.initialScale !== void 0) {
|
|
472
|
+
const scaleChange = distance / touchState.value.initialDistance;
|
|
473
|
+
scale.value = Math.max(minScale, Math.min(maxScale, touchState.value.initialScale * scaleChange));
|
|
474
|
+
}
|
|
475
|
+
} else if (touchState.value.type === "pan" && touches.length === 1 && scale.value > 1) {
|
|
476
|
+
event.preventDefault();
|
|
477
|
+
const touch = touches[0];
|
|
478
|
+
if (touch && touchState.value.lastPanX !== void 0 && touchState.value.lastPanY !== void 0) {
|
|
479
|
+
const deltaX = touch.clientX - touchState.value.lastPanX;
|
|
480
|
+
const deltaY = touch.clientY - touchState.value.lastPanY;
|
|
481
|
+
translateX.value += deltaX / scale.value;
|
|
482
|
+
translateY.value += deltaY / scale.value;
|
|
483
|
+
const maxTranslate = 200;
|
|
484
|
+
translateX.value = Math.max(-maxTranslate, Math.min(maxTranslate, translateX.value));
|
|
485
|
+
translateY.value = Math.max(-maxTranslate, Math.min(maxTranslate, translateY.value));
|
|
486
|
+
touchState.value.lastPanX = touch.clientX;
|
|
487
|
+
touchState.value.lastPanY = touch.clientY;
|
|
488
|
+
}
|
|
489
|
+
} else {
|
|
490
|
+
touchState.value = null;
|
|
491
|
+
isDragging.value = false;
|
|
492
|
+
isPinching.value = false;
|
|
493
|
+
}
|
|
494
|
+
});
|
|
495
|
+
};
|
|
496
|
+
const handleTouchEnd = () => {
|
|
497
|
+
touchState.value = null;
|
|
498
|
+
isDragging.value = false;
|
|
499
|
+
isPinching.value = false;
|
|
500
|
+
};
|
|
501
|
+
const handleImageLoad = () => {
|
|
502
|
+
isLoading.value = false;
|
|
503
|
+
hasError.value = false;
|
|
504
|
+
};
|
|
505
|
+
const handleImageError = () => {
|
|
506
|
+
isLoading.value = false;
|
|
507
|
+
hasError.value = true;
|
|
508
|
+
};
|
|
509
|
+
const goToPrevious = () => {
|
|
510
|
+
if (props.onPrevious) props.onPrevious();
|
|
511
|
+
};
|
|
512
|
+
const goToNext = () => {
|
|
513
|
+
if (props.onNext) props.onNext();
|
|
514
|
+
};
|
|
515
|
+
const handleDownload = () => {
|
|
516
|
+
if (props.onDownload) props.onDownload();
|
|
517
|
+
};
|
|
518
|
+
const handleClose = () => {
|
|
519
|
+
isOpen.value = false;
|
|
520
|
+
emit("close");
|
|
521
|
+
};
|
|
522
|
+
const handleBackdropClick = (event) => {
|
|
523
|
+
if (event.target === dialogRef.value) handleClose();
|
|
524
|
+
};
|
|
525
|
+
onUnmounted(() => {
|
|
526
|
+
window.removeEventListener("resize", () => {});
|
|
527
|
+
if (imageContainerRef.value) {
|
|
528
|
+
imageContainerRef.value.removeEventListener("wheel", handleWheel);
|
|
529
|
+
imageContainerRef.value.removeEventListener("mousedown", handleMouseDown);
|
|
530
|
+
imageContainerRef.value.removeEventListener("touchstart", handleTouchStart);
|
|
531
|
+
imageContainerRef.value.removeEventListener("touchmove", handleTouchMove);
|
|
532
|
+
imageContainerRef.value.removeEventListener("touchend", handleTouchEnd);
|
|
533
|
+
imageContainerRef.value.removeEventListener("touchcancel", handleTouchEnd);
|
|
534
|
+
}
|
|
535
|
+
document.removeEventListener("mousemove", handleMouseMove);
|
|
536
|
+
document.removeEventListener("mouseup", handleMouseUp);
|
|
537
|
+
});
|
|
538
|
+
return (_ctx, _cache) => {
|
|
539
|
+
return openBlock(), createElementBlock("dialog", {
|
|
540
|
+
ref_key: "dialogRef",
|
|
541
|
+
ref: dialogRef,
|
|
542
|
+
class: normalizeClass(["modal", { "modal-open": isOpen.value }]),
|
|
543
|
+
onClick: handleBackdropClick,
|
|
544
|
+
onKeydown: handleKeydown
|
|
545
|
+
}, [createElementVNode("div", _hoisted_1$1, [
|
|
546
|
+
createCommentVNode(" Header "),
|
|
547
|
+
createElementVNode("div", _hoisted_2$1, [createElementVNode("div", _hoisted_3$1, [createElementVNode("h3", _hoisted_4$1, toDisplayString(__props.imageName), 1), __props.imageIndex !== null && __props.totalImages !== null ? (openBlock(), createElementBlock("p", _hoisted_5$1, toDisplayString(__props.imageIndex + 1) + " / " + toDisplayString(__props.totalImages), 1)) : createCommentVNode("v-if", true)]), createElementVNode("div", _hoisted_6$1, [
|
|
548
|
+
createCommentVNode(" Navigation buttons (if multiple images) "),
|
|
549
|
+
showNavigation.value && __props.imageIndex !== null && __props.imageIndex > 0 ? (openBlock(), createElementBlock("button", {
|
|
550
|
+
key: 0,
|
|
551
|
+
onClick: withModifiers(goToPrevious, ["prevent"]),
|
|
552
|
+
class: "btn btn-sm btn-circle btn-ghost",
|
|
553
|
+
type: "button",
|
|
554
|
+
title: "Previous image",
|
|
555
|
+
"aria-label": "Previous image"
|
|
556
|
+
}, [..._cache[4] || (_cache[4] = [createElementVNode("svg", {
|
|
557
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
558
|
+
class: "h-5 w-5",
|
|
559
|
+
fill: "none",
|
|
560
|
+
viewBox: "0 0 24 24",
|
|
561
|
+
stroke: "currentColor"
|
|
562
|
+
}, [createElementVNode("path", {
|
|
563
|
+
"stroke-linecap": "round",
|
|
564
|
+
"stroke-linejoin": "round",
|
|
565
|
+
"stroke-width": "2",
|
|
566
|
+
d: "M15 19l-7-7 7-7"
|
|
567
|
+
})], -1)])])) : createCommentVNode("v-if", true),
|
|
568
|
+
showNavigation.value && __props.imageIndex !== null && __props.imageIndex < (__props.totalImages || 0) - 1 ? (openBlock(), createElementBlock("button", {
|
|
569
|
+
key: 1,
|
|
570
|
+
onClick: withModifiers(goToNext, ["prevent"]),
|
|
571
|
+
class: "btn btn-sm btn-circle btn-ghost",
|
|
572
|
+
type: "button",
|
|
573
|
+
title: "Next image",
|
|
574
|
+
"aria-label": "Next image"
|
|
575
|
+
}, [..._cache[5] || (_cache[5] = [createElementVNode("svg", {
|
|
576
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
577
|
+
class: "h-5 w-5",
|
|
578
|
+
fill: "none",
|
|
579
|
+
viewBox: "0 0 24 24",
|
|
580
|
+
stroke: "currentColor"
|
|
581
|
+
}, [createElementVNode("path", {
|
|
582
|
+
"stroke-linecap": "round",
|
|
583
|
+
"stroke-linejoin": "round",
|
|
584
|
+
"stroke-width": "2",
|
|
585
|
+
d: "M9 5l7 7-7 7"
|
|
586
|
+
})], -1)])])) : createCommentVNode("v-if", true),
|
|
587
|
+
createCommentVNode(" Download button "),
|
|
588
|
+
props.onDownload ? (openBlock(), createElementBlock("button", {
|
|
589
|
+
key: 2,
|
|
590
|
+
onClick: withModifiers(handleDownload, ["prevent"]),
|
|
591
|
+
class: "btn btn-sm btn-circle btn-ghost",
|
|
592
|
+
type: "button",
|
|
593
|
+
title: "Download",
|
|
594
|
+
"aria-label": "Download image"
|
|
595
|
+
}, [..._cache[6] || (_cache[6] = [createElementVNode("svg", {
|
|
596
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
597
|
+
class: "h-5 w-5",
|
|
598
|
+
fill: "none",
|
|
599
|
+
viewBox: "0 0 24 24",
|
|
600
|
+
stroke: "currentColor"
|
|
601
|
+
}, [createElementVNode("path", {
|
|
602
|
+
"stroke-linecap": "round",
|
|
603
|
+
"stroke-linejoin": "round",
|
|
604
|
+
"stroke-width": "2",
|
|
605
|
+
d: "M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"
|
|
606
|
+
})], -1)])])) : createCommentVNode("v-if", true),
|
|
607
|
+
createCommentVNode(" Close button "),
|
|
608
|
+
createElementVNode("button", {
|
|
609
|
+
onClick: withModifiers(handleClose, ["prevent"]),
|
|
610
|
+
class: "btn btn-sm btn-circle btn-ghost",
|
|
611
|
+
type: "button",
|
|
612
|
+
title: "Close",
|
|
613
|
+
"aria-label": "Close modal"
|
|
614
|
+
}, [..._cache[7] || (_cache[7] = [createElementVNode("svg", {
|
|
615
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
616
|
+
class: "h-5 w-5",
|
|
617
|
+
fill: "none",
|
|
618
|
+
viewBox: "0 0 24 24",
|
|
619
|
+
stroke: "currentColor"
|
|
620
|
+
}, [createElementVNode("path", {
|
|
621
|
+
"stroke-linecap": "round",
|
|
622
|
+
"stroke-linejoin": "round",
|
|
623
|
+
"stroke-width": "2",
|
|
624
|
+
d: "M6 18L18 6M6 6l12 12"
|
|
625
|
+
})], -1)])])
|
|
626
|
+
])]),
|
|
627
|
+
createCommentVNode(" Image container "),
|
|
628
|
+
createElementVNode("div", {
|
|
629
|
+
ref_key: "imageContainerRef",
|
|
630
|
+
ref: imageContainerRef,
|
|
631
|
+
class: "flex-1 flex items-center justify-center overflow-hidden bg-base-200 relative"
|
|
632
|
+
}, [
|
|
633
|
+
createCommentVNode(" Loading state "),
|
|
634
|
+
isLoading.value ? (openBlock(), createElementBlock("div", _hoisted_7$1, [..._cache[8] || (_cache[8] = [createElementVNode("span", { class: "loading loading-spinner loading-lg" }, null, -1)])])) : createCommentVNode("v-if", true),
|
|
635
|
+
createCommentVNode(" Error state "),
|
|
636
|
+
hasError.value ? (openBlock(), createElementBlock("div", _hoisted_8$1, [..._cache[9] || (_cache[9] = [createElementVNode("svg", {
|
|
637
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
638
|
+
class: "h-12 w-12 text-error mb-2",
|
|
639
|
+
fill: "none",
|
|
640
|
+
viewBox: "0 0 24 24",
|
|
641
|
+
stroke: "currentColor"
|
|
642
|
+
}, [createElementVNode("path", {
|
|
643
|
+
"stroke-linecap": "round",
|
|
644
|
+
"stroke-linejoin": "round",
|
|
645
|
+
"stroke-width": "2",
|
|
646
|
+
d: "M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
|
|
647
|
+
})], -1), createElementVNode("p", { class: "text-sm text-base-content/70" }, "Failed to load image", -1)])])) : createCommentVNode("v-if", true),
|
|
648
|
+
createCommentVNode(" Image "),
|
|
649
|
+
__props.imageSrc && !hasError.value ? (openBlock(), createElementBlock("img", {
|
|
650
|
+
key: 2,
|
|
651
|
+
ref_key: "imageRef",
|
|
652
|
+
ref: imageRef,
|
|
653
|
+
src: __props.imageSrc,
|
|
654
|
+
alt: __props.imageName,
|
|
655
|
+
class: normalizeClass(["max-w-full max-h-full object-contain", { "transition-transform duration-300 ease-out": !isDragging.value && !isPinching.value && !isResetting.value }]),
|
|
656
|
+
style: normalizeStyle(imageStyle.value),
|
|
657
|
+
onLoad: handleImageLoad,
|
|
658
|
+
onError: handleImageError,
|
|
659
|
+
onDblclick: resetZoom,
|
|
660
|
+
onTouchstart: withModifiers(handleImageTouchStart, ["stop"]),
|
|
661
|
+
onTouchmove: withModifiers(handleImageTouchMove, ["stop"]),
|
|
662
|
+
onTouchend: withModifiers(handleImageTouchEnd, ["stop"])
|
|
663
|
+
}, null, 46, _hoisted_9$1)) : createCommentVNode("v-if", true),
|
|
664
|
+
createCommentVNode(" Zoom controls "),
|
|
665
|
+
__props.imageSrc && !hasError.value ? (openBlock(), createElementBlock("div", {
|
|
666
|
+
key: 3,
|
|
667
|
+
class: "zoom-controls absolute bottom-4 left-1/2 transform -translate-x-1/2 flex gap-1 sm:gap-2 bg-base-100/90 backdrop-blur rounded-lg p-1.5 sm:p-2 shadow-lg z-10",
|
|
668
|
+
onTouchstart: _cache[0] || (_cache[0] = withModifiers(() => {}, ["stop"])),
|
|
669
|
+
onTouchmove: _cache[1] || (_cache[1] = withModifiers(() => {}, ["stop"])),
|
|
670
|
+
onTouchend: _cache[2] || (_cache[2] = withModifiers(() => {}, ["stop"])),
|
|
671
|
+
onClick: _cache[3] || (_cache[3] = withModifiers(() => {}, ["stop"]))
|
|
672
|
+
}, [
|
|
673
|
+
createElementVNode("button", {
|
|
674
|
+
onClick: withModifiers(zoomOut, ["prevent"]),
|
|
675
|
+
class: "btn btn-sm btn-circle btn-ghost",
|
|
676
|
+
type: "button",
|
|
677
|
+
disabled: scale.value <= minScale,
|
|
678
|
+
title: "Zoom out",
|
|
679
|
+
"aria-label": "Zoom out"
|
|
680
|
+
}, [..._cache[10] || (_cache[10] = [createElementVNode("svg", {
|
|
681
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
682
|
+
class: "h-4 w-4",
|
|
683
|
+
fill: "none",
|
|
684
|
+
viewBox: "0 0 24 24",
|
|
685
|
+
stroke: "currentColor"
|
|
686
|
+
}, [createElementVNode("path", {
|
|
687
|
+
"stroke-linecap": "round",
|
|
688
|
+
"stroke-linejoin": "round",
|
|
689
|
+
"stroke-width": "2",
|
|
690
|
+
d: "M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0zM13 10H7"
|
|
691
|
+
})], -1)])], 8, _hoisted_10$1),
|
|
692
|
+
createElementVNode("button", {
|
|
693
|
+
onClick: withModifiers(resetZoom, ["prevent"]),
|
|
694
|
+
class: "text-xs flex items-center px-2 text-base-content/70 hover:text-base-content hover:bg-base-200 rounded transition-colors cursor-pointer",
|
|
695
|
+
type: "button",
|
|
696
|
+
title: "Reset zoom to 100%",
|
|
697
|
+
"aria-label": "Reset zoom to 100%"
|
|
698
|
+
}, toDisplayString(Math.round(scale.value * 100)) + "% ", 1),
|
|
699
|
+
createElementVNode("button", {
|
|
700
|
+
onClick: withModifiers(zoomIn, ["prevent"]),
|
|
701
|
+
class: "btn btn-sm btn-circle btn-ghost",
|
|
702
|
+
type: "button",
|
|
703
|
+
disabled: scale.value >= maxScale,
|
|
704
|
+
title: "Zoom in",
|
|
705
|
+
"aria-label": "Zoom in"
|
|
706
|
+
}, [..._cache[11] || (_cache[11] = [createElementVNode("svg", {
|
|
707
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
708
|
+
class: "h-4 w-4",
|
|
709
|
+
fill: "none",
|
|
710
|
+
viewBox: "0 0 24 24",
|
|
711
|
+
stroke: "currentColor"
|
|
712
|
+
}, [createElementVNode("path", {
|
|
713
|
+
"stroke-linecap": "round",
|
|
714
|
+
"stroke-linejoin": "round",
|
|
715
|
+
"stroke-width": "2",
|
|
716
|
+
d: "M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0zM10 7v6m3-3H7"
|
|
717
|
+
})], -1)])], 8, _hoisted_11$1),
|
|
718
|
+
createElementVNode("button", {
|
|
719
|
+
onClick: withModifiers(rotateImage, ["prevent"]),
|
|
720
|
+
class: "btn btn-sm btn-circle btn-ghost",
|
|
721
|
+
type: "button",
|
|
722
|
+
title: "Rotate image",
|
|
723
|
+
"aria-label": "Rotate image"
|
|
724
|
+
}, [..._cache[12] || (_cache[12] = [createElementVNode("svg", {
|
|
725
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
726
|
+
viewBox: "0 0 24 24",
|
|
727
|
+
"stroke-width": "1.5",
|
|
728
|
+
stroke: "currentColor",
|
|
729
|
+
"aria-hidden": "true",
|
|
730
|
+
"data-slot": "icon",
|
|
731
|
+
fill: "none",
|
|
732
|
+
class: "size-6"
|
|
733
|
+
}, [createElementVNode("path", {
|
|
734
|
+
"stroke-linecap": "round",
|
|
735
|
+
"stroke-linejoin": "round",
|
|
736
|
+
d: "M18.363 5.634A8.997 9.002 29.494 0 0 7.5 4.206 8.997 9.002 29.494 0 0 3.306 14.33 8.997 9.002 29.494 0 0 11.996 21a8.997 9.002 29.494 0 0 8.694-6.673m-2.327-8.693L20.87 8.14m.017-4.994v5.015m0 0h-5.013"
|
|
737
|
+
})], -1)])])
|
|
738
|
+
], 32)) : createCommentVNode("v-if", true)
|
|
739
|
+
], 512)
|
|
740
|
+
]), createElementVNode("form", _hoisted_12$1, [createElementVNode("button", {
|
|
741
|
+
type: "button",
|
|
742
|
+
onClick: withModifiers(handleClose, ["prevent"])
|
|
743
|
+
}, "close")])], 34);
|
|
744
|
+
};
|
|
745
|
+
}
|
|
746
|
+
});
|
|
747
|
+
var ImageModal_default = _sfc_main$1;
|
|
748
|
+
|
|
749
|
+
//#endregion
|
|
750
|
+
//#region src/slices/support_ticket/shared/InlineAttachments.vue
|
|
751
|
+
const _hoisted_1 = { class: "mt-4 sm:mt-6" };
|
|
752
|
+
const _hoisted_2 = { class: "flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2 sm:gap-0 mb-3" };
|
|
753
|
+
const _hoisted_3 = ["disabled"];
|
|
754
|
+
const _hoisted_4 = { class: "flex flex-col items-center justify-center py-2 sm:py-4" };
|
|
755
|
+
const _hoisted_5 = {
|
|
756
|
+
key: 0,
|
|
757
|
+
class: "text-xs text-base-content/50 mt-2 sm:mt-3"
|
|
758
|
+
};
|
|
759
|
+
const _hoisted_6 = {
|
|
760
|
+
key: 1,
|
|
761
|
+
class: "mb-4 space-y-2"
|
|
762
|
+
};
|
|
763
|
+
const _hoisted_7 = { class: "flex-1 min-w-0" };
|
|
764
|
+
const _hoisted_8 = { class: "text-xs sm:text-sm font-medium truncate" };
|
|
765
|
+
const _hoisted_9 = {
|
|
766
|
+
key: 0,
|
|
767
|
+
class: "w-full bg-base-300 rounded-full h-1.5 mt-1"
|
|
768
|
+
};
|
|
769
|
+
const _hoisted_10 = {
|
|
770
|
+
key: 1,
|
|
771
|
+
class: "text-xs text-error mt-1 break-words"
|
|
772
|
+
};
|
|
773
|
+
const _hoisted_11 = { class: "flex gap-2 sm:gap-1" };
|
|
774
|
+
const _hoisted_12 = ["onClick"];
|
|
775
|
+
const _hoisted_13 = ["onClick"];
|
|
776
|
+
const _hoisted_14 = {
|
|
777
|
+
key: 2,
|
|
778
|
+
class: "space-y-2"
|
|
779
|
+
};
|
|
780
|
+
const _hoisted_15 = {
|
|
781
|
+
key: 0,
|
|
782
|
+
class: "flex justify-center py-4"
|
|
783
|
+
};
|
|
784
|
+
const _hoisted_16 = ["onClick"];
|
|
785
|
+
const _hoisted_17 = { class: "flex items-center gap-2 sm:gap-3 flex-1 min-w-0 w-full sm:w-auto" };
|
|
786
|
+
const _hoisted_18 = { class: "flex-shrink-0" };
|
|
787
|
+
const _hoisted_19 = {
|
|
788
|
+
key: 0,
|
|
789
|
+
class: "w-10 h-10 sm:w-12 sm:h-12 rounded overflow-hidden bg-base-200 flex items-center justify-center"
|
|
790
|
+
};
|
|
791
|
+
const _hoisted_20 = ["src", "alt"];
|
|
792
|
+
const _hoisted_21 = {
|
|
793
|
+
key: 1,
|
|
794
|
+
class: "w-full h-full flex items-center justify-center"
|
|
795
|
+
};
|
|
796
|
+
const _hoisted_22 = { class: "flex-1 min-w-0" };
|
|
797
|
+
const _hoisted_23 = { class: "text-sm sm:text-base font-medium truncate" };
|
|
798
|
+
const _hoisted_24 = { class: "text-xs text-base-content/60" };
|
|
799
|
+
const _hoisted_25 = ["onClick"];
|
|
800
|
+
const _hoisted_26 = ["onClick"];
|
|
801
|
+
const _hoisted_27 = ["onClick"];
|
|
802
|
+
const _hoisted_28 = {
|
|
803
|
+
key: 3,
|
|
804
|
+
class: "text-center py-8 text-base-content/50"
|
|
805
|
+
};
|
|
806
|
+
const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
807
|
+
__name: "InlineAttachments",
|
|
808
|
+
props: {
|
|
809
|
+
recordId: {},
|
|
810
|
+
canUpload: {
|
|
811
|
+
type: Boolean,
|
|
812
|
+
default: true
|
|
813
|
+
},
|
|
814
|
+
canDelete: {
|
|
815
|
+
type: Boolean,
|
|
816
|
+
default: true
|
|
817
|
+
}
|
|
818
|
+
},
|
|
819
|
+
emits: [
|
|
820
|
+
"uploaded",
|
|
821
|
+
"deleted",
|
|
822
|
+
"filesQueued"
|
|
823
|
+
],
|
|
824
|
+
setup(__props, { expose: __expose, emit: __emit }) {
|
|
825
|
+
const props = __props;
|
|
826
|
+
const emit = __emit;
|
|
827
|
+
const uploadQueuedFiles = async (ticketId) => {
|
|
828
|
+
const pendingItems = uploadQueue.value.filter((item) => item.status === "pending");
|
|
829
|
+
for (const item of pendingItems) {
|
|
830
|
+
item.status = "uploading";
|
|
831
|
+
await uploadFile(item, ticketId);
|
|
832
|
+
}
|
|
833
|
+
};
|
|
834
|
+
__expose({
|
|
835
|
+
getQueuedFiles: () => uploadQueue.value.filter((item) => item.status === "pending").map((item) => item.file),
|
|
836
|
+
uploadQueuedFiles
|
|
837
|
+
});
|
|
838
|
+
const fileInput = ref(null);
|
|
839
|
+
const isDragging = ref(false);
|
|
840
|
+
const isUploading = ref(false);
|
|
841
|
+
const uploadQueue = ref([]);
|
|
842
|
+
const fileToDelete = ref(null);
|
|
843
|
+
const showDeleteModal = ref(false);
|
|
844
|
+
const viewingImage = ref(null);
|
|
845
|
+
const imageUrls = ref(/* @__PURE__ */ new Map());
|
|
846
|
+
const env = useEnv();
|
|
847
|
+
const userStore = useUserSessionStore();
|
|
848
|
+
const { data: attachmentsData, loading: attachmentsLoading, refetch: refreshAttachments } = useQuery(async (api) => {
|
|
849
|
+
if (!props.recordId) return {
|
|
850
|
+
files: [],
|
|
851
|
+
folders: []
|
|
852
|
+
};
|
|
853
|
+
return await api.attachments.listAttachments({
|
|
854
|
+
record_id: props.recordId,
|
|
855
|
+
record_type: "support_ticket",
|
|
856
|
+
filters: {
|
|
857
|
+
limit: 100,
|
|
858
|
+
include_folders: false,
|
|
859
|
+
folder_id: null
|
|
860
|
+
}
|
|
861
|
+
});
|
|
862
|
+
}, {
|
|
863
|
+
enabled: computed(() => !!props.recordId),
|
|
864
|
+
staleTime: 120 * 1e3
|
|
865
|
+
});
|
|
866
|
+
const attachments = computed(() => {
|
|
867
|
+
if (!attachmentsData.value) return [];
|
|
868
|
+
const files = [];
|
|
869
|
+
const fileItems = attachmentsData.value.files || [];
|
|
870
|
+
for (const item of fileItems) files.push({
|
|
871
|
+
id: item.id,
|
|
872
|
+
name: item.original_name,
|
|
873
|
+
size: parseInt(item.file_size || "0"),
|
|
874
|
+
type: item.content_type || "",
|
|
875
|
+
uploadedAt: new Date(item.created_at)
|
|
876
|
+
});
|
|
877
|
+
return files;
|
|
878
|
+
});
|
|
879
|
+
const deleteMessage = computed(() => {
|
|
880
|
+
if (!fileToDelete.value) return "Are you sure you want to delete this attachment? This action cannot be undone.";
|
|
881
|
+
return `Are you sure you want to delete "${fileToDelete.value.name}"? This action cannot be undone.`;
|
|
882
|
+
});
|
|
883
|
+
const imageAttachments = computed(() => {
|
|
884
|
+
return attachments.value.filter((file) => isImage(file.type));
|
|
885
|
+
});
|
|
886
|
+
const viewingImageIndex = computed(() => {
|
|
887
|
+
if (!viewingImage.value) return null;
|
|
888
|
+
const index = imageAttachments.value.findIndex((img) => img.id === viewingImage.value?.id);
|
|
889
|
+
return index >= 0 ? index : null;
|
|
890
|
+
});
|
|
891
|
+
const canGoToPrevious = computed(() => {
|
|
892
|
+
return viewingImageIndex.value !== null && viewingImageIndex.value > 0;
|
|
893
|
+
});
|
|
894
|
+
const canGoToNext = computed(() => {
|
|
895
|
+
return viewingImageIndex.value !== null && viewingImageIndex.value < imageAttachments.value.length - 1;
|
|
896
|
+
});
|
|
897
|
+
const { mutate: deleteAttachment } = useMutation((api, input) => api.attachments.deleteAttachment(input.id), { invalidate: /^attachments?:/ });
|
|
898
|
+
const getFileTypeLabel = (mimeType) => {
|
|
899
|
+
if (mimeType.startsWith("image/")) return "IMG";
|
|
900
|
+
if (mimeType.startsWith("video/")) return "VID";
|
|
901
|
+
if (mimeType.startsWith("audio/")) return "AUD";
|
|
902
|
+
if (mimeType.includes("pdf")) return "PDF";
|
|
903
|
+
if (mimeType.includes("word") || mimeType.includes("document")) return "DOC";
|
|
904
|
+
if (mimeType.includes("excel") || mimeType.includes("spreadsheet")) return "XLS";
|
|
905
|
+
if (mimeType.includes("zip") || mimeType.includes("archive")) return "ZIP";
|
|
906
|
+
return "FILE";
|
|
907
|
+
};
|
|
908
|
+
const getFileTypeBadgeClass = (mimeType) => {
|
|
909
|
+
if (mimeType.startsWith("image/")) return "badge-primary";
|
|
910
|
+
if (mimeType.startsWith("video/")) return "badge-secondary";
|
|
911
|
+
if (mimeType.includes("pdf")) return "badge-error";
|
|
912
|
+
if (mimeType.includes("word") || mimeType.includes("document")) return "badge-info";
|
|
913
|
+
return "badge-ghost";
|
|
914
|
+
};
|
|
915
|
+
const formatFileSize = (bytes) => {
|
|
916
|
+
if (bytes === 0) return "0 B";
|
|
917
|
+
const k = 1024;
|
|
918
|
+
const sizes = [
|
|
919
|
+
"B",
|
|
920
|
+
"KB",
|
|
921
|
+
"MB",
|
|
922
|
+
"GB"
|
|
923
|
+
];
|
|
924
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
925
|
+
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
|
|
926
|
+
};
|
|
927
|
+
const formatDate = (date) => {
|
|
928
|
+
return new Intl.DateTimeFormat("en-US", {
|
|
929
|
+
month: "short",
|
|
930
|
+
day: "numeric",
|
|
931
|
+
year: "numeric"
|
|
932
|
+
}).format(date);
|
|
933
|
+
};
|
|
934
|
+
const isImage = (mimeType) => {
|
|
935
|
+
return mimeType.startsWith("image/");
|
|
936
|
+
};
|
|
937
|
+
const getImageUrl = async (fileId) => {
|
|
938
|
+
if (imageUrls.value.has(fileId)) return imageUrls.value.get(fileId);
|
|
939
|
+
try {
|
|
940
|
+
const res = await fetch(`${env.restApiClient.apiUrl}/attachments/support_ticket/${fileId}`, {
|
|
941
|
+
method: "GET",
|
|
942
|
+
headers: { Authorization: `Bearer ${userStore.accessToken}` }
|
|
943
|
+
});
|
|
944
|
+
if (!res.ok) throw new Error("Failed to load image");
|
|
945
|
+
const blob = await res.blob();
|
|
946
|
+
const url = URL.createObjectURL(blob);
|
|
947
|
+
imageUrls.value.set(fileId, url);
|
|
948
|
+
return url;
|
|
949
|
+
} catch (error) {
|
|
950
|
+
console.error("Failed to load image:", error);
|
|
951
|
+
return "";
|
|
952
|
+
}
|
|
953
|
+
};
|
|
954
|
+
const getImageUrlSync = (fileId) => {
|
|
955
|
+
return imageUrls.value.get(fileId) || "";
|
|
956
|
+
};
|
|
957
|
+
const loadImageThumbnail = async (fileId) => {
|
|
958
|
+
if (!imageUrls.value.has(fileId)) await getImageUrl(fileId);
|
|
959
|
+
};
|
|
960
|
+
const handleImageError = (event) => {
|
|
961
|
+
const img = event.target;
|
|
962
|
+
img.style.display = "none";
|
|
963
|
+
};
|
|
964
|
+
onUnmounted(() => {
|
|
965
|
+
imageUrls.value.forEach((url) => {
|
|
966
|
+
URL.revokeObjectURL(url);
|
|
967
|
+
});
|
|
968
|
+
imageUrls.value.clear();
|
|
969
|
+
});
|
|
970
|
+
const viewImage = async (file) => {
|
|
971
|
+
viewingImage.value = file;
|
|
972
|
+
if (!imageUrls.value.has(file.id)) await getImageUrl(file.id);
|
|
973
|
+
};
|
|
974
|
+
const goToPreviousImage = () => {
|
|
975
|
+
if (!canGoToPrevious.value || viewingImageIndex.value === null) return;
|
|
976
|
+
const previousImage = imageAttachments.value[viewingImageIndex.value - 1];
|
|
977
|
+
if (previousImage) viewImage(previousImage);
|
|
978
|
+
};
|
|
979
|
+
const goToNextImage = () => {
|
|
980
|
+
if (!canGoToNext.value || viewingImageIndex.value === null) return;
|
|
981
|
+
const nextImage = imageAttachments.value[viewingImageIndex.value + 1];
|
|
982
|
+
if (nextImage) viewImage(nextImage);
|
|
983
|
+
};
|
|
984
|
+
const closeImageModal = () => {
|
|
985
|
+
viewingImage.value = null;
|
|
986
|
+
};
|
|
987
|
+
const openFileSelector = (event) => {
|
|
988
|
+
if (event) {
|
|
989
|
+
event.preventDefault();
|
|
990
|
+
event.stopPropagation();
|
|
991
|
+
}
|
|
992
|
+
if (isUploading.value) return;
|
|
993
|
+
fileInput.value?.click();
|
|
994
|
+
};
|
|
995
|
+
const processFiles = (files) => {
|
|
996
|
+
const fileArray = Array.from(files);
|
|
997
|
+
fileArray.forEach((file) => {
|
|
998
|
+
const queueItem = {
|
|
999
|
+
id: `${Date.now()}-${Math.random()}`,
|
|
1000
|
+
file,
|
|
1001
|
+
status: "pending",
|
|
1002
|
+
progress: 0
|
|
1003
|
+
};
|
|
1004
|
+
uploadQueue.value.push(queueItem);
|
|
1005
|
+
});
|
|
1006
|
+
emit("filesQueued", fileArray);
|
|
1007
|
+
if (props.recordId) processUploadQueue();
|
|
1008
|
+
};
|
|
1009
|
+
const handleFileSelect = (event) => {
|
|
1010
|
+
const target = event.target;
|
|
1011
|
+
const files = target.files;
|
|
1012
|
+
if (!files) return;
|
|
1013
|
+
processFiles(Array.from(files));
|
|
1014
|
+
target.value = "";
|
|
1015
|
+
};
|
|
1016
|
+
const handleFileDrop = (event) => {
|
|
1017
|
+
isDragging.value = false;
|
|
1018
|
+
const files = event.dataTransfer?.files;
|
|
1019
|
+
if (!files || files.length === 0) return;
|
|
1020
|
+
processFiles(Array.from(files));
|
|
1021
|
+
};
|
|
1022
|
+
const processUploadQueue = async (ticketId) => {
|
|
1023
|
+
const nextItem = uploadQueue.value.find((item) => item.status === "pending");
|
|
1024
|
+
if (!nextItem) {
|
|
1025
|
+
isUploading.value = false;
|
|
1026
|
+
return;
|
|
1027
|
+
}
|
|
1028
|
+
isUploading.value = true;
|
|
1029
|
+
nextItem.status = "uploading";
|
|
1030
|
+
await uploadFile(nextItem, ticketId);
|
|
1031
|
+
if (uploadQueue.value.some((item) => item.status === "pending")) processUploadQueue(ticketId);
|
|
1032
|
+
else isUploading.value = false;
|
|
1033
|
+
};
|
|
1034
|
+
const uploadFile = async (item, ticketId) => {
|
|
1035
|
+
const recordId = ticketId || props.recordId;
|
|
1036
|
+
if (!recordId) {
|
|
1037
|
+
item.status = "error";
|
|
1038
|
+
item.errorMessage = "Ticket ID is required for upload";
|
|
1039
|
+
return;
|
|
1040
|
+
}
|
|
1041
|
+
const progressInterval = setInterval(() => {
|
|
1042
|
+
item.progress += Math.random() * 10;
|
|
1043
|
+
item.progress = Math.min(99, Math.round(item.progress * 100) / 100);
|
|
1044
|
+
}, 200);
|
|
1045
|
+
try {
|
|
1046
|
+
const formData = new FormData();
|
|
1047
|
+
formData.append("file", item.file);
|
|
1048
|
+
formData.append("file_name", item.file.name);
|
|
1049
|
+
formData.append("record_id", recordId);
|
|
1050
|
+
formData.append("record_type", "support_ticket");
|
|
1051
|
+
const response = await fetch(`${env.restApiClient.apiUrl}/attachments/support_ticket/${recordId}`, {
|
|
1052
|
+
method: "POST",
|
|
1053
|
+
headers: { Authorization: `Bearer ${userStore.accessToken}` },
|
|
1054
|
+
body: formData
|
|
1055
|
+
});
|
|
1056
|
+
if (!response.ok) throw new Error(`Upload failed: ${response.statusText}`);
|
|
1057
|
+
const result = await response.json();
|
|
1058
|
+
if (result && result.id) {
|
|
1059
|
+
clearInterval(progressInterval);
|
|
1060
|
+
item.progress = 100;
|
|
1061
|
+
item.status = "success";
|
|
1062
|
+
setTimeout(() => {
|
|
1063
|
+
removeFromQueue(item.id);
|
|
1064
|
+
refreshAttachments();
|
|
1065
|
+
emit("uploaded");
|
|
1066
|
+
}, 500);
|
|
1067
|
+
} else throw new Error("Invalid response");
|
|
1068
|
+
} catch (error) {
|
|
1069
|
+
clearInterval(progressInterval);
|
|
1070
|
+
item.status = "error";
|
|
1071
|
+
item.errorMessage = error instanceof Error ? error.message : "Upload failed";
|
|
1072
|
+
}
|
|
1073
|
+
};
|
|
1074
|
+
const removeFromQueue = (id) => {
|
|
1075
|
+
const index = uploadQueue.value.findIndex((item) => item.id === id);
|
|
1076
|
+
if (index !== -1) uploadQueue.value.splice(index, 1);
|
|
1077
|
+
};
|
|
1078
|
+
const retryUpload = (item) => {
|
|
1079
|
+
item.status = "pending";
|
|
1080
|
+
item.progress = 0;
|
|
1081
|
+
item.errorMessage = void 0;
|
|
1082
|
+
if (props.recordId) processUploadQueue();
|
|
1083
|
+
};
|
|
1084
|
+
const downloadFile = async (file, event) => {
|
|
1085
|
+
if (event) {
|
|
1086
|
+
event.preventDefault();
|
|
1087
|
+
event.stopPropagation();
|
|
1088
|
+
}
|
|
1089
|
+
try {
|
|
1090
|
+
const res = await fetch(`${env.restApiClient.apiUrl}/attachments/support_ticket/${file.id}`, {
|
|
1091
|
+
method: "GET",
|
|
1092
|
+
headers: {
|
|
1093
|
+
"Content-Type": "application/json",
|
|
1094
|
+
Authorization: `Bearer ${userStore.accessToken}`
|
|
1095
|
+
}
|
|
1096
|
+
});
|
|
1097
|
+
if (!res.ok) throw new Error("Failed to download file");
|
|
1098
|
+
const blob = await res.blob();
|
|
1099
|
+
const url = URL.createObjectURL(blob);
|
|
1100
|
+
const a = document.createElement("a");
|
|
1101
|
+
a.href = url;
|
|
1102
|
+
a.download = file.name;
|
|
1103
|
+
document.body.appendChild(a);
|
|
1104
|
+
a.click();
|
|
1105
|
+
a.remove();
|
|
1106
|
+
URL.revokeObjectURL(url);
|
|
1107
|
+
} catch (error) {
|
|
1108
|
+
console.error("Download failed:", error);
|
|
1109
|
+
}
|
|
1110
|
+
};
|
|
1111
|
+
const confirmDelete = (file) => {
|
|
1112
|
+
fileToDelete.value = file;
|
|
1113
|
+
showDeleteModal.value = true;
|
|
1114
|
+
};
|
|
1115
|
+
const closeDeleteModal = () => {
|
|
1116
|
+
showDeleteModal.value = false;
|
|
1117
|
+
fileToDelete.value = null;
|
|
1118
|
+
};
|
|
1119
|
+
const deleteFile = async () => {
|
|
1120
|
+
if (!fileToDelete.value) return;
|
|
1121
|
+
try {
|
|
1122
|
+
await deleteAttachment({ id: fileToDelete.value.id });
|
|
1123
|
+
refreshAttachments();
|
|
1124
|
+
emit("deleted");
|
|
1125
|
+
closeDeleteModal();
|
|
1126
|
+
} catch (error) {
|
|
1127
|
+
console.error("Delete failed:", error);
|
|
1128
|
+
}
|
|
1129
|
+
};
|
|
1130
|
+
watch(() => props.recordId, (newRecordId, oldRecordId) => {
|
|
1131
|
+
if (newRecordId && newRecordId !== oldRecordId) {
|
|
1132
|
+
refreshAttachments();
|
|
1133
|
+
if (uploadQueue.value.some((item) => item.status === "pending")) processUploadQueue(newRecordId);
|
|
1134
|
+
}
|
|
1135
|
+
});
|
|
1136
|
+
watch(() => attachments.value, (newAttachments) => {
|
|
1137
|
+
newAttachments.forEach((file) => {
|
|
1138
|
+
if (isImage(file.type) && !imageUrls.value.has(file.id)) loadImageThumbnail(file.id);
|
|
1139
|
+
});
|
|
1140
|
+
}, { immediate: true });
|
|
1141
|
+
return (_ctx, _cache) => {
|
|
1142
|
+
return openBlock(), createElementBlock("div", _hoisted_1, [
|
|
1143
|
+
createElementVNode("div", _hoisted_2, [_cache[5] || (_cache[5] = createElementVNode("h3", { class: "text-base sm:text-lg font-semibold" }, "Attachments", -1)), __props.canUpload ? (openBlock(), createElementBlock("button", {
|
|
1144
|
+
key: 0,
|
|
1145
|
+
onClick: withModifiers(openFileSelector, ["prevent"]),
|
|
1146
|
+
class: "btn btn-sm btn-primary w-full sm:w-auto",
|
|
1147
|
+
disabled: isUploading.value,
|
|
1148
|
+
type: "button"
|
|
1149
|
+
}, [..._cache[4] || (_cache[4] = [createElementVNode("svg", {
|
|
1150
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
1151
|
+
class: "h-4 w-4 mr-1",
|
|
1152
|
+
fill: "none",
|
|
1153
|
+
viewBox: "0 0 24 24",
|
|
1154
|
+
stroke: "currentColor"
|
|
1155
|
+
}, [createElementVNode("path", {
|
|
1156
|
+
"stroke-linecap": "round",
|
|
1157
|
+
"stroke-linejoin": "round",
|
|
1158
|
+
"stroke-width": "2",
|
|
1159
|
+
d: "M12 4v16m8-8H4"
|
|
1160
|
+
})], -1), createTextVNode(" Add Files ", -1)])], 8, _hoisted_3)) : createCommentVNode("v-if", true)]),
|
|
1161
|
+
createElementVNode("input", {
|
|
1162
|
+
ref_key: "fileInput",
|
|
1163
|
+
ref: fileInput,
|
|
1164
|
+
type: "file",
|
|
1165
|
+
multiple: "",
|
|
1166
|
+
class: "hidden",
|
|
1167
|
+
onChange: handleFileSelect
|
|
1168
|
+
}, null, 544),
|
|
1169
|
+
createCommentVNode(" Drag & Drop Zone (hidden on mobile, shown on desktop) "),
|
|
1170
|
+
__props.canUpload ? (openBlock(), createElementBlock("div", {
|
|
1171
|
+
key: 0,
|
|
1172
|
+
class: normalizeClass(["hidden sm:block border-2 border-dashed rounded-lg p-4 sm:p-6 text-center mb-4 transition-colors cursor-pointer", {
|
|
1173
|
+
"border-primary bg-primary/5": isDragging.value,
|
|
1174
|
+
"border-base-300 hover:border-primary/50": !isDragging.value
|
|
1175
|
+
}]),
|
|
1176
|
+
onDragover: _cache[0] || (_cache[0] = withModifiers(($event) => isDragging.value = true, ["prevent"])),
|
|
1177
|
+
onDragleave: _cache[1] || (_cache[1] = withModifiers(($event) => isDragging.value = false, ["prevent"])),
|
|
1178
|
+
onDrop: withModifiers(handleFileDrop, ["prevent"]),
|
|
1179
|
+
onClick: withModifiers(openFileSelector, ["prevent", "stop"])
|
|
1180
|
+
}, [createElementVNode("div", _hoisted_4, [
|
|
1181
|
+
_cache[6] || (_cache[6] = createElementVNode("svg", {
|
|
1182
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
1183
|
+
class: "h-8 w-8 sm:h-12 sm:w-12 mb-2 text-primary",
|
|
1184
|
+
fill: "none",
|
|
1185
|
+
viewBox: "0 0 24 24",
|
|
1186
|
+
stroke: "currentColor"
|
|
1187
|
+
}, [createElementVNode("path", {
|
|
1188
|
+
"stroke-linecap": "round",
|
|
1189
|
+
"stroke-linejoin": "round",
|
|
1190
|
+
"stroke-width": "2",
|
|
1191
|
+
d: "M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"
|
|
1192
|
+
})], -1)),
|
|
1193
|
+
_cache[7] || (_cache[7] = createElementVNode("p", { class: "text-sm sm:text-lg font-medium" }, "Drag and drop files here", -1)),
|
|
1194
|
+
_cache[8] || (_cache[8] = createElementVNode("p", { class: "text-xs sm:text-sm text-base-content/70 mt-1" }, "or click to browse files", -1)),
|
|
1195
|
+
!__props.recordId ? (openBlock(), createElementBlock("p", _hoisted_5, " Files will be queued until ticket is created ")) : createCommentVNode("v-if", true)
|
|
1196
|
+
])], 34)) : createCommentVNode("v-if", true),
|
|
1197
|
+
createCommentVNode(" Upload Queue "),
|
|
1198
|
+
uploadQueue.value.length > 0 ? (openBlock(), createElementBlock("div", _hoisted_6, [(openBlock(true), createElementBlock(Fragment, null, renderList(uploadQueue.value, (item) => {
|
|
1199
|
+
return openBlock(), createElementBlock("div", {
|
|
1200
|
+
key: item.id,
|
|
1201
|
+
class: "flex flex-col sm:flex-row sm:items-center gap-2 sm:gap-3 p-2 sm:p-3 bg-base-200 rounded-lg"
|
|
1202
|
+
}, [createElementVNode("div", _hoisted_7, [
|
|
1203
|
+
createElementVNode("div", _hoisted_8, toDisplayString(item.file.name), 1),
|
|
1204
|
+
item.status === "uploading" ? (openBlock(), createElementBlock("div", _hoisted_9, [createElementVNode("div", {
|
|
1205
|
+
class: "bg-primary h-1.5 rounded-full transition-all",
|
|
1206
|
+
style: normalizeStyle({ width: `${item.progress}%` })
|
|
1207
|
+
}, null, 4)])) : createCommentVNode("v-if", true),
|
|
1208
|
+
item.status === "error" ? (openBlock(), createElementBlock("div", _hoisted_10, toDisplayString(item.errorMessage), 1)) : createCommentVNode("v-if", true)
|
|
1209
|
+
]), createElementVNode("div", _hoisted_11, [item.status === "error" ? (openBlock(), createElementBlock("button", {
|
|
1210
|
+
key: 0,
|
|
1211
|
+
onClick: withModifiers(($event) => retryUpload(item), ["prevent"]),
|
|
1212
|
+
class: "btn btn-xs btn-ghost flex-1 sm:flex-none",
|
|
1213
|
+
type: "button"
|
|
1214
|
+
}, " Retry ", 8, _hoisted_12)) : createCommentVNode("v-if", true), item.status !== "uploading" ? (openBlock(), createElementBlock("button", {
|
|
1215
|
+
key: 1,
|
|
1216
|
+
onClick: withModifiers(($event) => removeFromQueue(item.id), ["prevent"]),
|
|
1217
|
+
class: "btn btn-xs btn-ghost text-error flex-1 sm:flex-none",
|
|
1218
|
+
type: "button"
|
|
1219
|
+
}, " Remove ", 8, _hoisted_13)) : createCommentVNode("v-if", true)])]);
|
|
1220
|
+
}), 128))])) : createCommentVNode("v-if", true),
|
|
1221
|
+
createCommentVNode(" Attachments List "),
|
|
1222
|
+
(attachments.value.length > 0 || unref(attachmentsLoading)) && __props.recordId ? (openBlock(), createElementBlock("div", _hoisted_14, [unref(attachmentsLoading) ? (openBlock(), createElementBlock("div", _hoisted_15, [..._cache[9] || (_cache[9] = [createElementVNode("span", { class: "loading loading-spinner loading-sm" }, null, -1)])])) : createCommentVNode("v-if", true), (openBlock(true), createElementBlock(Fragment, null, renderList(attachments.value, (file) => {
|
|
1223
|
+
return openBlock(), createElementBlock("div", {
|
|
1224
|
+
key: file.id,
|
|
1225
|
+
class: normalizeClass(["flex flex-col sm:flex-row sm:items-center gap-2 sm:gap-3 p-2 sm:p-3 bg-base-100 border border-base-300 rounded-lg hover:bg-base-200 transition-colors", { "cursor-pointer": isImage(file.type) }]),
|
|
1226
|
+
onClick: withModifiers(($event) => isImage(file.type) && viewImage(file), ["prevent"])
|
|
1227
|
+
}, [createElementVNode("div", _hoisted_17, [createElementVNode("div", _hoisted_18, [createCommentVNode(" Image thumbnail preview "), isImage(file.type) ? (openBlock(), createElementBlock("div", _hoisted_19, [getImageUrlSync(file.id) ? (openBlock(), createElementBlock("img", {
|
|
1228
|
+
key: 0,
|
|
1229
|
+
src: getImageUrlSync(file.id),
|
|
1230
|
+
alt: file.name,
|
|
1231
|
+
class: "w-full h-full object-cover",
|
|
1232
|
+
onError: handleImageError
|
|
1233
|
+
}, null, 40, _hoisted_20)) : (openBlock(), createElementBlock("div", _hoisted_21, [..._cache[10] || (_cache[10] = [createElementVNode("span", { class: "loading loading-spinner loading-xs" }, null, -1)])]))])) : (openBlock(), createElementBlock(Fragment, { key: 1 }, [createCommentVNode(" File type badge for non-images "), createElementVNode("div", { class: normalizeClass(["badge badge-sm", getFileTypeBadgeClass(file.type)]) }, toDisplayString(getFileTypeLabel(file.type)), 3)], 2112))]), createElementVNode("div", _hoisted_22, [createElementVNode("div", _hoisted_23, toDisplayString(file.name), 1), createElementVNode("div", _hoisted_24, toDisplayString(formatFileSize(file.size)) + " • " + toDisplayString(formatDate(file.uploadedAt)), 1)])]), createElementVNode("div", {
|
|
1234
|
+
class: "flex gap-1 justify-end sm:justify-start",
|
|
1235
|
+
onClick: _cache[2] || (_cache[2] = withModifiers(() => {}, ["stop"]))
|
|
1236
|
+
}, [
|
|
1237
|
+
isImage(file.type) ? (openBlock(), createElementBlock("button", {
|
|
1238
|
+
key: 0,
|
|
1239
|
+
onClick: withModifiers(($event) => viewImage(file), ["prevent"]),
|
|
1240
|
+
class: "btn btn-sm btn-ghost btn-circle",
|
|
1241
|
+
title: "View Image",
|
|
1242
|
+
type: "button"
|
|
1243
|
+
}, [..._cache[11] || (_cache[11] = [createElementVNode("svg", {
|
|
1244
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
1245
|
+
class: "h-4 w-4",
|
|
1246
|
+
fill: "none",
|
|
1247
|
+
viewBox: "0 0 24 24",
|
|
1248
|
+
stroke: "currentColor"
|
|
1249
|
+
}, [createElementVNode("path", {
|
|
1250
|
+
"stroke-linecap": "round",
|
|
1251
|
+
"stroke-linejoin": "round",
|
|
1252
|
+
"stroke-width": "2",
|
|
1253
|
+
d: "M15 12a3 3 0 11-6 0 3 3 0 016 0z"
|
|
1254
|
+
}), createElementVNode("path", {
|
|
1255
|
+
"stroke-linecap": "round",
|
|
1256
|
+
"stroke-linejoin": "round",
|
|
1257
|
+
"stroke-width": "2",
|
|
1258
|
+
d: "M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"
|
|
1259
|
+
})], -1)])], 8, _hoisted_25)) : createCommentVNode("v-if", true),
|
|
1260
|
+
createElementVNode("button", {
|
|
1261
|
+
onClick: withModifiers(($event) => downloadFile(file, $event), ["prevent"]),
|
|
1262
|
+
class: "btn btn-sm btn-ghost btn-circle",
|
|
1263
|
+
title: "Download",
|
|
1264
|
+
type: "button"
|
|
1265
|
+
}, [..._cache[12] || (_cache[12] = [createElementVNode("svg", {
|
|
1266
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
1267
|
+
class: "h-4 w-4",
|
|
1268
|
+
fill: "none",
|
|
1269
|
+
viewBox: "0 0 24 24",
|
|
1270
|
+
stroke: "currentColor"
|
|
1271
|
+
}, [createElementVNode("path", {
|
|
1272
|
+
"stroke-linecap": "round",
|
|
1273
|
+
"stroke-linejoin": "round",
|
|
1274
|
+
"stroke-width": "2",
|
|
1275
|
+
d: "M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"
|
|
1276
|
+
})], -1)])], 8, _hoisted_26),
|
|
1277
|
+
__props.canDelete ? (openBlock(), createElementBlock("button", {
|
|
1278
|
+
key: 1,
|
|
1279
|
+
onClick: withModifiers(($event) => confirmDelete(file), ["prevent"]),
|
|
1280
|
+
class: "btn btn-sm btn-ghost btn-circle text-error",
|
|
1281
|
+
title: "Delete",
|
|
1282
|
+
type: "button"
|
|
1283
|
+
}, [..._cache[13] || (_cache[13] = [createElementVNode("svg", {
|
|
1284
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
1285
|
+
class: "h-4 w-4",
|
|
1286
|
+
fill: "none",
|
|
1287
|
+
viewBox: "0 0 24 24",
|
|
1288
|
+
stroke: "currentColor"
|
|
1289
|
+
}, [createElementVNode("path", {
|
|
1290
|
+
"stroke-linecap": "round",
|
|
1291
|
+
"stroke-linejoin": "round",
|
|
1292
|
+
"stroke-width": "2",
|
|
1293
|
+
d: "M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
|
|
1294
|
+
})], -1)])], 8, _hoisted_27)) : createCommentVNode("v-if", true)
|
|
1295
|
+
])], 10, _hoisted_16);
|
|
1296
|
+
}), 128))])) : !unref(attachmentsLoading) && __props.recordId ? (openBlock(), createElementBlock("div", _hoisted_28, [..._cache[14] || (_cache[14] = [createElementVNode("svg", {
|
|
1297
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
1298
|
+
class: "h-12 w-12 mx-auto mb-2 opacity-50",
|
|
1299
|
+
fill: "none",
|
|
1300
|
+
viewBox: "0 0 24 24",
|
|
1301
|
+
stroke: "currentColor"
|
|
1302
|
+
}, [createElementVNode("path", {
|
|
1303
|
+
"stroke-linecap": "round",
|
|
1304
|
+
"stroke-linejoin": "round",
|
|
1305
|
+
"stroke-width": "2",
|
|
1306
|
+
d: "M15.172 7l-6.586 6.586a2 2 0 102.828 2.828l6.414-6.586a4 4 0 00-5.656-5.656l-6.415 6.585a6 6 0 108.486 8.486L20.5 13"
|
|
1307
|
+
})], -1), createElementVNode("p", null, "No attachments", -1)])])) : createCommentVNode("v-if", true),
|
|
1308
|
+
createCommentVNode(" Image View Modal "),
|
|
1309
|
+
createVNode(ImageModal_default, {
|
|
1310
|
+
"is-open": !!viewingImage.value,
|
|
1311
|
+
"image-src": viewingImage.value ? getImageUrlSync(viewingImage.value.id) : "",
|
|
1312
|
+
"image-name": viewingImage.value?.name || "",
|
|
1313
|
+
"image-index": viewingImageIndex.value,
|
|
1314
|
+
"total-images": imageAttachments.value.length,
|
|
1315
|
+
"on-download": viewingImage.value ? () => {
|
|
1316
|
+
if (viewingImage.value) downloadFile(viewingImage.value, void 0);
|
|
1317
|
+
} : void 0,
|
|
1318
|
+
"on-previous": canGoToPrevious.value ? goToPreviousImage : void 0,
|
|
1319
|
+
"on-next": canGoToNext.value ? goToNextImage : void 0,
|
|
1320
|
+
onClose: closeImageModal
|
|
1321
|
+
}, null, 8, [
|
|
1322
|
+
"is-open",
|
|
1323
|
+
"image-src",
|
|
1324
|
+
"image-name",
|
|
1325
|
+
"image-index",
|
|
1326
|
+
"total-images",
|
|
1327
|
+
"on-download",
|
|
1328
|
+
"on-previous",
|
|
1329
|
+
"on-next"
|
|
1330
|
+
]),
|
|
1331
|
+
createCommentVNode(" Delete Confirmation Modal "),
|
|
1332
|
+
createVNode(ConfirmDialog_default, {
|
|
1333
|
+
modelValue: showDeleteModal.value,
|
|
1334
|
+
"onUpdate:modelValue": _cache[3] || (_cache[3] = ($event) => showDeleteModal.value = $event),
|
|
1335
|
+
title: "Delete Attachment",
|
|
1336
|
+
message: deleteMessage.value,
|
|
1337
|
+
"confirm-text": "Delete",
|
|
1338
|
+
"cancel-text": "Cancel",
|
|
1339
|
+
"confirm-button-class": "btn-error",
|
|
1340
|
+
onConfirm: deleteFile,
|
|
1341
|
+
onCancel: closeDeleteModal
|
|
1342
|
+
}, null, 8, ["modelValue", "message"])
|
|
1343
|
+
]);
|
|
1344
|
+
};
|
|
1345
|
+
}
|
|
1346
|
+
});
|
|
1347
|
+
var InlineAttachments_default = _sfc_main;
|
|
1348
|
+
|
|
1349
|
+
//#endregion
|
|
1350
|
+
export { ImageModal_default as n, ConfirmDialog_default as r, InlineAttachments_default as t };
|
|
1351
|
+
//# sourceMappingURL=InlineAttachments-I39rOvip.js.map
|