From a4a770aeb934e50aab4bfad1e69dd6b4db0635e3 Mon Sep 17 00:00:00 2001 From: eruedin Date: Fri, 13 Oct 2023 10:47:49 +0200 Subject: [PATCH 001/251] Create rm-CH-scout.json new, rm --- frontend/src/locales/rm-CH-scout.json | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 frontend/src/locales/rm-CH-scout.json diff --git a/frontend/src/locales/rm-CH-scout.json b/frontend/src/locales/rm-CH-scout.json new file mode 100644 index 00000000000..873a2203c22 --- /dev/null +++ b/frontend/src/locales/rm-CH-scout.json @@ -0,0 +1,24 @@ +{ + "contentNode": { + "storycontext": { + "name": "Fil cotschen" + } + }, + "entity": { + "user": { + "fields": { + "nickname": "Num da battasendas" + } + } + }, + "global": { + "language": "Rumantsch (Battasendas)" + }, + "views": { + "camp": { + "story": { + "title": "Fil cotschen" + } + } + } +} From 264ee4b7411bed79f227dbf1408ad65f90a8b066 Mon Sep 17 00:00:00 2001 From: Pirmin Mattmann Date: Sat, 30 Sep 2023 20:06:56 +0200 Subject: [PATCH 002/251] Add 'Copy Activity'-Function to Copy ScheduleEntry-Url draft styling + fix Clipboard info dialog feature-complete cs-fix fix tests psalm UnitTests cs-fix translation translation & cleanup --- api/src/Entity/Activity.php | 7 ++ api/src/State/ActivityCreateProcessor.php | 15 ++- .../Api/Activities/CreateActivityTest.php | 66 ++++++++++ .../State/ActivityCreateProcessorTest.php | 1 + .../activity/CopyActivityInfoDialog.vue | 71 +++++++++++ .../src/components/activity/ScheduleEntry.vue | 41 +++++- .../program/DialogActivityCreate.vue | 119 ++++++++++++++++++ .../program/picasso/PicassoEntry.vue | 57 ++++++++- frontend/src/locales/de.json | 17 +++ frontend/src/locales/en.json | 17 +++ 10 files changed, 403 insertions(+), 8 deletions(-) create mode 100644 frontend/src/components/activity/CopyActivityInfoDialog.vue diff --git a/api/src/Entity/Activity.php b/api/src/Entity/Activity.php index 09376622587..0be95b4e43d 100644 --- a/api/src/Entity/Activity.php +++ b/api/src/Entity/Activity.php @@ -120,6 +120,13 @@ class Activity extends BaseEntity implements BelongsToCampInterface { #[ORM\JoinColumn(nullable: false)] public ?Category $category = null; + /** + * Copy Contents from this Source-Activity. + */ + #[ApiProperty(example: '/activities/1a2b3c4d')] + #[Groups(['create'])] + public ?Activity $copyActivitySource; + /** * The current assigned ProgressLabel. */ diff --git a/api/src/State/ActivityCreateProcessor.php b/api/src/State/ActivityCreateProcessor.php index 5a3d2bc4d15..c3a25839de1 100644 --- a/api/src/State/ActivityCreateProcessor.php +++ b/api/src/State/ActivityCreateProcessor.php @@ -17,7 +17,7 @@ class ActivityCreateProcessor extends AbstractPersistProcessor { public function __construct( ProcessorInterface $decorated, - private EntityManagerInterface $em, + private EntityManagerInterface $em ) { parent::__construct($decorated); } @@ -26,16 +26,21 @@ public function __construct( * @param Activity $data */ public function onBefore($data, Operation $operation, array $uriVariables = [], array $context = []): Activity { - $data->camp = $data->category?->camp; - if (!isset($data->category?->rootContentNode)) { throw new \UnexpectedValueException('Property rootContentNode of provided category is null. Object of type '.ColumnLayout::class.' expected.'); } - if (!is_a($data->category->rootContentNode, ColumnLayout::class)) { throw new \UnexpectedValueException('Property rootContentNode of provided category is of wrong type. Object of type '.ColumnLayout::class.' expected.'); } + $data->camp = $data->category->camp; + $rootContentNodePrototype = $data->category->rootContentNode; + + if (isset($data->copyActivitySource)) { + // CopyActivity Source is set -> copy it's content (rootContentNode) + $rootContentNodePrototype = $data->copyActivitySource->rootContentNode; + } + $rootContentNode = new ColumnLayout(); $rootContentNode->contentType = $this->em ->getRepository(ContentType::class) @@ -45,7 +50,7 @@ public function onBefore($data, Operation $operation, array $uriVariables = [], // deep copy from category root node $entityMap = new EntityMap(); - $rootContentNode->copyFromPrototype($data->category->rootContentNode, $entityMap); + $rootContentNode->copyFromPrototype($rootContentNodePrototype, $entityMap); return $data; } diff --git a/api/tests/Api/Activities/CreateActivityTest.php b/api/tests/Api/Activities/CreateActivityTest.php index 362b2927a57..7ae78494730 100644 --- a/api/tests/Api/Activities/CreateActivityTest.php +++ b/api/tests/Api/Activities/CreateActivityTest.php @@ -477,6 +477,71 @@ public function testCreateActivityValidatesMissingScheduleEntries() { $this->assertResponseStatusCodeSame(422); } + public function testCreateActivityFromCopySourceValidatesAccess() { + static::createClientWithCredentials(['email' => static::$fixtures['user8memberOnlyInCamp2']->getEmail()])->request( + 'POST', + '/activities', + ['json' => $this->getExampleWritePayload( + [ + 'copyActivitySource' => $this->getIriFor('activity1'), + 'category' => $this->getIriFor('category1camp2'), + 'scheduleEntries' => [ + [ + 'period' => $this->getIriFor('period1camp2'), + 'start' => '2023-03-25T15:00:00+00:00', + 'end' => '2023-03-25T16:00:00+00:00', + ], + ], + ], + [] + )] + ); + + // No Access on activity1 -> BadRequest + $this->assertResponseStatusCodeSame(400); + } + + public function testCreateActivityFromCopySourceWithinSameCamp() { + static::createClientWithCredentials()->request( + 'POST', + '/activities', + ['json' => $this->getExampleWritePayload( + [ + 'copyActivitySource' => $this->getIriFor('activity1'), + 'category' => $this->getIriFor('category1'), + ], + [] + )] + ); + + // No Access on activity1 -> BadRequest + $this->assertResponseStatusCodeSame(201); + } + + public function testCreateActivityFromCopySourceAcrossCamp() { + static::createClientWithCredentials()->request( + 'POST', + '/activities', + ['json' => $this->getExampleWritePayload( + [ + 'copyActivitySource' => $this->getIriFor('activity1'), + 'category' => $this->getIriFor('category1camp2'), + 'scheduleEntries' => [ + [ + 'period' => $this->getIriFor('period1camp2'), + 'start' => '2023-03-25T15:00:00+00:00', + 'end' => '2023-03-25T16:00:00+00:00', + ], + ], + ], + [] + )] + ); + + // No Access on activity1 -> BadRequest + $this->assertResponseStatusCodeSame(201); + } + /** * @throws RedirectionExceptionInterface * @throws DecodingExceptionInterface @@ -509,6 +574,7 @@ public function getExampleWritePayload($attributes = [], $except = []) { Activity::class, Post::class, array_merge([ + 'copyActivitySource' => null, 'category' => $this->getIriFor('category1'), 'progressLabel' => null, 'scheduleEntries' => [ diff --git a/api/tests/State/ActivityCreateProcessorTest.php b/api/tests/State/ActivityCreateProcessorTest.php index 633bf2fcd5a..9ad5991aec9 100644 --- a/api/tests/State/ActivityCreateProcessorTest.php +++ b/api/tests/State/ActivityCreateProcessorTest.php @@ -41,6 +41,7 @@ protected function setUp(): void { $categoryRoot->contentType = $contentType; $this->activity->category->setRootContentNode($categoryRoot); + // EntityManager $repository = $this->createMock(EntityRepository::class); $this->em->method('getRepository')->willReturn($repository); $repository->method('findOneBy')->willReturn($contentType); diff --git a/frontend/src/components/activity/CopyActivityInfoDialog.vue b/frontend/src/components/activity/CopyActivityInfoDialog.vue new file mode 100644 index 00000000000..351368d8042 --- /dev/null +++ b/frontend/src/components/activity/CopyActivityInfoDialog.vue @@ -0,0 +1,71 @@ + + + diff --git a/frontend/src/components/activity/ScheduleEntry.vue b/frontend/src/components/activity/ScheduleEntry.vue index fecaf2688ba..c500cba48dd 100644 --- a/frontend/src/components/activity/ScheduleEntry.vue +++ b/frontend/src/components/activity/ScheduleEntry.vue @@ -117,6 +117,18 @@ Displays a single scheduleEntry + + + mdi-content-copy + + + {{ $tc('components.activity.scheduleEntry.copyScheduleEntry') }} + + + + + + + + +
+
+ {{ $tc('components.program.dialogActivityCreate.clipboard') }} + + + + mdi-clipboard-check-outline + + + + + {{ copyActivitySource.title }} + + + {{ copyActivitySource.camp().title }} + + + + + + +
+
@@ -24,13 +77,16 @@ import DialogForm from '@/components/dialog/DialogForm.vue' import DialogBase from '@/components/dialog/DialogBase.vue' import DialogActivityForm from './DialogActivityForm.vue' +import CopyActivityInfoDialog from '@/components/activity/CopyActivityInfoDialog.vue' import { uniqueId } from 'lodash' +import router from '@/router.js' export default { name: 'DialogActivityCreate', components: { DialogForm, DialogActivityForm, + CopyActivityInfoDialog, }, extends: DialogBase, props: { @@ -41,14 +97,41 @@ export default { }, data() { return { + clipboardPermission: 'unknown', + copyActivitySource: null, entityProperties: ['title', 'location', 'scheduleEntries'], embeddedEntities: ['category'], entityUri: '/activities', } }, + computed: { + hasCopyActivitySource() { + return this.copyActivitySource != null && this.copyActivitySource._meta.self != null + }, + copyContent: { + get() { + return this.entityData.copyActivitySource != null + }, + set(val) { + if (val) { + this.entityData.copyActivitySource = this.copyActivitySource._meta.self + this.entityData.title = this.copyActivitySource.title + if ( + this.period().camp()._meta.self == this.copyActivitySource.camp()._meta.self + ) { + console.log(this.copyActivitySource.category()._meta.self) + this.entityData.category = this.copyActivitySource.category()._meta.self + } + } else { + this.entityData.copyActivitySource = null + } + }, + }, + }, watch: { showDialog: function (showDialog) { if (showDialog) { + this.refreshCopyActivitySource() this.setEntityData({ title: this.entityData?.title || this.$tc('entity.activity.new'), location: '', @@ -61,8 +144,10 @@ export default { deleted: false, }, ], + copyActivitySource: null, }) } else { + this.canReadClipboard = 'unknown' // clear the variable parts of the form on exit this.entityData.location = '' this.entityData.scheduleEntries = [] @@ -70,6 +155,40 @@ export default { }, }, methods: { + refreshCopyActivitySource() { + navigator.permissions.query({ name: 'clipboard-read' }).then((p) => { + this.$set(this, 'clipboardPermission', p.state) + this.$set(this, 'copyActivitySource', null) + + if (p.state == 'granted') { + this.getCopyActivitySource().then((activity) => { + this.$set(this, 'copyActivitySource', activity) + }) + } + }) + console.log('refreshCopyActivitySource') + }, + async getCopyActivitySource() { + let url = await navigator.clipboard.readText() + + if (url.startsWith(window.location.origin)) { + url = url.substring(window.location.origin.length) + let match = router.matcher.match(url) + + if (match.name == 'activity') { + var scheduleEntry = await this.api + .get() + .scheduleEntries({ id: match.params['scheduleEntryId'] }) + + return await scheduleEntry.activity() + } + } + return null + }, + async clearClipboard() { + await navigator.clipboard.writeText('') + this.refreshCopyActivitySource() + }, cancelCreate() { this.close() }, diff --git a/frontend/src/components/program/picasso/PicassoEntry.vue b/frontend/src/components/program/picasso/PicassoEntry.vue index 643b1937dba..cde0698d7c4 100644 --- a/frontend/src/components/program/picasso/PicassoEntry.vue +++ b/frontend/src/components/program/picasso/PicassoEntry.vue @@ -12,6 +12,21 @@ :style="colorStyles" v-on="listeners" > + + + mdi-content-copy + + + + @@ -321,6 +354,26 @@ export default { } } +.e-picasso-entry__copy-url { + display: none; + position: absolute; + right: 20px; + top: 0; + max-height: calc(100% + 4px); + padding: 0 !important; + min-width: 20px !important; + border-radius: 0 0px 0 4px !important; + z-index: 100; +} + +.e-picasso-entry:hover .e-picasso-entry__copy-url { + display: inline-block; + background-color: rgba(0, 0, 0, 0.75); + &:hover { + background-color: black; + } +} + .e-picasso-entry__quickedit { display: none; position: absolute; @@ -329,7 +382,7 @@ export default { max-height: calc(100% + 4px); padding: 0 !important; min-width: 20px !important; - border-radius: 0 3px 0 4px !important; + border-radius: 0 3px 0 0 !important; z-index: 100; } diff --git a/frontend/src/locales/de.json b/frontend/src/locales/de.json index 51caa4115aa..5c5ecbfa0c5 100644 --- a/frontend/src/locales/de.json +++ b/frontend/src/locales/de.json @@ -17,12 +17,20 @@ "title": "Wirklich löschen?" } }, + "copyActivityInfoDialog": { + "title": "Aktivität kopieren & einfügen", + "description": "Damit du eine kopierte Aktivität einfügen kannst, musst du eCamp erlauben deine Zwischenablage zu lesen.", + "allow": "Jetzt erlauben", + "denied": "Du hast das Lesen von der Zwischenablage untersagt. Du kannst daher kopierte Aktivitäten nicht einfügen.", + "granted": "Du kannst nun kopierte Aktivitäten einfügen." + }, "menuCardlessContentNode": { "deletingDisabled": "Muss leer sein zum Löschen" }, "scheduleEntry": { "backToContents": "Zurück zum Bearbeiten des Inhalts", "changeLayout": "Layout ändern", + "copyScheduleEntry": "Aktivität kopieren", "deleteWarning": "Möchtest du diese Aktivität wirklich löschen? Der komplette Inhalt dieser Aktivität wird gelöscht." } }, @@ -290,6 +298,12 @@ } }, "program": { + "dialogActivityCreate": { + "clipboard": "Zwischenablage", + "copyPastActivity": "Aktivität kopieren & einfügen", + "clearClipboard": "Zwischenablage leeren", + "copyActivityContent": "Inhalt von Aktivität kopieren" + }, "formScheduleEntryItem": { "end": "Ende", "start": "Start" @@ -405,6 +419,9 @@ "409": "Uuuups... Diese Aktion hat zu einem Fehler auf dem Server geführt.", "short": "Serverfehler" }, + "toast": { + "copied": "{source} kopiert" + }, "validation": { "greaterThanOrEqual_date": "{_field_} darf nicht vor {min} liegen", "greaterThan_time": "{_field_} muss später als {min} sein", diff --git a/frontend/src/locales/en.json b/frontend/src/locales/en.json index 15a4dba791c..59ea25b163b 100644 --- a/frontend/src/locales/en.json +++ b/frontend/src/locales/en.json @@ -17,12 +17,20 @@ "title": "Really delete?" } }, + "copyActivityInfoDialog": { + "title": "Copy & paste activity", + "description": "In order to paste a copied activity, you must allow eCamp to read your clipboard.", + "allow": "Allow now", + "denied": "You have denied access to your clipboard. Therefore, you cannot paste copied activities.", + "granted": "You can now paste copied activities." + }, "menuCardlessContentNode": { "deletingDisabled": "Must be empty to delete" }, "scheduleEntry": { "backToContents": "Back to editing content", "changeLayout": "Change layout", + "copyScheduleEntry": "Copy activity", "deleteWarning": "Do you really want to delete this activity? All content of this activity will be removed." } }, @@ -290,6 +298,12 @@ } }, "program": { + "dialogActivityCreate": { + "clipboard": "Clipboard", + "copyPastActivity": "Copy & paste activity", + "clearClipboard": "Clear clipboard", + "copyActivityContent": "Copy content from activity" + }, "formScheduleEntryItem": { "end": "End", "start": "Start" @@ -405,6 +419,9 @@ "409": "Uuuups... This action caused an server-side error.", "short": "Server error" }, + "toast": { + "copied": "{source} copied" + }, "validation": { "greaterThanOrEqual_date": "{_field_} may not be earlier than {min}", "greaterThan_time": "{_field_} must be later than {min}", From 04d854b48e40c90ebe8d097d3c4e5fddb0b2846d Mon Sep 17 00:00:00 2001 From: Pirmin Mattmann Date: Sat, 21 Oct 2023 18:45:33 +0200 Subject: [PATCH 003/251] update snapshot --- ...Test__testOpenApiSpecMatchesSnapshot__1.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testOpenApiSpecMatchesSnapshot__1.yml b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testOpenApiSpecMatchesSnapshot__1.yml index 448beb3179e..275d96335a4 100644 --- a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testOpenApiSpecMatchesSnapshot__1.yml +++ b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testOpenApiSpecMatchesSnapshot__1.yml @@ -347,6 +347,12 @@ components: example: /categories/1a2b3c4d format: iri-reference type: string + copyActivitySource: + description: 'Copy Contents from this Source-Activity.' + example: /activities/1a2b3c4d + format: iri-reference + nullable: true + type: string location: description: "The physical location where this activity's programme will be carried out." example: Spielwiese @@ -736,6 +742,12 @@ components: example: /categories/1a2b3c4d format: iri-reference type: string + copyActivitySource: + description: 'Copy Contents from this Source-Activity.' + example: /activities/1a2b3c4d + format: iri-reference + nullable: true + type: string location: description: "The physical location where this activity's programme will be carried out." example: Spielwiese @@ -1155,6 +1167,12 @@ components: example: /categories/1a2b3c4d format: iri-reference type: string + copyActivitySource: + description: 'Copy Contents from this Source-Activity.' + example: /activities/1a2b3c4d + format: iri-reference + nullable: true + type: string location: description: "The physical location where this activity's programme will be carried out." example: Spielwiese From 84bef9a52edebd530b9f5121b102e74d1c23da0d Mon Sep 17 00:00:00 2001 From: Pirmin Mattmann Date: Sat, 21 Oct 2023 22:05:49 +0200 Subject: [PATCH 004/251] code-review --- .../components/activity/CopyActivityInfoDialog.vue | 12 ++++++------ frontend/src/components/activity/ScheduleEntry.vue | 6 +++--- .../src/components/program/DialogActivityCreate.vue | 2 -- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/frontend/src/components/activity/CopyActivityInfoDialog.vue b/frontend/src/components/activity/CopyActivityInfoDialog.vue index 351368d8042..902430d88ea 100644 --- a/frontend/src/components/activity/CopyActivityInfoDialog.vue +++ b/frontend/src/components/activity/CopyActivityInfoDialog.vue @@ -13,17 +13,17 @@

{{ $tc('components.activity.copyActivityInfoDialog.description') }}

-

+

{{ $tc('components.activity.copyActivityInfoDialog.allow') }}

-

+

{{ $tc('components.activity.copyActivityInfoDialog.granted') }}

-

+

{{ $tc('components.activity.copyActivityInfoDialog.denied') }}

@@ -46,7 +46,7 @@ export default { }, async mounted() { // read current permission - let res = await navigator.permissions.query({ name: 'clipboard-read' }) + const res = await navigator.permissions.query({ name: 'clipboard-read' }) this.clipboardReadState = res.state }, methods: { @@ -55,14 +55,14 @@ export default { }, async requestClipboardAccess() { // if permission is not yet requested, request it - if (this.clipboardReadState == 'prompt') { + if (this.clipboardReadState === 'prompt') { try { await navigator.clipboard.readText() } catch { console.log('clipboard read is denied') } - let res = await navigator.permissions.query({ name: 'clipboard-read' }) + const res = await navigator.permissions.query({ name: 'clipboard-read' }) this.clipboardReadState = res.state } }, diff --git a/frontend/src/components/activity/ScheduleEntry.vue b/frontend/src/components/activity/ScheduleEntry.vue index c500cba48dd..03ac23f39f3 100644 --- a/frontend/src/components/activity/ScheduleEntry.vue +++ b/frontend/src/components/activity/ScheduleEntry.vue @@ -371,13 +371,13 @@ export default { } }, async copyUrlToClipboard() { - let res = await navigator.permissions.query({ name: 'clipboard-read' }) + const res = await navigator.permissions.query({ name: 'clipboard-read' }) if (res.state == 'prompt') { this.$refs.copyInfoDialog.open() } - let scheduleEntry = scheduleEntryRoute(this.scheduleEntry()) - let url = window.location.origin + router.resolve(scheduleEntry).href + const scheduleEntry = scheduleEntryRoute(this.scheduleEntry()) + const url = window.location.origin + router.resolve(scheduleEntry).href navigator.clipboard.writeText(url) this.$toast.info( diff --git a/frontend/src/components/program/DialogActivityCreate.vue b/frontend/src/components/program/DialogActivityCreate.vue index c20acc1c59d..a56bc0b4d64 100644 --- a/frontend/src/components/program/DialogActivityCreate.vue +++ b/frontend/src/components/program/DialogActivityCreate.vue @@ -119,7 +119,6 @@ export default { if ( this.period().camp()._meta.self == this.copyActivitySource.camp()._meta.self ) { - console.log(this.copyActivitySource.category()._meta.self) this.entityData.category = this.copyActivitySource.category()._meta.self } } else { @@ -166,7 +165,6 @@ export default { }) } }) - console.log('refreshCopyActivitySource') }, async getCopyActivitySource() { let url = await navigator.clipboard.readText() From 618b40eb60279f7b3b22bf67f49227a372cb21bf Mon Sep 17 00:00:00 2001 From: Pirmin Mattmann Date: Sat, 21 Oct 2023 22:08:55 +0200 Subject: [PATCH 005/251] fix comment --- api/tests/Api/Activities/CreateActivityTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/tests/Api/Activities/CreateActivityTest.php b/api/tests/Api/Activities/CreateActivityTest.php index 7ae78494730..cf2f2b80962 100644 --- a/api/tests/Api/Activities/CreateActivityTest.php +++ b/api/tests/Api/Activities/CreateActivityTest.php @@ -538,7 +538,7 @@ public function testCreateActivityFromCopySourceAcrossCamp() { )] ); - // No Access on activity1 -> BadRequest + // Activity created $this->assertResponseStatusCodeSame(201); } From da3fd0973dc228bda8ab7fc02bca1c4b3a51c31f Mon Sep 17 00:00:00 2001 From: Pirmin Mattmann Date: Sat, 21 Oct 2023 23:42:38 +0200 Subject: [PATCH 006/251] firefox-compatible --- .../activity/CopyActivityInfoDialog.vue | 10 ++- .../src/components/activity/ScheduleEntry.vue | 10 ++- .../program/DialogActivityCreate.vue | 82 +++++++++++++------ .../program/picasso/PicassoEntry.vue | 10 ++- frontend/src/locales/de.json | 3 +- frontend/src/locales/en.json | 3 +- 6 files changed, 84 insertions(+), 34 deletions(-) diff --git a/frontend/src/components/activity/CopyActivityInfoDialog.vue b/frontend/src/components/activity/CopyActivityInfoDialog.vue index 902430d88ea..d07cd274361 100644 --- a/frontend/src/components/activity/CopyActivityInfoDialog.vue +++ b/frontend/src/components/activity/CopyActivityInfoDialog.vue @@ -45,9 +45,13 @@ export default { } }, async mounted() { - // read current permission - const res = await navigator.permissions.query({ name: 'clipboard-read' }) - this.clipboardReadState = res.state + try { + // read current permission + const res = await navigator.permissions.query({ name: 'clipboard-read' }) + this.clipboardReadState = res.state + } catch { + console.warn('clipboard permission not requestable') + } }, methods: { cancel() { diff --git a/frontend/src/components/activity/ScheduleEntry.vue b/frontend/src/components/activity/ScheduleEntry.vue index 03ac23f39f3..d8db2b9c595 100644 --- a/frontend/src/components/activity/ScheduleEntry.vue +++ b/frontend/src/components/activity/ScheduleEntry.vue @@ -371,9 +371,13 @@ export default { } }, async copyUrlToClipboard() { - const res = await navigator.permissions.query({ name: 'clipboard-read' }) - if (res.state == 'prompt') { - this.$refs.copyInfoDialog.open() + try { + const res = await navigator.permissions.query({ name: 'clipboard-read' }) + if (res.state == 'prompt') { + this.$refs.copyInfoDialog.open() + } + } catch { + console.warn('clipboard permission not requestable') } const scheduleEntry = scheduleEntryRoute(this.scheduleEntry()) diff --git a/frontend/src/components/program/DialogActivityCreate.vue b/frontend/src/components/program/DialogActivityCreate.vue index a56bc0b4d64..1fcaa2876dc 100644 --- a/frontend/src/components/program/DialogActivityCreate.vue +++ b/frontend/src/components/program/DialogActivityCreate.vue @@ -19,7 +19,7 @@ -
- - - -
-
@@ -87,7 +71,28 @@
- + + + @@ -118,6 +123,7 @@ export default { clipboardPermission: 'unknown', copyActivitySource: null, copyActivitySourceUrl: null, + copyActivitySourceUrlShowPopover: false, entityProperties: ['title', 'location', 'scheduleEntries'], embeddedEntities: ['category'], entityUri: '/activities', @@ -180,6 +186,12 @@ export default { this.getCopyActivitySource(url).then((activity) => { this.$set(this, 'copyActivitySource', activity) this.$set(this, 'copyContent', activity != null) + + if (this.copyActivitySourceUrlShowPopover) { + this.$nextTick(() => { + this.$set(this, 'copyActivitySourceUrlShowPopover', false) + }) + } }) }, }, @@ -205,7 +217,7 @@ export default { ) }, async getCopyActivitySource(url) { - if (url.startsWith(window.location.origin)) { + if (url?.startsWith(window.location.origin)) { url = url.substring(window.location.origin.length) let match = router.matcher.match(url) diff --git a/frontend/src/components/program/DialogActivityForm.vue b/frontend/src/components/program/DialogActivityForm.vue index 5f3e132d9e9..9f5a91e73f5 100644 --- a/frontend/src/components/program/DialogActivityForm.vue +++ b/frontend/src/components/program/DialogActivityForm.vue @@ -4,7 +4,9 @@ v-model="localActivity.title" :name="$tc('entity.activity.fields.title')" vee-rules="required" - /> + > + + Date: Sun, 22 Oct 2023 12:07:22 +0200 Subject: [PATCH 008/251] visual improvments; copy location & category --- .../program/DialogActivityCreate.vue | 32 ++++++++++++++++--- .../components/program/DialogActivityForm.vue | 16 ++++++---- 2 files changed, 36 insertions(+), 12 deletions(-) diff --git a/frontend/src/components/program/DialogActivityCreate.vue b/frontend/src/components/program/DialogActivityCreate.vue index c8b3c77b649..21215427dc4 100644 --- a/frontend/src/components/program/DialogActivityCreate.vue +++ b/frontend/src/components/program/DialogActivityCreate.vue @@ -79,7 +79,13 @@ title="Kopierte Aktivität einfügen" > @@ -130,6 +136,9 @@ export default { } }, computed: { + camp() { + return this.period().camp() + }, clipboardUnsccessable() { return this.clipboardPermission === 'unaccessable' }, @@ -144,10 +153,23 @@ export default { if (val) { this.entityData.copyActivitySource = this.copyActivitySource._meta.self this.entityData.title = this.copyActivitySource.title - if ( - this.period().camp()._meta.self == this.copyActivitySource.camp()._meta.self - ) { - this.entityData.category = this.copyActivitySource.category()._meta.self + this.entityData.location = this.copyActivitySource.location + + const sourceCamp = this.copyActivitySource.camp() + const sourceCategory = this.copyActivitySource.category() + + if (this.camp._meta.self == sourceCamp._meta.self) { + // same camp; use came category + this.entityData.category = sourceCategory._meta.self + } else { + // different camp; use category with same short-name + const categories = this.camp + .categories() + .allItems.filter((c) => c.short == sourceCategory.short) + + if (categories.length == 1) { + this.entityData.category = categories[0]._meta.self + } } } else { this.entityData.copyActivitySource = null diff --git a/frontend/src/components/program/DialogActivityForm.vue b/frontend/src/components/program/DialogActivityForm.vue index 9f5a91e73f5..96ea1c009c5 100644 --- a/frontend/src/components/program/DialogActivityForm.vue +++ b/frontend/src/components/program/DialogActivityForm.vue @@ -1,12 +1,14 @@ {{ $tc('components.program.dialogActivityCreate.copySourceInfo') }} @@ -129,6 +130,7 @@ export default { clipboardPermission: 'unknown', copyActivitySource: null, copyActivitySourceUrl: null, + copyActivitySourceUrlLoading: false, copyActivitySourceUrlShowPopover: false, entityProperties: ['title', 'location', 'scheduleEntries'], embeddedEntities: ['category'], @@ -205,24 +207,38 @@ export default { } }, copyActivitySourceUrl: function (url) { - this.getCopyActivitySource(url).then((activityProxy) => { - if (activityProxy != null) { - activityProxy._meta.load.then((activity) => { - this.$set(this, 'copyActivitySource', activity) - this.$set(this, 'copyContent', activity != null) - }) - } else { - this.$set(this, 'copyActivitySource', null) - this.$set(this, 'copyContent', false) - } + this.copyActivitySourceUrlLoading = true + + this.getCopyActivitySource(url).then( + (activityProxy) => { + if (activityProxy != null) { + activityProxy._meta.load.then( + (activity) => { + this.$set(this, 'copyActivitySource', activity) + this.$set(this, 'copyContent', activity != null) + this.copyActivitySourceUrlLoading = false + }, + () => { + this.copyActivitySourceUrlLoading = false + } + ) + } else { + this.$set(this, 'copyActivitySource', null) + this.$set(this, 'copyContent', false) + this.copyActivitySourceUrlLoading = false + } - // if Paste-Popover is shown, close it now - if (this.copyActivitySourceUrlShowPopover) { - this.$nextTick(() => { - this.$set(this, 'copyActivitySourceUrlShowPopover', false) - }) + // if Paste-Popover is shown, close it now + if (this.copyActivitySourceUrlShowPopover) { + this.$nextTick(() => { + this.$set(this, 'copyActivitySourceUrlShowPopover', false) + }) + } + }, + () => { + this.copyActivitySourceUrlLoading = false } - }) + ) }, }, methods: { From 04cf90dbc6b5b744f8de464e513243e4f8325dfc Mon Sep 17 00:00:00 2001 From: Pirmin Mattmann Date: Sun, 22 Oct 2023 14:19:47 +0200 Subject: [PATCH 012/251] fix coloring; allow paste when clipboard-access is denied --- api/tests/Api/Activities/CreateActivityTest.php | 2 +- .../collaborator/PromptCollaboratorDeactivate.vue | 1 + .../components/program/DialogActivityCreate.vue | 5 ++++- frontend/src/components/prompt/PopoverPrompt.vue | 15 +++++++++++++-- .../src/components/prompt/PromptEntityDelete.vue | 1 + 5 files changed, 20 insertions(+), 4 deletions(-) diff --git a/api/tests/Api/Activities/CreateActivityTest.php b/api/tests/Api/Activities/CreateActivityTest.php index cf2f2b80962..b61e75cfbb2 100644 --- a/api/tests/Api/Activities/CreateActivityTest.php +++ b/api/tests/Api/Activities/CreateActivityTest.php @@ -514,7 +514,7 @@ public function testCreateActivityFromCopySourceWithinSameCamp() { )] ); - // No Access on activity1 -> BadRequest + // Activity created $this->assertResponseStatusCodeSame(201); } diff --git a/frontend/src/components/collaborator/PromptCollaboratorDeactivate.vue b/frontend/src/components/collaborator/PromptCollaboratorDeactivate.vue index 68fc3beaca2..dcb3e65de5b 100644 --- a/frontend/src/components/collaborator/PromptCollaboratorDeactivate.vue +++ b/frontend/src/components/collaborator/PromptCollaboratorDeactivate.vue @@ -1,6 +1,7 @@