From 555f40b90326fd319b208197aa7cdda7018dcb9c Mon Sep 17 00:00:00 2001 From: Manuel Meister Date: Sun, 2 Jun 2024 11:58:45 +0200 Subject: [PATCH] Add custom color & abbr on CampCollaboration --- .../schema/Version20240507154923.php | 27 +++ api/src/Entity/CampCollaboration.php | 19 ++ ... with data set camp_collaborations__1.json | 24 +++ ... with data set camp_collaborations__1.json | 2 + ...tchesStructure with data set camps__1.json | 12 ++ ...est__testOpenApiSpecMatchesSnapshot__1.yml | 197 ++++++++++++++++++ common/helpers/campCollaborationInitials.js | 4 +- common/helpers/colors.js | 4 + common/locales/de.json | 2 + common/locales/en.json | 2 + common/locales/fr.json | 2 + common/locales/it.json | 7 + .../collaborator/CollaboratorCreate.vue | 4 +- .../collaborator/CollaboratorEdit.vue | 4 +- .../collaborator/CollaboratorForm.vue | 4 + frontend/src/views/dev/Controls.vue | 7 +- 16 files changed, 316 insertions(+), 5 deletions(-) create mode 100644 api/migrations/schema/Version20240507154923.php diff --git a/api/migrations/schema/Version20240507154923.php b/api/migrations/schema/Version20240507154923.php new file mode 100644 index 0000000000..0a95ef486b --- /dev/null +++ b/api/migrations/schema/Version20240507154923.php @@ -0,0 +1,27 @@ +addSql('ALTER TABLE camp_collaboration ADD color VARCHAR(8) DEFAULT NULL'); + $this->addSql('ALTER TABLE camp_collaboration ADD abbr VARCHAR(2) DEFAULT NULL'); + } + + public function down(Schema $schema): void { + $this->addSql('ALTER TABLE camp_collaboration DROP color'); + $this->addSql('ALTER TABLE camp_collaboration DROP abbr'); + } +} diff --git a/api/src/Entity/CampCollaboration.php b/api/src/Entity/CampCollaboration.php index 16e2a0fb5e..cfa86dd763 100644 --- a/api/src/Entity/CampCollaboration.php +++ b/api/src/Entity/CampCollaboration.php @@ -202,6 +202,25 @@ class CampCollaboration extends BaseEntity implements BelongsToCampInterface { #[ORM\Column(type: 'string', length: 16, nullable: false)] public string $role; + /** + * The color of the avatar as a hex color string. + */ + #[InputFilter\Trim] + #[Assert\Regex(pattern: '/^#[0-9a-zA-Z]{6}$/')] + #[ApiProperty(example: '#4DBB52')] + #[Groups(['read', 'write'])] + #[ORM\Column(type: 'string', length: 8, nullable: true)] + public ?string $color = null; + + /** + * The abbreviation in th avatar. + */ + #[InputFilter\Trim] + #[ApiProperty(example: 'AB')] + #[Groups(['read', 'write'])] + #[ORM\Column(type: 'string', length: 2, nullable: true)] + public ?string $abbr = null; + #[ApiProperty(readable: false, writable: false)] #[ORM\Column(type: 'text', nullable: true)] public ?string $collaborationAcceptedBy = null; diff --git a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set camp_collaborations__1.json b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set camp_collaborations__1.json index cd375a7770..56f0d81929 100644 --- a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set camp_collaborations__1.json +++ b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetCollectionMatchesStructure with data set camp_collaborations__1.json @@ -61,6 +61,8 @@ }, "user": "escaped_value" }, + "abbr": "escaped_value", + "color": "escaped_value", "id": "escaped_value", "inviteEmail": "escaped_value", "role": "escaped_value", @@ -139,6 +141,8 @@ "href": "escaped_value" } }, + "abbr": "escaped_value", + "color": "escaped_value", "id": "escaped_value", "inviteEmail": "escaped_value", "role": "escaped_value", @@ -217,6 +221,8 @@ "href": "escaped_value" } }, + "abbr": "escaped_value", + "color": "escaped_value", "id": "escaped_value", "inviteEmail": "escaped_value", "role": "escaped_value", @@ -295,6 +301,8 @@ "href": "escaped_value" } }, + "abbr": "escaped_value", + "color": "escaped_value", "id": "escaped_value", "inviteEmail": "escaped_value", "role": "escaped_value", @@ -373,6 +381,8 @@ "href": "escaped_value" } }, + "abbr": "escaped_value", + "color": "escaped_value", "id": "escaped_value", "inviteEmail": "escaped_value", "role": "escaped_value", @@ -451,6 +461,8 @@ "href": "escaped_value" } }, + "abbr": "escaped_value", + "color": "escaped_value", "id": "escaped_value", "inviteEmail": "escaped_value", "role": "escaped_value", @@ -529,6 +541,8 @@ "href": "escaped_value" } }, + "abbr": "escaped_value", + "color": "escaped_value", "id": "escaped_value", "inviteEmail": "escaped_value", "role": "escaped_value", @@ -607,6 +621,8 @@ "href": "escaped_value" } }, + "abbr": "escaped_value", + "color": "escaped_value", "id": "escaped_value", "inviteEmail": "escaped_value", "role": "escaped_value", @@ -685,6 +701,8 @@ "href": "escaped_value" } }, + "abbr": "escaped_value", + "color": "escaped_value", "id": "escaped_value", "inviteEmail": "escaped_value", "role": "escaped_value", @@ -763,6 +781,8 @@ "href": "escaped_value" } }, + "abbr": "escaped_value", + "color": "escaped_value", "id": "escaped_value", "inviteEmail": "escaped_value", "role": "escaped_value", @@ -841,6 +861,8 @@ "href": "escaped_value" } }, + "abbr": "escaped_value", + "color": "escaped_value", "id": "escaped_value", "inviteEmail": "escaped_value", "role": "escaped_value", @@ -919,6 +941,8 @@ "href": "escaped_value" } }, + "abbr": "escaped_value", + "color": "escaped_value", "id": "escaped_value", "inviteEmail": "escaped_value", "role": "escaped_value", diff --git a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetItemMatchesStructure with data set camp_collaborations__1.json b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetItemMatchesStructure with data set camp_collaborations__1.json index 2c1376f8fa..78670ef47e 100644 --- a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetItemMatchesStructure with data set camp_collaborations__1.json +++ b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetItemMatchesStructure with data set camp_collaborations__1.json @@ -71,6 +71,8 @@ "href": "escaped_value" } }, + "abbr": "escaped_value", + "color": "escaped_value", "id": "escaped_value", "inviteEmail": "escaped_value", "role": "escaped_value", diff --git a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetItemMatchesStructure with data set camps__1.json b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetItemMatchesStructure with data set camps__1.json index 6739d732be..1626ee343f 100644 --- a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetItemMatchesStructure with data set camps__1.json +++ b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testGetItemMatchesStructure with data set camps__1.json @@ -14,6 +14,8 @@ }, "user": "escaped_value" }, + "abbr": "escaped_value", + "color": "escaped_value", "id": "escaped_value", "inviteEmail": "escaped_value", "role": "escaped_value", @@ -45,6 +47,8 @@ "href": "escaped_value" } }, + "abbr": "escaped_value", + "color": "escaped_value", "id": "escaped_value", "inviteEmail": "escaped_value", "role": "escaped_value", @@ -76,6 +80,8 @@ "href": "escaped_value" } }, + "abbr": "escaped_value", + "color": "escaped_value", "id": "escaped_value", "inviteEmail": "escaped_value", "role": "escaped_value", @@ -107,6 +113,8 @@ "href": "escaped_value" } }, + "abbr": "escaped_value", + "color": "escaped_value", "id": "escaped_value", "inviteEmail": "escaped_value", "role": "escaped_value", @@ -138,6 +146,8 @@ "href": "escaped_value" } }, + "abbr": "escaped_value", + "color": "escaped_value", "id": "escaped_value", "inviteEmail": "escaped_value", "role": "escaped_value", @@ -169,6 +179,8 @@ "href": "escaped_value" } }, + "abbr": "escaped_value", + "color": "escaped_value", "id": "escaped_value", "inviteEmail": "escaped_value", "role": "escaped_value", diff --git a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testOpenApiSpecMatchesSnapshot__1.yml b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testOpenApiSpecMatchesSnapshot__1.yml index 8aec149818..64a1035d82 100644 --- a/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testOpenApiSpecMatchesSnapshot__1.yml +++ b/api/tests/Api/SnapshotTests/__snapshots__/ResponseSnapshotTest__testOpenApiSpecMatchesSnapshot__1.yml @@ -5322,11 +5322,23 @@ components: deprecated: false description: 'A user participating in some way in the planning or realization of a camp.' properties: + abbr: + description: 'The abbreviation in th avatar.' + example: AB + type: + - 'null' + - string camp: description: 'The camp that the collaborator is part of. Cannot be changed once the campCollaboration is created.' example: /camps/1a2b3c4d format: iri-reference type: string + color: + description: 'The color of the avatar as a hex color string.' + example: '#4DBB52' + type: + - 'null' + - string id: description: 'An internal, unique, randomly generated identifier of this entity.' example: 1a2b3c4d @@ -5363,11 +5375,26 @@ components: deprecated: false description: '' properties: + abbr: + description: 'The abbreviation in th avatar.' + example: AB + maxLength: 2 + type: + - 'null' + - string camp: description: 'The camp that the collaborator is part of. Cannot be changed once the campCollaboration is created.' example: /camps/1a2b3c4d format: iri-reference type: string + color: + description: 'The color of the avatar as a hex color string.' + example: '#4DBB52' + maxLength: 8 + pattern: '^(#[0-9a-zA-Z]{6})$' + type: + - 'null' + - string id: description: 'An internal, unique, randomly generated identifier of this entity.' example: 1a2b3c4d @@ -5424,6 +5451,13 @@ components: deprecated: false description: 'A user participating in some way in the planning or realization of a camp.' properties: + abbr: + description: 'The abbreviation in th avatar.' + example: AB + maxLength: 2 + type: + - 'null' + - string camp: anyOf: - @@ -5431,6 +5465,14 @@ components: - type: 'null' readOnly: true + color: + description: 'The color of the avatar as a hex color string.' + example: '#4DBB52' + maxLength: 8 + pattern: '^(#[0-9a-zA-Z]{6})$' + type: + - 'null' + - string id: description: 'An internal, unique, randomly generated identifier of this entity.' example: 1a2b3c4d @@ -5491,11 +5533,26 @@ components: deprecated: false description: 'A user participating in some way in the planning or realization of a camp.' properties: + abbr: + description: 'The abbreviation in th avatar.' + example: AB + maxLength: 2 + type: + - 'null' + - string camp: description: 'The camp that the collaborator is part of. Cannot be changed once the campCollaboration is created.' example: /camps/1a2b3c4d format: iri-reference type: string + color: + description: 'The color of the avatar as a hex color string.' + example: '#4DBB52' + maxLength: 8 + pattern: '^(#[0-9a-zA-Z]{6})$' + type: + - 'null' + - string inviteEmail: description: |- The receiver email address of the invitation email, in case the collaboration does not yet have @@ -5535,6 +5592,21 @@ components: deprecated: false description: 'A user participating in some way in the planning or realization of a camp.' properties: + abbr: + description: 'The abbreviation in th avatar.' + example: AB + maxLength: 2 + type: + - 'null' + - string + color: + description: 'The color of the avatar as a hex color string.' + example: '#4DBB52' + maxLength: 8 + pattern: '^(#[0-9a-zA-Z]{6})$' + type: + - 'null' + - string role: description: |- The role that this person has in the camp. Depending on the role, the collaborator might have @@ -5574,6 +5646,17 @@ components: maxLength: 16 readOnly: true type: string + abbr: + description: 'The abbreviation in th avatar.' + example: AB + maxLength: 2 + type: ['null', string] + color: + description: 'The color of the avatar as a hex color string.' + example: '#4DBB52' + maxLength: 8 + pattern: '^(#[0-9a-zA-Z]{6})$' + type: ['null', string] inviteEmail: description: |- The receiver email address of the invitation email, in case the collaboration does not yet have @@ -5648,11 +5731,23 @@ components: type: string type: object type: object + abbr: + description: 'The abbreviation in th avatar.' + example: AB + type: + - 'null' + - string camp: description: 'The camp that the collaborator is part of. Cannot be changed once the campCollaboration is created.' example: /camps/1a2b3c4d format: iri-reference type: string + color: + description: 'The color of the avatar as a hex color string.' + example: '#4DBB52' + type: + - 'null' + - string id: description: 'An internal, unique, randomly generated identifier of this entity.' example: 1a2b3c4d @@ -5698,11 +5793,26 @@ components: type: string type: object type: object + abbr: + description: 'The abbreviation in th avatar.' + example: AB + maxLength: 2 + type: + - 'null' + - string camp: description: 'The camp that the collaborator is part of. Cannot be changed once the campCollaboration is created.' example: /camps/1a2b3c4d format: iri-reference type: string + color: + description: 'The color of the avatar as a hex color string.' + example: '#4DBB52' + maxLength: 8 + pattern: '^(#[0-9a-zA-Z]{6})$' + type: + - 'null' + - string id: description: 'An internal, unique, randomly generated identifier of this entity.' example: 1a2b3c4d @@ -5768,6 +5878,13 @@ components: type: string type: object type: object + abbr: + description: 'The abbreviation in th avatar.' + example: AB + maxLength: 2 + type: + - 'null' + - string camp: anyOf: - @@ -5775,6 +5892,14 @@ components: - type: 'null' readOnly: true + color: + description: 'The color of the avatar as a hex color string.' + example: '#4DBB52' + maxLength: 8 + pattern: '^(#[0-9a-zA-Z]{6})$' + type: + - 'null' + - string id: description: 'An internal, unique, randomly generated identifier of this entity.' example: 1a2b3c4d @@ -5840,11 +5965,26 @@ components: type: string type: object type: object + abbr: + description: 'The abbreviation in th avatar.' + example: AB + maxLength: 2 + type: + - 'null' + - string camp: description: 'The camp that the collaborator is part of. Cannot be changed once the campCollaboration is created.' example: /camps/1a2b3c4d format: iri-reference type: string + color: + description: 'The color of the avatar as a hex color string.' + example: '#4DBB52' + maxLength: 8 + pattern: '^(#[0-9a-zA-Z]{6})$' + type: + - 'null' + - string inviteEmail: description: |- The receiver email address of the invitation email, in case the collaboration does not yet have @@ -5907,11 +6047,23 @@ components: '@type': readOnly: true type: string + abbr: + description: 'The abbreviation in th avatar.' + example: AB + type: + - 'null' + - string camp: description: 'The camp that the collaborator is part of. Cannot be changed once the campCollaboration is created.' example: /camps/1a2b3c4d format: iri-reference type: string + color: + description: 'The color of the avatar as a hex color string.' + example: '#4DBB52' + type: + - 'null' + - string id: description: 'An internal, unique, randomly generated identifier of this entity.' example: 1a2b3c4d @@ -5971,11 +6123,26 @@ components: '@type': readOnly: true type: string + abbr: + description: 'The abbreviation in th avatar.' + example: AB + maxLength: 2 + type: + - 'null' + - string camp: description: 'The camp that the collaborator is part of. Cannot be changed once the campCollaboration is created.' example: /camps/1a2b3c4d format: iri-reference type: string + color: + description: 'The color of the avatar as a hex color string.' + example: '#4DBB52' + maxLength: 8 + pattern: '^(#[0-9a-zA-Z]{6})$' + type: + - 'null' + - string id: description: 'An internal, unique, randomly generated identifier of this entity.' example: 1a2b3c4d @@ -6055,6 +6222,13 @@ components: '@type': readOnly: true type: string + abbr: + description: 'The abbreviation in th avatar.' + example: AB + maxLength: 2 + type: + - 'null' + - string camp: anyOf: - @@ -6062,6 +6236,14 @@ components: - type: 'null' readOnly: true + color: + description: 'The color of the avatar as a hex color string.' + example: '#4DBB52' + maxLength: 8 + pattern: '^(#[0-9a-zA-Z]{6})$' + type: + - 'null' + - string id: description: 'An internal, unique, randomly generated identifier of this entity.' example: 1a2b3c4d @@ -6118,11 +6300,26 @@ components: deprecated: false description: 'A user participating in some way in the planning or realization of a camp.' properties: + abbr: + description: 'The abbreviation in th avatar.' + example: AB + maxLength: 2 + type: + - 'null' + - string camp: description: 'The camp that the collaborator is part of. Cannot be changed once the campCollaboration is created.' example: /camps/1a2b3c4d format: iri-reference type: string + color: + description: 'The color of the avatar as a hex color string.' + example: '#4DBB52' + maxLength: 8 + pattern: '^(#[0-9a-zA-Z]{6})$' + type: + - 'null' + - string inviteEmail: description: |- The receiver email address of the invitation email, in case the collaboration does not yet have diff --git a/common/helpers/campCollaborationInitials.js b/common/helpers/campCollaborationInitials.js index 63c1c46a6e..0c2f37422a 100644 --- a/common/helpers/campCollaborationInitials.js +++ b/common/helpers/campCollaborationInitials.js @@ -5,5 +5,7 @@ import initials from './initials.js' * Returns two characters to display for a camp collaboration based on its user */ export default function (campCollaboration) { - return initials(campCollaborationDisplayName(campCollaboration, null, false)) + return campCollaboration?.abbr + ? campCollaboration.abbr + : initials(campCollaborationDisplayName(campCollaboration, null, false)) } diff --git a/common/helpers/colors.js b/common/helpers/colors.js index 63fa103ea4..2b79dc1025 100644 --- a/common/helpers/colors.js +++ b/common/helpers/colors.js @@ -58,6 +58,10 @@ function campCollaborationColor(campCollaboration) { (typeof campCollaboration.user === 'function' && campCollaboration.user()._meta?.loading) + if (campCollaboration?.color) { + return campCollaboration.color + } + return idToColor( typeof campCollaboration.user === 'function' ? campCollaboration.user().id diff --git a/common/locales/de.json b/common/locales/de.json index 01caa21b8c..f403aca3d2 100644 --- a/common/locales/de.json +++ b/common/locales/de.json @@ -140,6 +140,8 @@ }, "campCollaboration": { "fields": { + "abbr": "Abkürzung", + "color": "Farbe", "inviteEmail": "E-Mail", "role": "Rolle", "status": "Status" diff --git a/common/locales/en.json b/common/locales/en.json index 2fffbf8059..48464cabda 100644 --- a/common/locales/en.json +++ b/common/locales/en.json @@ -147,6 +147,8 @@ }, "campCollaboration": { "fields": { + "abbr": "Abbreviation", + "color": "Color", "inviteEmail": "Email", "role": "Role", "status": "Status" diff --git a/common/locales/fr.json b/common/locales/fr.json index 5eb45fcf17..a859087449 100644 --- a/common/locales/fr.json +++ b/common/locales/fr.json @@ -129,6 +129,8 @@ }, "campCollaboration": { "fields": { + "abbr": "Abréviation", + "color": "Couleur", "inviteEmail": "E-mail", "role": "Role", "status": "Statut" diff --git a/common/locales/it.json b/common/locales/it.json index 2c7bd291f6..d2f8d602e1 100644 --- a/common/locales/it.json +++ b/common/locales/it.json @@ -122,6 +122,13 @@ "prototype": "Modello di campo" }, "campCollaboration": { + "fields": { + "abbr": "Abbreviazione", + "color": "Colore", + "inviteEmail": "E-mail", + "role": "Ruolo", + "status": "Stato" + }, "inactive": "non attivo" }, "category": { diff --git a/frontend/src/components/collaborator/CollaboratorCreate.vue b/frontend/src/components/collaborator/CollaboratorCreate.vue index ba303341be..6c56f0faa4 100644 --- a/frontend/src/components/collaborator/CollaboratorCreate.vue +++ b/frontend/src/components/collaborator/CollaboratorCreate.vue @@ -51,7 +51,7 @@ export default { }, data() { return { - entityProperties: ['camp', 'inviteEmail', 'role'], + entityProperties: ['abbr', 'camp', 'color', 'inviteEmail', 'role'], entityUri: '', } }, @@ -59,7 +59,9 @@ export default { showDialog: function (showDialog) { if (showDialog) { this.setEntityData({ + abbr: '', camp: this.camp._meta.self, + color: '', inviteEmail: '', role: DEFAULT_INVITE_ROLE, }) diff --git a/frontend/src/components/collaborator/CollaboratorEdit.vue b/frontend/src/components/collaborator/CollaboratorEdit.vue index 7484fe53d3..621cc7fcb4 100644 --- a/frontend/src/components/collaborator/CollaboratorEdit.vue +++ b/frontend/src/components/collaborator/CollaboratorEdit.vue @@ -152,7 +152,7 @@ export default { return { resendingEmail: false, emailSent: false, - entityProperties: ['camp', 'inviteEmail', 'role', 'status'], + entityProperties: ['abbr', 'camp', 'color', 'inviteEmail', 'role', 'status'], entityUri: '', } }, @@ -187,6 +187,8 @@ export default { this.emailSent = false this.entityUri = this.collaborator._meta.self this.setEntityData({ + abbr: this.collaborator.abbr, + color: this.collaborator.color || '', role: this.collaborator.role, }) } else { diff --git a/frontend/src/components/collaborator/CollaboratorForm.vue b/frontend/src/components/collaborator/CollaboratorForm.vue index f9a277e12e..65348e07f6 100644 --- a/frontend/src/components/collaborator/CollaboratorForm.vue +++ b/frontend/src/components/collaborator/CollaboratorForm.vue @@ -75,6 +75,10 @@ + + + + diff --git a/frontend/src/views/dev/Controls.vue b/frontend/src/views/dev/Controls.vue index 4707811688..a791fae02b 100644 --- a/frontend/src/views/dev/Controls.vue +++ b/frontend/src/views/dev/Controls.vue @@ -137,7 +137,7 @@ export default { textareaValue: 'FFFFFFFFFF', richtextValue: '

FFFFFFFFFF

', checkboxValue: false, - colorValue: '#FFFFFF', + colorValue: null, selectValue: null, dateValue: '2020-01-01', timeValue: '2020-01-01T14:45:00+00:00', @@ -264,7 +264,7 @@ export default { props: { placeholder: this.placeholder, path: 'color', - uri: this.categoryUri, + uri: this.campCollaborationUri, veeRules: 'required', }, }, @@ -291,6 +291,9 @@ export default { scheduleEntryUri() { return '/api/schedule_entries/b6668dffbb2b' // Harry Potter - LA Lagerbau }, + campCollaborationUri() { + return '/camp_collaborations/3229d273decd' // Harry Potter - Snoopy + }, availableLocales() { return VueI18n.availableLocales.map((l) => ({ value: l,