Skip to content

Commit 46c691b

Browse files
committed
Feat: Highlight cards with important labels
Signed-off-by: Kostiantyn Miakshyn <molodchick@gmail.com>
1 parent 0a14a0d commit 46c691b

12 files changed

Lines changed: 149 additions & 22 deletions

File tree

lib/Controller/LabelApiController.php

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,12 @@ public function get() {
5050
*
5151
* @params $title
5252
* @params $color
53+
* @param array<string, scalar> $customSettings
54+
*
5355
* Create a new label
5456
*/
55-
public function create($title, $color) {
56-
$label = $this->labelService->create($title, $color, $this->request->getParam('boardId'));
57+
public function create($title, $color, array $customSettings = []) {
58+
$label = $this->labelService->create($title, $color, $this->request->getParam('boardId'), $customSettings);
5759
return new DataResponse($label, HTTP::STATUS_OK);
5860
}
5961

@@ -64,10 +66,12 @@ public function create($title, $color) {
6466
*
6567
* @params $title
6668
* @params $color
69+
* @param array<string, scalar> $customSettings
70+
*
6771
* Update a specific label
6872
*/
69-
public function update($title, $color) {
70-
$label = $this->labelService->update($this->request->getParam('labelId'), $title, $color);
73+
public function update($title, $color, array $customSettings = []) {
74+
$label = $this->labelService->update($this->request->getParam('labelId'), $title, $color, $customSettings);
7175
return new DataResponse($label, HTTP::STATUS_OK);
7276
}
7377

lib/Controller/LabelController.php

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,21 +25,23 @@ public function __construct(
2525
* @param $title
2626
* @param $color
2727
* @param $boardId
28+
* @param array<string, scalar> $customSettings
2829
* @return \OCP\AppFramework\Db\Entity
2930
*/
30-
public function create($title, $color, $boardId) {
31-
return $this->labelService->create($title, $color, $boardId);
31+
public function create($title, $color, $boardId, array $customSettings = []) {
32+
return $this->labelService->create($title, $color, $boardId, $customSettings);
3233
}
3334

3435
/**
3536
* @NoAdminRequired
3637
* @param $id
3738
* @param $title
3839
* @param $color
40+
* @param array<string, scalar> $customSettings
3941
* @return \OCP\AppFramework\Db\Entity
4042
*/
41-
public function update($id, $title, $color) {
42-
return $this->labelService->update($id, $title, $color);
43+
public function update($id, $title, $color, array $customSettings = []) {
44+
return $this->labelService->update($id, $title, $color, $customSettings);
4345
}
4446

4547
/**

lib/Db/Label.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,41 @@
99

1010
/**
1111
* @method getTitle(): string
12+
* @method getCustomSettings(): string
13+
* @method setCustomSettings(string $customSettings)
1214
*/
1315
class Label extends RelationalEntity {
1416
protected $title;
1517
protected $color;
1618
protected $boardId;
1719
protected $cardId;
1820
protected $lastModified;
21+
protected $customSettings;
1922

2023
public function __construct() {
2124
$this->addType('id', 'integer');
2225
$this->addType('boardId', 'integer');
2326
$this->addType('cardId', 'integer');
2427
$this->addType('lastModified', 'integer');
28+
$this->addType('customSettings', 'string');
2529
}
2630

2731
public function getETag() {
2832
return md5((string)$this->getLastModified());
2933
}
34+
35+
public function getCustomSettingsArray(): array {
36+
return $this->customSettings ? json_decode($this->customSettings, true) : [];
37+
}
38+
39+
public function setCustomSettingsArray(array $customSettings): void {
40+
$this->setCustomSettings(json_encode($customSettings ?: new \stdClass()));
41+
}
42+
43+
public function jsonSerialize(): array {
44+
$data = parent::jsonSerialize();
45+
$data['customSettings'] = $this->getCustomSettingsArray() ?: new \stdClass();
46+
47+
return $data;
48+
}
3049
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
/**
4+
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors
5+
* SPDX-License-Identifier: AGPL-3.0-or-later
6+
*/
7+
8+
declare(strict_types=1);
9+
10+
namespace OCA\Deck\Migration;
11+
12+
use Closure;
13+
use OCP\DB\ISchemaWrapper;
14+
use OCP\DB\Types;
15+
use OCP\Migration\IOutput;
16+
use OCP\Migration\SimpleMigrationStep;
17+
18+
class Version20000Date20250907000000 extends SimpleMigrationStep {
19+
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options): ?ISchemaWrapper {
20+
/** @var ISchemaWrapper $schema */
21+
$schema = $schemaClosure();
22+
23+
$table = $schema->getTable('deck_labels');
24+
if (!$table->hasColumn('custom_settings')) {
25+
$table->addColumn('custom_settings', Types::JSON, [
26+
'notnull' => false,
27+
]);
28+
}
29+
30+
return $schema;
31+
}
32+
}

lib/Service/CardService.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -340,7 +340,7 @@ public function update($id, $title, $stackId, $type, $owner, $description = '',
340340
// clone labels that are assigned to card but don't exist in new board
341341
if (empty($filteredLabels)) {
342342
if ($this->permissionService->getPermissions($boardId)[Acl::PERMISSION_MANAGE] === true) {
343-
$newLabel = $this->labelService->create($label->getTitle(), $label->getColor(), $board->getId());
343+
$newLabel = $this->labelService->create($label->getTitle(), $label->getColor(), $board->getId(), $label->getCustomSettingsArray());
344344
$boardLabels[] = $label;
345345
$this->assignLabel($card->getId(), $newLabel->getId());
346346
}

lib/Service/LabelService.php

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,14 +62,15 @@ public function find($labelId) {
6262
* @param $title
6363
* @param $color
6464
* @param $boardId
65+
* @param array<string, scalar> $customSettings
6566
* @return \OCP\AppFramework\Db\Entity
6667
* @throws StatusException
6768
* @throws \OCA\Deck\NoPermissionException
6869
* @throws \OCP\AppFramework\Db\DoesNotExistException
6970
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
7071
* @throws BadRequestException
7172
*/
72-
public function create($title, $color, $boardId) {
73+
public function create($title, $color, $boardId, array $customSettings = []) {
7374
$this->labelServiceValidator->check(compact('title', 'color', 'boardId'));
7475

7576
$this->permissionService->checkPermission(null, $boardId, Acl::PERMISSION_MANAGE);
@@ -89,6 +90,7 @@ public function create($title, $color, $boardId) {
8990
$label->setTitle($title);
9091
$label->setColor($color);
9192
$label->setBoardId($boardId);
93+
$label->setCustomSettingsArray($customSettings);
9294
$this->changeHelper->boardChanged($boardId);
9395
return $this->labelMapper->insert($label);
9496
}
@@ -130,14 +132,15 @@ public function delete($id) {
130132
* @param $id
131133
* @param $title
132134
* @param $color
135+
* @param array<string, scalar> $customSettings
133136
* @return \OCP\AppFramework\Db\Entity
134137
* @throws StatusException
135138
* @throws \OCA\Deck\NoPermissionException
136139
* @throws \OCP\AppFramework\Db\DoesNotExistException
137140
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
138141
* @throws BadRequestException
139142
*/
140-
public function update($id, $title, $color) {
143+
public function update($id, $title, $color, array $customSettings = []) {
141144
$this->labelServiceValidator->check(compact('title', 'color', 'id'));
142145

143146
$this->permissionService->checkPermission($this->labelMapper, $id, Acl::PERMISSION_MANAGE);
@@ -161,6 +164,7 @@ public function update($id, $title, $color) {
161164

162165
$label->setTitle($title);
163166
$label->setColor($color);
167+
$label->setCustomSettingsArray($customSettings);
164168
$this->changeHelper->boardChanged($label->getBoardId());
165169
return $this->labelMapper->update($label);
166170
}

src/components/board/Board.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,7 @@ export default {
314314
flex-direction: column;
315315
// Margin left instead of padidng to avoid jumps on dropping a card
316316
margin-left: $stack-spacing;
317+
padding-left: 5px;
317318
padding-right: $stack-spacing;
318319
overflow-x: hidden;
319320
overflow-y: auto;

src/components/board/BoardSidebar.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
<NcAppSidebar v-if="board != null"
88
:actions="[]"
99
:name="board.title"
10+
style="width: 400px"
1011
@close="closeSidebar">
1112
<NcAppSidebarTab id="sharing"
1213
:order="0"

src/components/board/TagsTabSidebar.vue

Lines changed: 49 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,11 @@
1515
@input="updateColor">
1616
<div :style="{ backgroundColor: '#' + editingLabel.color }" class="color0 icon-colorpicker" />
1717
</NcColorPicker>
18-
<input v-model="editingLabel.title" type="text">
18+
<NcCheckboxRadioSwitch v-model="editingLabelIsImportant"
19+
type="switch">
20+
{{ t('deck', 'Important') }}
21+
</NcCheckboxRadioSwitch>
22+
<input v-model="editingLabel.title" type="text" style="margin-right: 20px;">
1923
<input :disabled="!editLabelObjValidated"
2024
type="submit"
2125
value=""
@@ -34,10 +38,18 @@
3438
</template>
3539
<template v-else>
3640
<div v-if="canManage && !isArchived" class="label-title" @click="clickEdit(label)">
37-
<span :style="{ backgroundColor: `#${label.color}`, color: textColor(label.color) }">{{ label.title }}</span>
41+
<span :style="{
42+
backgroundColor: `#${label.color}`,
43+
color: textColor(label.color),
44+
fontWeight: label.customSettings.isImportant ? 'bold' : 'normal'
45+
}">{{ label.title }}</span>
3846
</div>
3947
<div v-else class="label-title">
40-
<span :style="{ backgroundColor: `#${label.color}`, color: textColor(label.color) }">{{ label.title }}</span>
48+
<span :style="{
49+
backgroundColor: `#${label.color}`,
50+
color: textColor(label.color),
51+
fontWeight: label.customSettings.isImportant ? 'bold' : 'normal'
52+
}">{{ label.title }}</span>
4153
</div>
4254

4355
<NcActions v-if="canManage && !isArchived">
@@ -62,7 +74,11 @@
6274
@input="updateColor">
6375
<div :style="{ backgroundColor: '#' + addLabelObj.color }" class="color0 icon-colorpicker" />
6476
</NcColorPicker>
65-
<input v-model="addLabelObj.title" type="text">
77+
<NcCheckboxRadioSwitch v-model="addLabelIsImportant"
78+
type="switch">
79+
{{ t('deck', 'Important') }}
80+
</NcCheckboxRadioSwitch>
81+
<input v-model="addLabelObj.title" type="text" style="margin-right: 20px;">
6682
<input :disabled="!addLabelObjValidated"
6783
type="submit"
6884
value=""
@@ -88,14 +104,15 @@
88104
89105
import { mapGetters } from 'vuex'
90106
import Color from '../../mixins/color.js'
91-
import { NcColorPicker, NcActions, NcActionButton } from '@nextcloud/vue'
107+
import { NcColorPicker, NcActions, NcActionButton, NcCheckboxRadioSwitch } from '@nextcloud/vue'
92108
93109
export default {
94110
name: 'TagsTabSidebar',
95111
components: {
96112
NcColorPicker,
97113
NcActions,
98114
NcActionButton,
115+
NcCheckboxRadioSwitch,
99116
},
100117
mixins: [Color],
101118
data() {
@@ -139,7 +156,22 @@ export default {
139156
labelsSorted() {
140157
return [...this.labels].sort((a, b) => a.title.localeCompare(b.title))
141158
},
142-
159+
addLabelIsImportant: {
160+
get() {
161+
return this.addLabelObj?.customSettings?.isImportant || false
162+
},
163+
set(isImportant) {
164+
this.addLabelObj.customSettings = { ...this.addLabelObj.customSettings, isImportant }
165+
},
166+
},
167+
editingLabelIsImportant: {
168+
get() {
169+
return this.editingLabel?.customSettings?.isImportant
170+
},
171+
set(isImportant) {
172+
this.editingLabel.customSettings = { ...this.editingLabel.customSettings, isImportant }
173+
},
174+
},
143175
},
144176
methods: {
145177
updateColor(c) {
@@ -157,15 +189,23 @@ export default {
157189
this.$store.dispatch('removeLabelFromCurrentBoard', id)
158190
},
159191
updateLabel(label) {
160-
this.$store.dispatch('updateLabelFromCurrentBoard', this.editingLabel)
192+
const payload = {
193+
...this.editingLabel,
194+
customSettings: { ...this.editingLabel.customSettings },
195+
}
196+
this.$store.dispatch('updateLabelFromCurrentBoard', payload)
161197
this.editingLabelId = null
162198
},
163199
clickShowAddLabel() {
164-
this.addLabelObj = { cardId: null, color: this.defaultColors[Math.floor(Math.random() * this.defaultColors.length)], title: '' }
200+
this.addLabelObj = { cardId: null, color: this.defaultColors[Math.floor(Math.random() * this.defaultColors.length)], title: '', customSettings: {} }
165201
this.addLabel = true
166202
},
167203
clickAddLabel() {
168-
this.$store.dispatch('addLabelToCurrentBoard', this.addLabelObj)
204+
const payload = {
205+
...this.addLabelObj,
206+
customSettings: { ...this.addLabelObj.customSettings },
207+
}
208+
this.$store.dispatch('addLabelToCurrentBoard', payload)
169209
this.addLabel = false
170210
this.addLabelObj = null
171211
},

src/components/cards/CardItem.vue

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,19 @@
66
<template>
77
<AttachmentDragAndDrop v-if="card" :card-id="card.id" class="drop-upload--card">
88
<div :ref="`card${card.id}`"
9-
:class="{'compact': compactMode, 'current-card': currentCard, 'has-labels': card.labels && card.labels.length > 0, 'card__editable': canEdit, 'card__archived': card.archived, 'card__highlight': highlight}"
9+
:class="{
10+
'compact': compactMode,
11+
'current-card': currentCard,
12+
'has-labels': card.labels && card.labels.length > 0,
13+
'card__editable': canEdit,
14+
'card__archived': card.archived,
15+
'card__highlight': highlight,
16+
'is-important': !!importantColor,
17+
}"
1018
tag="div"
1119
:tabindex="0"
1220
class="card"
21+
:style="{'box-shadow': importantColor ? `-5px 0px 0px 0px ${importantColor}` : null}"
1322
@click="openCard"
1423
@keyup.self="handleCardKeyboardShortcut"
1524
@mouseenter="focus(card.id)">
@@ -150,6 +159,15 @@ export default {
150159
const board = this.$store.getters.boards.find((item) => item.id === this.card.boardId)
151160
return board ? !board.archived && board.permissions.PERMISSION_EDIT : false
152161
},
162+
importantColor() {
163+
for (const label of this.card.labels) {
164+
if (label.customSettings.isImportant) {
165+
return '#' + label.color
166+
}
167+
}
168+
169+
return null
170+
},
153171
card() {
154172
return this.item ? this.item : this.$store.getters.cardById(this.id)
155173
},
@@ -338,6 +356,10 @@ export default {
338356
display: flex;
339357
flex-direction: column;
340358
359+
&:not(.is-important) {
360+
box-shadow: -5px 0px 0px 0px var(--color-main-background);
361+
}
362+
341363
&:deep(*) {
342364
cursor: pointer;
343365
}

0 commit comments

Comments
 (0)