@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.
@@ -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 class="masonry-item">
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="i + j - 2 < images.length && imageComponent === 'img'"
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="$eventBus.emit(`${id}GalleryImage`, i + j - 2)"
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="$eventBus.emit(`${id}GalleryImage`, i + j - 2)"
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 class="overflow-hidden rounded-lg">
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="$eventBus.emit(`${id}GalleryImage`, i - 1)"
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="$eventBus.emit(`${id}GalleryImage`, i - 1)"
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'"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fy-/fws-vue",
3
- "version": "2.3.78",
3
+ "version": "2.3.80",
4
4
  "author": "Florian 'Fy' Gasquez <m@fy.to>",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/fy-to/FWJS#readme",