@fy-/fws-vue 2.3.78 → 2.3.79
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 +207 -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,52 @@ 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 -->
|
|
898
|
+
<div class="flex justify-end mb-3">
|
|
899
|
+
<button
|
|
900
|
+
class="px-4 py-2 rounded-lg font-medium text-sm transition-colors"
|
|
901
|
+
:class="localEditMode
|
|
902
|
+
? 'bg-red-600 hover:bg-red-700 text-white'
|
|
903
|
+
: 'bg-gray-200 dark:bg-gray-700 hover:bg-gray-300 dark:hover:bg-gray-600 text-gray-800 dark:text-gray-200'"
|
|
904
|
+
@click="toggleEditMode"
|
|
905
|
+
>
|
|
906
|
+
{{ localEditMode ? cancelButtonText : editButtonText }}
|
|
907
|
+
</button>
|
|
908
|
+
</div>
|
|
909
|
+
|
|
910
|
+
<!-- Bulk Actions Bar (shown when items are selected) -->
|
|
911
|
+
<div
|
|
912
|
+
v-if="localEditMode && localSelectedItems.size > 0"
|
|
913
|
+
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"
|
|
914
|
+
>
|
|
915
|
+
<div class="flex items-center gap-3">
|
|
916
|
+
<span class="text-sm font-medium">
|
|
917
|
+
{{ localSelectedItems.size }} {{ selectedCountText }}
|
|
918
|
+
</span>
|
|
919
|
+
<button
|
|
920
|
+
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"
|
|
921
|
+
@click="selectAll"
|
|
922
|
+
>
|
|
923
|
+
{{ selectAllText }}
|
|
924
|
+
</button>
|
|
925
|
+
<button
|
|
926
|
+
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"
|
|
927
|
+
@click="clearSelection"
|
|
928
|
+
>
|
|
929
|
+
{{ clearSelectionText }}
|
|
930
|
+
</button>
|
|
931
|
+
</div>
|
|
932
|
+
<button
|
|
933
|
+
class="px-4 py-1.5 text-sm bg-red-600 hover:bg-red-700 text-white rounded-md transition-colors font-medium"
|
|
934
|
+
@click="handleBulkAction"
|
|
935
|
+
>
|
|
936
|
+
{{ bulkActionText }}
|
|
937
|
+
</button>
|
|
938
|
+
</div>
|
|
939
|
+
</div>
|
|
940
|
+
|
|
803
941
|
<!-- Thumbnail Grid/Masonry/Custom Layouts if gallery is not open -->
|
|
804
942
|
<div v-if="mode === 'grid' || mode === 'mason' || mode === 'custom'" class="gallery-grid">
|
|
805
943
|
<div
|
|
@@ -823,13 +961,44 @@ onUnmounted(() => {
|
|
|
823
961
|
{{ i }}
|
|
824
962
|
</div>
|
|
825
963
|
<template v-for="j in gridHeight" :key="`gi_${id}_${i + j}`">
|
|
826
|
-
<div
|
|
964
|
+
<div
|
|
965
|
+
v-if="i + j - 2 < images.length"
|
|
966
|
+
class="masonry-item relative"
|
|
967
|
+
:class="{ 'ring-4 ring-blue-500': localEditMode && isItemSelected(images[i + j - 2], i + j - 2) }"
|
|
968
|
+
>
|
|
969
|
+
<!-- Checkbox overlay for edit mode -->
|
|
970
|
+
<div
|
|
971
|
+
v-if="localEditMode"
|
|
972
|
+
class="absolute top-2 left-2 z-20 pointer-events-none"
|
|
973
|
+
>
|
|
974
|
+
<div
|
|
975
|
+
class="w-6 h-6 rounded border-2 bg-white/90 dark:bg-black/90 flex items-center justify-center pointer-events-auto"
|
|
976
|
+
:class="isItemSelected(images[i + j - 2], i + j - 2) ? 'bg-blue-500 border-blue-500' : 'border-gray-400'"
|
|
977
|
+
>
|
|
978
|
+
<svg
|
|
979
|
+
v-if="isItemSelected(images[i + j - 2], i + j - 2)"
|
|
980
|
+
class="w-4 h-4 text-white"
|
|
981
|
+
fill="none"
|
|
982
|
+
stroke="currentColor"
|
|
983
|
+
viewBox="0 0 24 24"
|
|
984
|
+
>
|
|
985
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="3" d="M5 13l4 4L19 7" />
|
|
986
|
+
</svg>
|
|
987
|
+
</div>
|
|
988
|
+
</div>
|
|
989
|
+
|
|
990
|
+
<!-- Selection overlay -->
|
|
991
|
+
<div
|
|
992
|
+
v-if="localEditMode && isItemSelected(images[i + j - 2], i + j - 2)"
|
|
993
|
+
class="absolute inset-0 bg-blue-500/20 pointer-events-none z-10 rounded-lg"
|
|
994
|
+
/>
|
|
995
|
+
|
|
827
996
|
<img
|
|
828
|
-
v-if="
|
|
997
|
+
v-if="imageComponent === 'img'"
|
|
829
998
|
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
999
|
:src="getThumbnailUrl(images[i + j - 2])"
|
|
831
1000
|
:alt="`Gallery image ${i + j - 1}`"
|
|
832
|
-
@click="
|
|
1001
|
+
@click="handleThumbnailClick(images[i + j - 2], i + j - 2)"
|
|
833
1002
|
>
|
|
834
1003
|
<component
|
|
835
1004
|
:is="imageComponent"
|
|
@@ -844,7 +1013,7 @@ onUnmounted(() => {
|
|
|
844
1013
|
:show-likes="getThumbnailUrl(images[i + j - 2]).showLikes"
|
|
845
1014
|
:is-author="getThumbnailUrl(images[i + j - 2]).isAuthor"
|
|
846
1015
|
:user-uuid="getThumbnailUrl(images[i + j - 2]).userUUID"
|
|
847
|
-
@click="
|
|
1016
|
+
@click="handleThumbnailClick(images[i + j - 2], i + j - 2)"
|
|
848
1017
|
/>
|
|
849
1018
|
</div>
|
|
850
1019
|
</template>
|
|
@@ -854,13 +1023,43 @@ onUnmounted(() => {
|
|
|
854
1023
|
<div v-if="ranking" class="img-gallery-ranking">
|
|
855
1024
|
{{ i }}
|
|
856
1025
|
</div>
|
|
857
|
-
<div
|
|
1026
|
+
<div
|
|
1027
|
+
class="overflow-hidden rounded-lg relative"
|
|
1028
|
+
:class="{ 'ring-4 ring-blue-500': localEditMode && isItemSelected(images[i - 1], i - 1) }"
|
|
1029
|
+
>
|
|
1030
|
+
<!-- Checkbox overlay for edit mode -->
|
|
1031
|
+
<div
|
|
1032
|
+
v-if="localEditMode"
|
|
1033
|
+
class="absolute top-2 left-2 z-20 pointer-events-none"
|
|
1034
|
+
>
|
|
1035
|
+
<div
|
|
1036
|
+
class="w-6 h-6 rounded border-2 bg-white/90 dark:bg-black/90 flex items-center justify-center pointer-events-auto"
|
|
1037
|
+
:class="isItemSelected(images[i - 1], i - 1) ? 'bg-blue-500 border-blue-500' : 'border-gray-400'"
|
|
1038
|
+
>
|
|
1039
|
+
<svg
|
|
1040
|
+
v-if="isItemSelected(images[i - 1], i - 1)"
|
|
1041
|
+
class="w-4 h-4 text-white"
|
|
1042
|
+
fill="none"
|
|
1043
|
+
stroke="currentColor"
|
|
1044
|
+
viewBox="0 0 24 24"
|
|
1045
|
+
>
|
|
1046
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="3" d="M5 13l4 4L19 7" />
|
|
1047
|
+
</svg>
|
|
1048
|
+
</div>
|
|
1049
|
+
</div>
|
|
1050
|
+
|
|
1051
|
+
<!-- Selection overlay -->
|
|
1052
|
+
<div
|
|
1053
|
+
v-if="localEditMode && isItemSelected(images[i - 1], i - 1)"
|
|
1054
|
+
class="absolute inset-0 bg-blue-500/20 pointer-events-none z-10"
|
|
1055
|
+
/>
|
|
1056
|
+
|
|
858
1057
|
<img
|
|
859
1058
|
v-if="imageComponent === 'img'"
|
|
860
1059
|
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
1060
|
:src="getThumbnailUrl(images[i - 1])"
|
|
862
1061
|
:alt="`Gallery image ${i}`"
|
|
863
|
-
@click="
|
|
1062
|
+
@click="handleThumbnailClick(images[i - 1], i - 1)"
|
|
864
1063
|
>
|
|
865
1064
|
<component
|
|
866
1065
|
:is="imageComponent"
|
|
@@ -875,7 +1074,7 @@ onUnmounted(() => {
|
|
|
875
1074
|
:show-likes="getThumbnailUrl(images[i - 1]).showLikes"
|
|
876
1075
|
:is-author="getThumbnailUrl(images[i - 1]).isAuthor"
|
|
877
1076
|
:user-uuid="getThumbnailUrl(images[i - 1]).userUUID"
|
|
878
|
-
@click="
|
|
1077
|
+
@click="handleThumbnailClick(images[i - 1], i - 1)"
|
|
879
1078
|
/>
|
|
880
1079
|
</div>
|
|
881
1080
|
</div>
|