@fy-/fws-vue 2.3.78 → 2.3.80
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/ui/DefaultGallery.vue +255 -8
- package/package.json +1 -1
|
@@ -85,6 +85,18 @@ const props = withDefaults(
|
|
|
85
85
|
imageComponent?: Component | string
|
|
86
86
|
isVideo?: Function
|
|
87
87
|
ranking?: boolean
|
|
88
|
+
// Edit mode support
|
|
89
|
+
editEnabled?: boolean
|
|
90
|
+
editMode?: boolean
|
|
91
|
+
selectedItems?: Set<string>
|
|
92
|
+
onBulkAction?: Function
|
|
93
|
+
getItemId?: Function
|
|
94
|
+
editButtonText?: string
|
|
95
|
+
cancelButtonText?: string
|
|
96
|
+
bulkActionText?: string
|
|
97
|
+
selectAllText?: string
|
|
98
|
+
clearSelectionText?: string
|
|
99
|
+
selectedCountText?: string
|
|
88
100
|
}>(),
|
|
89
101
|
{
|
|
90
102
|
modelValue: 0,
|
|
@@ -99,11 +111,23 @@ const props = withDefaults(
|
|
|
99
111
|
paging: undefined,
|
|
100
112
|
borderColor: undefined,
|
|
101
113
|
ranking: false,
|
|
114
|
+
// Edit mode defaults
|
|
115
|
+
editEnabled: false,
|
|
116
|
+
editMode: false,
|
|
117
|
+
selectedItems: () => new Set(),
|
|
118
|
+
onBulkAction: undefined,
|
|
119
|
+
getItemId: (item: any, index: number) => item.id || item.UUID || index.toString(),
|
|
120
|
+
editButtonText: 'Edit',
|
|
121
|
+
cancelButtonText: 'Cancel',
|
|
122
|
+
bulkActionText: 'Delete Selected',
|
|
123
|
+
selectAllText: 'Select All',
|
|
124
|
+
clearSelectionText: 'Clear',
|
|
125
|
+
selectedCountText: 'selected',
|
|
102
126
|
},
|
|
103
127
|
)
|
|
104
128
|
|
|
105
129
|
// Emits
|
|
106
|
-
const emit = defineEmits(['update:modelValue'])
|
|
130
|
+
const emit = defineEmits(['update:modelValue', 'update:editMode', 'update:selectedItems'])
|
|
107
131
|
|
|
108
132
|
// Two-way binding for model value
|
|
109
133
|
const modelValue = computed({
|
|
@@ -113,6 +137,74 @@ const modelValue = computed({
|
|
|
113
137
|
},
|
|
114
138
|
})
|
|
115
139
|
|
|
140
|
+
// Edit mode state management
|
|
141
|
+
const localEditMode = computed({
|
|
142
|
+
get: () => props.editMode,
|
|
143
|
+
set: value => emit('update:editMode', value),
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
const localSelectedItems = computed({
|
|
147
|
+
get: () => props.selectedItems,
|
|
148
|
+
set: value => emit('update:selectedItems', value),
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
// Edit mode functions
|
|
152
|
+
function toggleEditMode() {
|
|
153
|
+
localEditMode.value = !localEditMode.value
|
|
154
|
+
if (!localEditMode.value) {
|
|
155
|
+
// Clear selections when exiting edit mode
|
|
156
|
+
localSelectedItems.value = new Set()
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function toggleItemSelection(item: any, index: number) {
|
|
161
|
+
const itemId = props.getItemId(item, index)
|
|
162
|
+
const newSelection = new Set(localSelectedItems.value)
|
|
163
|
+
|
|
164
|
+
if (newSelection.has(itemId)) {
|
|
165
|
+
newSelection.delete(itemId)
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
newSelection.add(itemId)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
localSelectedItems.value = newSelection
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function selectAll() {
|
|
175
|
+
const newSelection = new Set<string>()
|
|
176
|
+
props.images.forEach((item, index) => {
|
|
177
|
+
newSelection.add(props.getItemId(item, index))
|
|
178
|
+
})
|
|
179
|
+
localSelectedItems.value = newSelection
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function clearSelection() {
|
|
183
|
+
localSelectedItems.value = new Set()
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function handleBulkAction() {
|
|
187
|
+
if (props.onBulkAction && localSelectedItems.value.size > 0) {
|
|
188
|
+
props.onBulkAction(Array.from(localSelectedItems.value))
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Check if an item is selected
|
|
193
|
+
function isItemSelected(item: any, index: number): boolean {
|
|
194
|
+
const itemId = props.getItemId(item, index)
|
|
195
|
+
return localSelectedItems.value.has(itemId)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Handle thumbnail click - either select or open gallery
|
|
199
|
+
function handleThumbnailClick(item: any, index: number) {
|
|
200
|
+
if (props.editMode) {
|
|
201
|
+
toggleItemSelection(item, index)
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
eventBus.emit(`${props.id}GalleryImage`, index)
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
116
208
|
// Computed values
|
|
117
209
|
const modelValueSrc = computed(() => {
|
|
118
210
|
if (props.images.length === 0) return false
|
|
@@ -800,6 +892,59 @@ onUnmounted(() => {
|
|
|
800
892
|
</div>
|
|
801
893
|
</transition>
|
|
802
894
|
|
|
895
|
+
<!-- Edit Mode Controls (only if editEnabled is true) -->
|
|
896
|
+
<div v-if="editEnabled && images.length > 0 && (mode === 'grid' || mode === 'mason' || mode === 'custom')">
|
|
897
|
+
<!-- Edit Mode Toggle Button (only show when not in edit mode) -->
|
|
898
|
+
<div v-if="!localEditMode" class="flex justify-end mb-3">
|
|
899
|
+
<button
|
|
900
|
+
class="px-4 py-2 rounded-lg font-medium text-sm transition-colors bg-gray-200 dark:bg-gray-700 hover:bg-gray-300 dark:hover:bg-gray-600 text-gray-800 dark:text-gray-200"
|
|
901
|
+
@click="toggleEditMode"
|
|
902
|
+
>
|
|
903
|
+
{{ editButtonText }}
|
|
904
|
+
</button>
|
|
905
|
+
</div>
|
|
906
|
+
|
|
907
|
+
<!-- Bulk Actions Bar TOP (shown when in edit mode) -->
|
|
908
|
+
<div
|
|
909
|
+
v-if="localEditMode"
|
|
910
|
+
class="flex flex-wrap items-center justify-between gap-2 p-3 mb-3 bg-gray-100 dark:bg-gray-800 rounded-lg border border-gray-300 dark:border-gray-700"
|
|
911
|
+
>
|
|
912
|
+
<div class="flex items-center gap-3">
|
|
913
|
+
<span class="text-sm font-medium">
|
|
914
|
+
{{ localSelectedItems.size }} {{ selectedCountText }}
|
|
915
|
+
</span>
|
|
916
|
+
<button
|
|
917
|
+
class="px-3 py-1 text-sm bg-gray-200 dark:bg-gray-700 hover:bg-gray-300 dark:hover:bg-gray-600 rounded-md transition-colors"
|
|
918
|
+
@click="selectAll"
|
|
919
|
+
>
|
|
920
|
+
{{ selectAllText }}
|
|
921
|
+
</button>
|
|
922
|
+
<button
|
|
923
|
+
class="px-3 py-1 text-sm bg-gray-200 dark:bg-gray-700 hover:bg-gray-300 dark:hover:bg-gray-600 rounded-md transition-colors"
|
|
924
|
+
@click="clearSelection"
|
|
925
|
+
>
|
|
926
|
+
{{ clearSelectionText }}
|
|
927
|
+
</button>
|
|
928
|
+
</div>
|
|
929
|
+
<div class="flex items-center gap-2">
|
|
930
|
+
<button
|
|
931
|
+
class="px-4 py-1.5 text-sm bg-gray-600 hover:bg-gray-700 text-white rounded-md transition-colors font-medium"
|
|
932
|
+
@click="toggleEditMode"
|
|
933
|
+
>
|
|
934
|
+
{{ cancelButtonText }}
|
|
935
|
+
</button>
|
|
936
|
+
<button
|
|
937
|
+
class="px-4 py-1.5 text-sm bg-red-600 hover:bg-red-700 text-white rounded-md transition-colors font-medium"
|
|
938
|
+
:disabled="localSelectedItems.size === 0"
|
|
939
|
+
:class="{ 'opacity-50 cursor-not-allowed': localSelectedItems.size === 0 }"
|
|
940
|
+
@click="handleBulkAction"
|
|
941
|
+
>
|
|
942
|
+
{{ bulkActionText }}
|
|
943
|
+
</button>
|
|
944
|
+
</div>
|
|
945
|
+
</div>
|
|
946
|
+
</div>
|
|
947
|
+
|
|
803
948
|
<!-- Thumbnail Grid/Masonry/Custom Layouts if gallery is not open -->
|
|
804
949
|
<div v-if="mode === 'grid' || mode === 'mason' || mode === 'custom'" class="gallery-grid">
|
|
805
950
|
<div
|
|
@@ -823,13 +968,44 @@ onUnmounted(() => {
|
|
|
823
968
|
{{ i }}
|
|
824
969
|
</div>
|
|
825
970
|
<template v-for="j in gridHeight" :key="`gi_${id}_${i + j}`">
|
|
826
|
-
<div
|
|
971
|
+
<div
|
|
972
|
+
v-if="i + j - 2 < images.length"
|
|
973
|
+
class="masonry-item relative"
|
|
974
|
+
:class="{ 'ring-4 ring-blue-500': localEditMode && isItemSelected(images[i + j - 2], i + j - 2) }"
|
|
975
|
+
>
|
|
976
|
+
<!-- Checkbox overlay for edit mode -->
|
|
977
|
+
<div
|
|
978
|
+
v-if="localEditMode"
|
|
979
|
+
class="absolute top-2 left-2 z-20 pointer-events-none"
|
|
980
|
+
>
|
|
981
|
+
<div
|
|
982
|
+
class="w-6 h-6 rounded border-2 bg-white/90 dark:bg-black/90 flex items-center justify-center pointer-events-auto"
|
|
983
|
+
:class="isItemSelected(images[i + j - 2], i + j - 2) ? 'bg-blue-500 border-blue-500' : 'border-gray-400'"
|
|
984
|
+
>
|
|
985
|
+
<svg
|
|
986
|
+
v-if="isItemSelected(images[i + j - 2], i + j - 2)"
|
|
987
|
+
class="w-4 h-4 text-white"
|
|
988
|
+
fill="none"
|
|
989
|
+
stroke="currentColor"
|
|
990
|
+
viewBox="0 0 24 24"
|
|
991
|
+
>
|
|
992
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="3" d="M5 13l4 4L19 7" />
|
|
993
|
+
</svg>
|
|
994
|
+
</div>
|
|
995
|
+
</div>
|
|
996
|
+
|
|
997
|
+
<!-- Selection overlay -->
|
|
998
|
+
<div
|
|
999
|
+
v-if="localEditMode && isItemSelected(images[i + j - 2], i + j - 2)"
|
|
1000
|
+
class="absolute inset-0 bg-blue-500/20 pointer-events-none z-10 rounded-lg"
|
|
1001
|
+
/>
|
|
1002
|
+
|
|
827
1003
|
<img
|
|
828
|
-
v-if="
|
|
1004
|
+
v-if="imageComponent === 'img'"
|
|
829
1005
|
class="h-auto max-w-full w-full rounded-lg cursor-pointer shadow-md hover:shadow-xl transition-all duration-300 hover:brightness-110 hover:scale-[1.02]"
|
|
830
1006
|
:src="getThumbnailUrl(images[i + j - 2])"
|
|
831
1007
|
:alt="`Gallery image ${i + j - 1}`"
|
|
832
|
-
@click="
|
|
1008
|
+
@click="handleThumbnailClick(images[i + j - 2], i + j - 2)"
|
|
833
1009
|
>
|
|
834
1010
|
<component
|
|
835
1011
|
:is="imageComponent"
|
|
@@ -844,7 +1020,7 @@ onUnmounted(() => {
|
|
|
844
1020
|
:show-likes="getThumbnailUrl(images[i + j - 2]).showLikes"
|
|
845
1021
|
:is-author="getThumbnailUrl(images[i + j - 2]).isAuthor"
|
|
846
1022
|
:user-uuid="getThumbnailUrl(images[i + j - 2]).userUUID"
|
|
847
|
-
@click="
|
|
1023
|
+
@click="handleThumbnailClick(images[i + j - 2], i + j - 2)"
|
|
848
1024
|
/>
|
|
849
1025
|
</div>
|
|
850
1026
|
</template>
|
|
@@ -854,13 +1030,43 @@ onUnmounted(() => {
|
|
|
854
1030
|
<div v-if="ranking" class="img-gallery-ranking">
|
|
855
1031
|
{{ i }}
|
|
856
1032
|
</div>
|
|
857
|
-
<div
|
|
1033
|
+
<div
|
|
1034
|
+
class="overflow-hidden rounded-lg relative"
|
|
1035
|
+
:class="{ 'ring-4 ring-blue-500': localEditMode && isItemSelected(images[i - 1], i - 1) }"
|
|
1036
|
+
>
|
|
1037
|
+
<!-- Checkbox overlay for edit mode -->
|
|
1038
|
+
<div
|
|
1039
|
+
v-if="localEditMode"
|
|
1040
|
+
class="absolute top-2 left-2 z-20 pointer-events-none"
|
|
1041
|
+
>
|
|
1042
|
+
<div
|
|
1043
|
+
class="w-6 h-6 rounded border-2 bg-white/90 dark:bg-black/90 flex items-center justify-center pointer-events-auto"
|
|
1044
|
+
:class="isItemSelected(images[i - 1], i - 1) ? 'bg-blue-500 border-blue-500' : 'border-gray-400'"
|
|
1045
|
+
>
|
|
1046
|
+
<svg
|
|
1047
|
+
v-if="isItemSelected(images[i - 1], i - 1)"
|
|
1048
|
+
class="w-4 h-4 text-white"
|
|
1049
|
+
fill="none"
|
|
1050
|
+
stroke="currentColor"
|
|
1051
|
+
viewBox="0 0 24 24"
|
|
1052
|
+
>
|
|
1053
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="3" d="M5 13l4 4L19 7" />
|
|
1054
|
+
</svg>
|
|
1055
|
+
</div>
|
|
1056
|
+
</div>
|
|
1057
|
+
|
|
1058
|
+
<!-- Selection overlay -->
|
|
1059
|
+
<div
|
|
1060
|
+
v-if="localEditMode && isItemSelected(images[i - 1], i - 1)"
|
|
1061
|
+
class="absolute inset-0 bg-blue-500/20 pointer-events-none z-10"
|
|
1062
|
+
/>
|
|
1063
|
+
|
|
858
1064
|
<img
|
|
859
1065
|
v-if="imageComponent === 'img'"
|
|
860
1066
|
class="h-auto max-w-full w-full rounded-lg cursor-pointer shadow-md transition-all duration-300 group-hover:brightness-110 group-hover:scale-[1.03]"
|
|
861
1067
|
:src="getThumbnailUrl(images[i - 1])"
|
|
862
1068
|
:alt="`Gallery image ${i}`"
|
|
863
|
-
@click="
|
|
1069
|
+
@click="handleThumbnailClick(images[i - 1], i - 1)"
|
|
864
1070
|
>
|
|
865
1071
|
<component
|
|
866
1072
|
:is="imageComponent"
|
|
@@ -875,7 +1081,7 @@ onUnmounted(() => {
|
|
|
875
1081
|
:show-likes="getThumbnailUrl(images[i - 1]).showLikes"
|
|
876
1082
|
:is-author="getThumbnailUrl(images[i - 1]).isAuthor"
|
|
877
1083
|
:user-uuid="getThumbnailUrl(images[i - 1]).userUUID"
|
|
878
|
-
@click="
|
|
1084
|
+
@click="handleThumbnailClick(images[i - 1], i - 1)"
|
|
879
1085
|
/>
|
|
880
1086
|
</div>
|
|
881
1087
|
</div>
|
|
@@ -883,6 +1089,47 @@ onUnmounted(() => {
|
|
|
883
1089
|
</div>
|
|
884
1090
|
</div>
|
|
885
1091
|
|
|
1092
|
+
<!-- Bulk Actions Bar BOTTOM (duplicate bar below gallery when in edit mode) -->
|
|
1093
|
+
<div v-if="editEnabled && localEditMode && images.length > 0 && (mode === 'grid' || mode === 'mason' || mode === 'custom')" class="mt-3">
|
|
1094
|
+
<div
|
|
1095
|
+
class="flex flex-wrap items-center justify-between gap-2 p-3 bg-gray-100 dark:bg-gray-800 rounded-lg border border-gray-300 dark:border-gray-700"
|
|
1096
|
+
>
|
|
1097
|
+
<div class="flex items-center gap-3">
|
|
1098
|
+
<span class="text-sm font-medium">
|
|
1099
|
+
{{ localSelectedItems.size }} {{ selectedCountText }}
|
|
1100
|
+
</span>
|
|
1101
|
+
<button
|
|
1102
|
+
class="px-3 py-1 text-sm bg-gray-200 dark:bg-gray-700 hover:bg-gray-300 dark:hover:bg-gray-600 rounded-md transition-colors"
|
|
1103
|
+
@click="selectAll"
|
|
1104
|
+
>
|
|
1105
|
+
{{ selectAllText }}
|
|
1106
|
+
</button>
|
|
1107
|
+
<button
|
|
1108
|
+
class="px-3 py-1 text-sm bg-gray-200 dark:bg-gray-700 hover:bg-gray-300 dark:hover:bg-gray-600 rounded-md transition-colors"
|
|
1109
|
+
@click="clearSelection"
|
|
1110
|
+
>
|
|
1111
|
+
{{ clearSelectionText }}
|
|
1112
|
+
</button>
|
|
1113
|
+
</div>
|
|
1114
|
+
<div class="flex items-center gap-2">
|
|
1115
|
+
<button
|
|
1116
|
+
class="px-4 py-1.5 text-sm bg-gray-600 hover:bg-gray-700 text-white rounded-md transition-colors font-medium"
|
|
1117
|
+
@click="toggleEditMode"
|
|
1118
|
+
>
|
|
1119
|
+
{{ cancelButtonText }}
|
|
1120
|
+
</button>
|
|
1121
|
+
<button
|
|
1122
|
+
class="px-4 py-1.5 text-sm bg-red-600 hover:bg-red-700 text-white rounded-md transition-colors font-medium"
|
|
1123
|
+
:disabled="localSelectedItems.size === 0"
|
|
1124
|
+
:class="{ 'opacity-50 cursor-not-allowed': localSelectedItems.size === 0 }"
|
|
1125
|
+
@click="handleBulkAction"
|
|
1126
|
+
>
|
|
1127
|
+
{{ bulkActionText }}
|
|
1128
|
+
</button>
|
|
1129
|
+
</div>
|
|
1130
|
+
</div>
|
|
1131
|
+
</div>
|
|
1132
|
+
|
|
886
1133
|
<!-- Button Mode -->
|
|
887
1134
|
<button
|
|
888
1135
|
v-if="mode === 'button'"
|