Skip to content

Commit c760d81

Browse files
authored
Merge pull request #4180 from nextcloud/fix/vote-indicator
fix vote indicator and confirmed option
2 parents d34230a + 30910af commit c760d81

13 files changed

Lines changed: 193 additions & 257 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ All notable changes to this project will be documented in this file.
88
## [unreleased]
99
### New
1010
- Add option to delete orphaned votes
11+
- Add vote indicator for locked options
1112

1213
### Changes
1314
- Make vote cell focusable
@@ -16,7 +17,8 @@ All notable changes to this project will be documented in this file.
1617

1718
### Fixes
1819
- Force list view mode initially on mobile viewports
19-
- Fix some viual issues of the vote page
20+
- Fix some visual issues of the vote page
21+
- Bring back indicator for confirmed options after closing the poll
2022

2123
## [8.1.4] - 2025-07-15
2224
### Fixes

src/assets/scss/vars.scss

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,6 @@
1111
--color-polls-background-maybe: rgba(var(--color-warning-rgb), 0.1);
1212
--container-background-light: rgba(var(--color-info-rgb), 0.1);
1313
--cap-width: 49rem;
14+
--shadow-height: 0.7rem;
15+
--shadow-height-inverted: -0.7rem;
1416
}

src/components/Base/modules/StickyDiv.vue

