From ebf53c29c5d28e31de2160ae4a9c1fba67753b68 Mon Sep 17 00:00:00 2001 From: Jack Sleight Date: Fri, 12 Apr 2024 15:10:37 +0100 Subject: [PATCH 1/8] WIP --- .../fieldtypes/replicator/AddSetButton.vue | 16 ++- .../fieldtypes/replicator/Replicator.vue | 103 +++++++++++++++++- .../components/fieldtypes/replicator/Set.vue | 14 +++ src/Fieldtypes/Replicator.php | 22 ++++ 4 files changed, 151 insertions(+), 4 deletions(-) diff --git a/resources/js/components/fieldtypes/replicator/AddSetButton.vue b/resources/js/components/fieldtypes/replicator/AddSetButton.vue index d3258f60ea..f003caab9e 100644 --- a/resources/js/components/fieldtypes/replicator/AddSetButton.vue +++ b/resources/js/components/fieldtypes/replicator/AddSetButton.vue @@ -1,6 +1,6 @@ @@ -32,6 +39,7 @@ export default { groups: Array, index: Number, last: Boolean, + pasteEnabled: Boolean, enabled: { type: Boolean, default: true }, }, @@ -45,7 +53,11 @@ export default { if (this.sets.length === 1) { this.addSet(this.sets[0].handle); } - } + }, + + pasteSet() { + this.$emit('pasted', this.index); + }, } diff --git a/resources/js/components/fieldtypes/replicator/Replicator.vue b/resources/js/components/fieldtypes/replicator/Replicator.vue index 3f7d4cb216..8e0243599f 100644 --- a/resources/js/components/fieldtypes/replicator/Replicator.vue +++ b/resources/js/components/fieldtypes/replicator/Replicator.vue @@ -69,9 +69,12 @@ :previews="previews[set._id]" :show-field-previews="config.previews" :can-add-set="canAddSet" + :can-copy-set="canCopySet" @collapsed="collapseSet(set._id)" @expanded="expandSet(set._id)" @duplicated="duplicateSet(set._id)" + @cut="copySet(set._id, true)" + @copied="copySet(set._id)" @updated="updated" @meta-updated="updateSetMeta(set._id, $event)" @removed="removed(set, index)" @@ -86,7 +89,9 @@ :sets="setConfigs" :index="index" :enabled="canAddSet" - @added="addSet" /> + :paste-enabled="canPasteSet" + @added="addSet" + @pasted="pasteSet" /> @@ -98,7 +103,9 @@ :groups="groupConfigs" :sets="setConfigs" :index="value.length" - @added="addSet" /> + :paste-enabled="canPasteSet" + @added="addSet" + @pasted="pasteSet" /> @@ -135,6 +142,7 @@ export default { collapsed: clone(this.meta.collapsed), previews: this.meta.previews, fullScreenMode: false, + copiedSet: null, provide: { storeName: this.storeName, replicatorSets: this.config.sets @@ -150,6 +158,16 @@ export default { return !this.config.max_sets || this.value.length < this.config.max_sets; }, + canCopySet() { + return this.meta.groupKey !== null; + }, + + canPasteSet() { + if (!this.canAddSet) return false; + + return this.meta.groupKey !== null && this.copiedSet?.groupKey === this.meta.groupKey; + }, + setConfigs() { return reduce(this.groupConfigs, (sets, group) => { return sets.concat(group.sets); @@ -241,6 +259,51 @@ export default { this.expandSet(set._id); }, + copySet(id, cut = false) { + const index = this.value.findIndex(v => v._id === id); + const value = this.value[index]; + const meta = this.meta.existing[id]; + + const payload = { + value, + meta, + groupKey: this.meta.groupKey, + }; + + this.$events.$emit('replicator-set-copied', payload); + localStorage.setItem('statamic.replicator.set', JSON.stringify(payload)); + + if (cut) { + this.removed(value, index); + } + }, + + pasteSet(index) { + if (!this.copiedSet) { + return; + } + + const set = { + ...this.copiedSet.value, + _id: uniqid(), + }; + + this.updateSetPreviews(set._id, {}); + + this.updateSetMeta(set._id, this.copiedSet.meta); + + this.update([ + ...this.value.slice(0, index), + set, + ...this.value.slice(index) + ]); + + this.expandSet(set._id); + + this.$events.$emit('replicator-set-copied', null); + localStorage.setItem('statamic.replicator.set', JSON.stringify(null)); + }, + updateSetPreviews(id, previews) { this.previews[id] = previews; }, @@ -284,10 +347,46 @@ export default { return Object.keys(this.storeState.errors ?? []).some(handle => handle.startsWith(prefix)); }, + + setCopied(payload) { + this.copiedSet = payload; + }, + + storageInit() { + const payload = JSON.parse(localStorage.getItem('statamic.replicator.set')); + if (!payload) { + return; + } + + this.copiedSet = payload; + }, + + storageUpdated(event) { + if (event.key !== 'statamic.replicator.set') { + return; + } + + const payload = JSON.parse(event.newValue); + if (!payload) { + return; + } + + this.copiedSet = payload; + }, + }, mounted() { if (this.config.collapse) this.collapseAll(); + + this.$events.$on('replicator-set-copied', this.setCopied); + window.addEventListener('storage', this.storageUpdated); + this.storageInit(); + }, + + beforeDestroy() { + this.$events.$off('replicator-set-copied', this.setCopied); + window.removeEventListener('storage', this.storageUpdated); }, watch: { diff --git a/resources/js/components/fieldtypes/replicator/Set.vue b/resources/js/components/fieldtypes/replicator/Set.vue index 4bc73d493a..c945a13836 100644 --- a/resources/js/components/fieldtypes/replicator/Set.vue +++ b/resources/js/components/fieldtypes/replicator/Set.vue @@ -33,6 +33,8 @@ + + @@ -129,6 +131,10 @@ export default { type: Boolean, default: true }, + canCopySet: { + type: Boolean, + default: true + }, isReadOnly: Boolean, previews: Object, showFieldPreviews: { @@ -226,6 +232,14 @@ export default { this.$emit('duplicated'); }, + cut() { + this.$emit('cut'); + }, + + copy() { + this.$emit('copied'); + }, + fieldPath(field) { return `${this.fieldPathPrefix}.${this.index}.${field.handle}`; }, diff --git a/src/Fieldtypes/Replicator.php b/src/Fieldtypes/Replicator.php index a437022e07..2094b64ddb 100644 --- a/src/Fieldtypes/Replicator.php +++ b/src/Fieldtypes/Replicator.php @@ -224,6 +224,7 @@ public function preload() 'defaults' => $defaults, 'collapsed' => [], 'previews' => $previews, + 'groupKey' => $this->groupKey(), ]; } @@ -308,4 +309,25 @@ public function toQueryableValue($value) { return empty($value) ? null : $value; } + + protected function groupKey() + { + return md5(serialize($this->field->config('sets'))); + + // if (! $parent = $this->field->parent()) { + // return; + // } + // if (! $blueprint = $parent->blueprint()) { + // return; + // } + // if (! $handlePath = $this->field->handlePath()) { + // return; + // } + + // return implode('.', [ + // $blueprint->namespace(), + // $blueprint->handle(), + // ...$handlePath, + // ]); + } } From b2236de849055ab40fb4158e82c487947472d4ae Mon Sep 17 00:00:00 2001 From: Jack Sleight Date: Mon, 24 Jun 2024 15:25:32 +0100 Subject: [PATCH 2/8] WIP --- .../fieldtypes/replicator/AddSetButton.vue | 10 +++++++--- src/Fieldtypes/Replicator.php | 17 +---------------- 2 files changed, 8 insertions(+), 19 deletions(-) diff --git a/resources/js/components/fieldtypes/replicator/AddSetButton.vue b/resources/js/components/fieldtypes/replicator/AddSetButton.vue index 72d9187402..207a06d593 100644 --- a/resources/js/components/fieldtypes/replicator/AddSetButton.vue +++ b/resources/js/components/fieldtypes/replicator/AddSetButton.vue @@ -1,6 +1,6 @@ @@ -80,9 +80,9 @@ :sets="setConfigs" :index="value.length" :label="config.button_label" - :paste-enabled="canPasteSet" + :paste-enabled="canPasteSets" @added="addSet" - @pasted="pasteSet" /> + @pasted="pasteSets" /> @@ -134,9 +134,17 @@ export default { return !this.config.max_sets || this.value.length < this.config.max_sets; }, - canPasteSet() { + canPasteSets() { + if (!this.canAddSet) { + return false; + } const data = this.$clipboard.get(); - return this.canAddSet && data?.type === 'replicator' && Object.values(this.setConfigHashes).includes(data?.configHash); + if (data?.type !== 'replicator') { + return false; + } + const itemConfigHashes = data.items.map(item => item.configHash); + const setConfigHashes = Object.values(this.setConfigHashes); + return itemConfigHashes.every(hash => setConfigHashes.includes(hash)); }, setConfigs() { @@ -187,6 +195,14 @@ export default { visibleWhenReadOnly: true, run: this.collapseAll, }, + { + title: __('Cut All Sets'), + run: () => this.copySets(true), + }, + { + title: __('Copy All Sets'), + run: () => this.copySets(), + }, { title: __('Toggle Fullscreen Mode'), icon: ({ vm }) => vm.fullScreenMode ? 'shrink-all' : 'expand-bold', @@ -271,38 +287,72 @@ export default { this.$clipboard.set({ type: 'replicator', - configHash: this.setConfigHash(value.type), - value, - meta, + items: [{ + configHash: this.setConfigHash(value.type), + value: value, + meta: meta, + }], }); if (cut) { - this.removed(value, index); + this.removed(value, index); } }, - pasteSet(index) { + copySets(cut = false) { + this.$clipboard.set({ + type: 'replicator', + items: this.value.map((value) => ({ + configHash: this.setConfigHash(value.type), + value: value, + meta: this.meta.existing[value._id], + })), + }); + + if (cut) { + this.update([]); + this.updateMeta({ ...this.meta, existing: {} }); + } + }, + + pasteSets(index) { const data = this.$clipboard.get(); if (!data || data.type !== 'replicator') { return; } - const set = { - ...data.value, - _id: uniqid(), - }; + const value = []; + const meta = {}; + const previews = {}; + data.items.forEach((item) => { + const set = { ...item.value, _id: uniqid() }; + value.push(set); + meta[set._id] = item.meta; + previews[set._id] = {}; + }); - this.updateSetPreviews(set._id, {}); + this.previews = { + ...this.previews, + ...previews, + }; - this.updateSetMeta(set._id, data.meta); + this.updateMeta({ + ...this.meta, + existing: { + ...this.meta.existing, + ...meta, + }, + }); this.update([ ...this.value.slice(0, index), - set, + ...value, ...this.value.slice(index) ]); - this.expandSet(set._id); + value.forEach((set) => { + this.expandSet(set._id); + }); }, updateSetPreviews(id, previews) { From 33bd6bd1586c2368283d537d599bfdfd03498044 Mon Sep 17 00:00:00 2001 From: Jack Sleight Date: Fri, 6 Dec 2024 10:12:17 +0000 Subject: [PATCH 8/8] Plural --- .../js/components/fieldtypes/replicator/AddSetButton.vue | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/resources/js/components/fieldtypes/replicator/AddSetButton.vue b/resources/js/components/fieldtypes/replicator/AddSetButton.vue index 207a06d593..f8a29bbe51 100644 --- a/resources/js/components/fieldtypes/replicator/AddSetButton.vue +++ b/resources/js/components/fieldtypes/replicator/AddSetButton.vue @@ -25,9 +25,9 @@