Lines changed: 88 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@ const style = computed(() => {
5050
})
5151
5252
const stickyClass = computed(() => ({
53-
container: true,
5453
'sticky-top': stickyTop,
5554
'sticky-left': stickyLeft,
5655
'sticky-bottom-shadow': activateBottomShadow,
@@ -59,93 +58,114 @@ const stickyClass = computed(() => ({
5958
</script>
6059

6160
<template>
62-
<div :class="stickyClass" :style="style">
63-
<slot name="default">
64-
<div class="inner"></div>
65-
</slot>
61+
<div :class="['sticky-div', stickyClass]" :style="style">
62+
<div class="top-left-corner"></div>
63+
<div class="top"></div>
64+
<div class="top-right-corner"></div>
65+
66+
<div class="right"></div>
67+
68+
<div class="bottom-right-corner"></div>
69+
<div class="bottom"></div>
70+
<div class="bottom-left-corner"></div>
71+
72+
<div class="left"></div>
73+
74+
<div class="stage outer" :style="style">
75+
<slot name="default">
76+
<div class="inner"></div>
77+
</slot>
78+
</div>
6679
</div>
6780
</template>
6881

6982
<style lang="scss" scoped>
70-
.container {
71-
--shadow-height: 10px;
83+
.sticky-div {
84+
display: grid;
85+
grid-template-columns: auto 1fr auto;
86+
grid-template-rows: auto 1fr auto;
87+
grid-template-areas:
88+
'top-left-corner top top-right-corner'
89+
'left center right'
90+
'bottom-left-corner bottom bottom-right-corner';
7291
}
7392
74-
.inner {
93+
.stage {
94+
padding: 0.3rem;
95+
grid-area: center;
7596
width: 100%;
7697
height: 100%;
7798
background-color: var(--color-main-background);
7899
}
79100
101+
.top-left-corner {
102+
grid-area: top-left-corner;
103+
height: 0;
104+
width: 0;
105+
}
106+
107+
.top {
108+
grid-area: top;
109+
height: 0;
110+
}
111+
112+
.top-right-corner {
113+
grid-area: top-right-corner;
114+
height: 0;
115+
width: 0;
116+
}
117+
118+
.right {
119+
grid-area: right;
120+
width: 0;
121+
}
122+
123+
.bottom-right-corner {
124+
grid-area: bottom-right-corner;
125+
height: 0;
126+
width: 0;
127+
}
128+
129+
.bottom {
130+
grid-area: bottom;
131+
height: 0;
132+
}
133+
134+
.bottom-left-corner {
135+
grid-area: bottom-left-corner;
136+
height: 0;
137+
width: 0;
138+
}
139+
140+
.left {
141+
grid-area: left;
142+
width: 0;
143+
}
144+
80145
.sticky-left {
81146
position: sticky;
82147
left: 0;
83148
}
84149
85150
.sticky-top {
86-
--shadow-height: 10px;
87151
position: sticky;
88152
top: 0;
89-
padding-bottom: 0px;
90-
padding-bottom: var(--shadow-height);
91-
92-
&::after {
93-
content: '';
94-
position: absolute;
95-
bottom: 0;
96-
left: -1px;
97-
right: 0;
98-
height: 0;
99-
background: linear-gradient(
100-
to bottom,
101-
rgba(var(--color-box-shadow-rgb), 0.3),
102-
rgba(var(--color-box-shadow-rgb), 0)
103-
);
104-
transition:
105-
all var(--animation-slow) linear,
106-
border 1ms;
107-
}
108-
109-
&.sticky-bottom-shadow {
110-
border-top: 0;
111-
padding-bottom: var(--shadow-height);
112-
margin-bottom: 0;
113-
&::after {
114-
height: var(--shadow-height);
115-
}
116-
}
117153
}
118154
119-
/* TODO: Implement sticky right shadow
120-
An Alternative could be using a grid instead of ::after
121-
to be able to position multiple shadows in all directions */
122-
123-
/*
124-
padding-right: var(--shadow-height);
125-
126-
&::after {
127-
content: '';
128-
position: absolute;
129-
right: 0;
130-
top: -1px;
131-
bottom: 0;
132-
width: 0;
133-
background: linear-gradient(
134-
to right,
135-
rgba(var(--color-box-shadow-rgb), 0.3),
136-
rgba(var(--color-box-shadow-rgb), 0)
137-
);
138-
transition:
139-
all var(--animation-slow) linear,
140-
border 1ms;
155+
.bottom-right-corner,
156+
.bottom,
157+
.bottom-left-corner {
158+
background: linear-gradient(
159+
to bottom,
160+
rgba(var(--color-box-shadow-rgb), 0.3),
161+
rgba(var(--color-box-shadow-rgb), 0)
162+
);
163+
164+
transition:
165+
all var(--animation-slow) linear,
166+
border 1ms;
167+
.sticky-bottom-shadow & {
168+
height: var(--shadow-height);
141169
}
142-
143-
&.sticky-right-shadow {
144-
border-right: 0;
145-
padding-right: var(--shadow-height);
146-
margin-right: 0;
147-
&::after {
148-
width: var(--shadow-height);
149-
}
150-
} */
170+
}
151171
</style>

src/components/Options/Counter.vue

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,11 @@
66
<script setup lang="ts">
77
import YesCounterIcon from 'vue-material-design-icons/AccountCheck.vue'
88
import MaybeCounterIcon from 'vue-material-design-icons/AccountCheckOutline.vue'
9+
import CheckboxMarkedOutlinedIcon from 'vue-material-design-icons/CheckboxMarkedOutline.vue'
910
import { Option } from '../../Types/index.ts'
11+
import { usePollStore } from '../../stores/poll.ts'
1012
13+
const pollStore = usePollStore()
1114
interface Props {
1215
option: Option
1316
showMaybe?: boolean
@@ -17,7 +20,10 @@ const { option, showMaybe = false } = defineProps<Props>()
1720
</script>
1821

1922
<template>
20-
<div class="counter">
23+
<div v-if="option.confirmed && pollStore.status.isExpired" class="counter">
24+
<CheckboxMarkedOutlinedIcon :size="20" />
25+
</div>
26+
<div v-else class="counter">
2127
<div class="yes">
2228
<YesCounterIcon
2329
fill-color="var(--color-polls-foreground-yes)"

src/components/Options/OptionItem.vue

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,17 @@ const pollStore = usePollStore()
6464
grid-template-areas: 'drag option owner actions';
6565
position: relative;
6666
padding: 8px 0;
67+
background-color: var(--color-main-background);
68+
.confirmed & {
69+
background-color: var(--color-polls-background-yes);
70+
border-radius: var(--border-radius-container);
71+
border: 2px solid var(--color-success-text);
72+
}
73+
.list-view .confirmed & {
74+
padding-left: 0.5rem;
75+
left: -0.5rem;
76+
padding-right: 0.5rem;
77+
}
6778
}
6879
6980
.grid-area-drag-icon {

src/components/VoteTable/VoteButton.vue

Lines changed: 30 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -5,64 +5,62 @@
55

66
<script setup lang="ts">
77
import { computed } from 'vue'
8+
import { AxiosError } from '@nextcloud/axios'
9+
10+
import { t } from '@nextcloud/l10n'
811
import { showSuccess, showError } from '@nextcloud/dialogs'
912
13+
import VoteIndicator from './VoteIndicator.vue'
1014
import { usePollStore } from '../../stores/poll.ts'
11-
import { useVotesStore } from '../../stores/votes.ts'
15+
import { Answer, useVotesStore } from '../../stores/votes.ts'
1216
import { Option, User } from '../../Types/index.ts'
1317
14-
import { t } from '@nextcloud/l10n'
15-
import VoteIndicator from './VoteIndicator.vue'
16-
import { AxiosError } from '@nextcloud/axios'
17-
1818
interface Props {
1919
option: Option
2020
user: User
2121
}
2222
23+
export type richAnswer = {
24+
name: Answer
25+
translated: string
26+
}
27+
28+
const richAnswers: { [key in Answer]: richAnswer } = {
29+
yes: { name: 'yes', translated: t('polls', 'Yes') },
30+
maybe: { name: 'maybe', translated: t('polls', 'Maybe') },
31+
no: { name: 'no', translated: t('polls', 'No') },
32+
'': { name: '', translated: t('polls', 'No answer') },
33+
}
34+
2335
const { option, user } = defineProps<Props>()
2436
2537
const pollStore = usePollStore()
2638
const votesStore = useVotesStore()
2739
28-
const thisVote = computed(() =>
40+
const vote = computed(() =>
2941
votesStore.getVote({
3042
option,
3143
user,
3244
}),
3345
)
3446
35-
const iconAnswer = computed(() => {
36-
if (['no', ''].includes(thisVote.value.answer)) {
37-
return pollStore.isClosed && option.confirmed ? 'no' : ''
47+
const nextAnswer = computed<richAnswer>(() => {
48+
if (['no', ''].includes(vote.value.answer)) {
49+
return richAnswers.yes
3850
}
39-
return thisVote.value.answer
40-
})
4151
42-
const nextAnswer = computed(() => {
43-
if (pollStore.answerSequence.indexOf(thisVote.value.answer) < 0) {
44-
return pollStore.answerSequence[1]
52+
if (vote.value.answer === 'yes' && pollStore.configuration.allowMaybe) {
53+
return richAnswers.maybe
4554
}
46-
return pollStore.answerSequence[
47-
(pollStore.answerSequence.indexOf(thisVote.value.answer) + 1)
48-
% pollStore.answerSequence.length
49-
]
50-
})
51-
const nextAnswerTranslated = computed(() => {
52-
if (nextAnswer.value === 'yes') {
53-
return t('polls', 'Yes')
54-
}
55-
if (nextAnswer.value === 'maybe') {
56-
return t('polls', 'Maybe')
57-
}
58-
return t('polls', 'No')
55+
56+
return pollStore.configuration.useNo ? richAnswers.no : richAnswers['']
5957
})
6058
6159
async function setVote() {
6260
try {
6361
await votesStore.set({
6462
option,
65-
setTo: nextAnswer.value,
63+
setTo: nextAnswer.value.name,
6664
})
6765
showSuccess(t('polls', 'Vote saved'), { timeout: 2000 })
6866
} catch (error) {
@@ -78,15 +76,15 @@ async function setVote() {
7876
<template>
7977
<button
8078
class="vote-button active"
81-
:class="[thisVote.answer]"
79+
:class="[vote.answer]"
8280
:aria-label="
83-
t('polls', 'Vote {nextAnswer} for {option}', {
81+
t('polls', 'Click to vote with {nextAnswer} for option {option}', {
8482
option: option.text,
85-
nextAnswer: nextAnswerTranslated,
83+
nextAnswer: nextAnswer.translated,
8684
})
8785
"
8886
@click="setVote()">
89-
<VoteIndicator :answer="iconAnswer" />
87+
<VoteIndicator :answer="vote.answer" />
9088
</button>
9189
</template>
9290

0 commit comments

Comments
 (0)