Merge pull request #5045 from nextcloud/enh/dark-mode

This commit is contained in:
Julius Härtl
2023-09-04 10:06:20 +02:00
committed by GitHub
5 changed files with 75 additions and 101 deletions

11
package-lock.json generated
View File

@@ -23,6 +23,7 @@
"@nextcloud/router": "^2.1.2", "@nextcloud/router": "^2.1.2",
"@nextcloud/vue": "^7.12.4", "@nextcloud/vue": "^7.12.4",
"blueimp-md5": "^2.19.0", "blueimp-md5": "^2.19.0",
"chroma-js": "^2.4.2",
"dompurify": "^3.0.5", "dompurify": "^3.0.5",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"markdown-it": "^13.0.1", "markdown-it": "^13.0.1",
@@ -6713,6 +6714,11 @@
"fsevents": "~2.3.2" "fsevents": "~2.3.2"
} }
}, },
"node_modules/chroma-js": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chroma-js/-/chroma-js-2.4.2.tgz",
"integrity": "sha512-U9eDw6+wt7V8z5NncY2jJfZa+hUH8XEj8FQHgFJTrUFnJfXYf4Ml4adI2vXZOjqRDpFWtYVWypDfZwnJ+HIR4A=="
},
"node_modules/chrome-trace-event": { "node_modules/chrome-trace-event": {
"version": "1.0.3", "version": "1.0.3",
"license": "MIT", "license": "MIT",
@@ -24833,6 +24839,11 @@
"readdirp": "~3.6.0" "readdirp": "~3.6.0"
} }
}, },
"chroma-js": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chroma-js/-/chroma-js-2.4.2.tgz",
"integrity": "sha512-U9eDw6+wt7V8z5NncY2jJfZa+hUH8XEj8FQHgFJTrUFnJfXYf4Ml4adI2vXZOjqRDpFWtYVWypDfZwnJ+HIR4A=="
},
"chrome-trace-event": { "chrome-trace-event": {
"version": "1.0.3", "version": "1.0.3",
"peer": true "peer": true

View File

@@ -44,6 +44,7 @@
"@nextcloud/router": "^2.1.2", "@nextcloud/router": "^2.1.2",
"@nextcloud/vue": "^7.12.4", "@nextcloud/vue": "^7.12.4",
"blueimp-md5": "^2.19.0", "blueimp-md5": "^2.19.0",
"chroma-js": "^2.4.2",
"dompurify": "^3.0.5", "dompurify": "^3.0.5",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"markdown-it": "^13.0.1", "markdown-it": "^13.0.1",

View File

@@ -65,6 +65,7 @@
<CardMenu v-if="!editing && compactMode" :card="card" class="right" /> <CardMenu v-if="!editing && compactMode" :card="card" class="right" />
</div> </div>
<transition-group v-if="card.labels && card.labels.length" <transition-group v-if="card.labels && card.labels.length"
name="zoom" name="zoom"
tag="ul" tag="ul"
@@ -200,27 +201,28 @@ export default {
@import './../../css/animations'; @import './../../css/animations';
@import './../../css/variables'; @import './../../css/variables';
@mixin dark-card {
border: 2px solid var(--color-border-dark);
box-shadow: none;
}
.card { .card {
transition: box-shadow 0.1s ease-in-out; transition: border 0.1s ease-in-out;
box-shadow: 0 0 2px 0 var(--color-box-shadow);
border-radius: var(--border-radius-large); border-radius: var(--border-radius-large);
font-size: 100%; font-size: 100%;
background-color: var(--color-main-background); background-color: var(--color-main-background);
margin-bottom: $card-spacing; margin-bottom: $card-spacing;
border: 2px solid var(--color-border);
&:deep(*) { &:deep(*) {
cursor: pointer; cursor: pointer;
} }
body.dark &, body.theme--dark & {
border: 2px solid var(--color-border);
}
&:hover { &:hover {
box-shadow: 0 0 5px 0 var(--color-box-shadow); border: 2px solid var(--color-border-dark);
} }
&.current-card { &.current-card {
box-shadow: 0 0 5px 1px var(--color-box-shadow); border: 2px solid var(--color-primary-element);
} }
.card-upper { .card-upper {
@@ -326,4 +328,11 @@ export default {
color: transparent; color: transparent;
} }
} }
@media (prefers-color-scheme: dark) {
.card {
@include dark-card;
}
}
</style> </style>

View File

@@ -21,20 +21,35 @@
--> -->
<template> <template>
<div v-if="card" class="duedate"> <div v-if="card" class="duedate" :data-due-state="dueState">
<transition name="zoom"> <transition name="zoom">
<div v-if="card.duedate" :class="dueIcon" :title="absoluteDate"> <div v-if="card.duedate" class="due" :title="absoluteDate">
<span>{{ relativeDate }}</span> <Clock v-if="overdue" :size="16" />
<ClockOutline v-else :size="16" />
<span v-if="!compactMode" class="due--label">{{ relativeDate }}</span>
</div> </div>
</transition> </transition>
</div> </div>
</template> </template>
<script> <script>
import { mapState } from 'vuex'
import moment from '@nextcloud/moment' import moment from '@nextcloud/moment'
import Clock from 'vue-material-design-icons/Clock.vue'
import ClockOutline from 'vue-material-design-icons/ClockOutline.vue'
const DueState = {
Future: 'Future',
Next: 'Next',
Now: 'Now',
Overdue: 'Overdue',
}
export default { export default {
name: 'DueDate', name: 'DueDate',
components: {
Clock,
ClockOutline,
},
props: { props: {
card: { card: {
type: Object, type: Object,
@@ -42,18 +57,25 @@ export default {
}, },
}, },
computed: { computed: {
dueIcon() { ...mapState({
compactMode: state => state.compactMode,
}),
dueState() {
const days = Math.floor(moment(this.card.duedate).diff(this.$root.time, 'seconds') / 60 / 60 / 24) const days = Math.floor(moment(this.card.duedate).diff(this.$root.time, 'seconds') / 60 / 60 / 24)
if (days < 0) { if (days < 0) {
return 'icon-calendar due icon overdue' return DueState.Overdue
} }
if (days === 0) { if (days === 0) {
return 'icon-calendar-dark due icon now' return DueState.Now
} }
if (days === 1) { if (days === 1) {
return 'icon-calendar-dark due icon next' return DueState.Next
} }
return 'icon-calendar-dark due icon'
return DueState.Future
},
overdue() {
return this.dueState === DueState.Overdue
}, },
relativeDate() { relativeDate() {
const diff = moment(this.$root.time).diff(this.card.duedate, 'seconds') const diff = moment(this.$root.time).diff(this.card.duedate, 'seconds')
@@ -63,55 +85,43 @@ export default {
return moment(this.card.duedate).fromNow() return moment(this.card.duedate).fromNow()
}, },
absoluteDate() { absoluteDate() {
return moment(this.card.duedate).format('L') return moment(this.card.duedate).format('LLLL')
}, },
}, },
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.icon.due { .due {
background-position: 4px center; background-position: 4px center;
border-radius: 3px; border-radius: var(--border-radius-large);
margin-top: 9px; margin-top: 9px;
margin-bottom: 9px; margin-bottom: 9px;
padding: 3px 4px; padding: 2px 8px;
padding-right: 0;
font-size: 90%; font-size: 90%;
display: flex; display: flex;
align-items: center; align-items: center;
opacity: .5;
flex-shrink: 1; flex-shrink: 1;
z-index: 2; z-index: 2;
.icon { [data-due-state='Overdue'] & {
background-size: contain; color: var(--color-main-background);
background-color: var(--color-error-text);
} }
[data-due-state='Now'] & {
&.overdue { color: var(--color-main-background);
background-color: var(--color-error);
color: var(--color-primary-element-text);
opacity: .7;
padding: 3px 4px;
}
&.now {
background-color: var(--color-warning); background-color: var(--color-warning);
opacity: .7;
padding: 3px 4px;
}
&.next {
background-color: var(--color-background-dark);
opacity: .7;
padding: 3px 4px;
} }
&::before,
span { span {
margin-left: 20px;
white-space: nowrap; white-space: nowrap;
text-overflow: ellipsis; text-overflow: ellipsis;
overflow: hidden; overflow: hidden;
} }
.due--label {
margin-left: 4px;
}
} }
@media print { @media print {

View File

@@ -20,76 +20,19 @@
* *
*/ */
import chroma from 'chroma-js'
export default { export default {
methods: { methods: {
hexToRgb(hex) {
const result = /^#?([A-Fa-f\d]{2})([A-Fa-f\d]{2})([A-Fa-f\d]{2})$/i.exec(hex)
if (result) {
return {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16),
}
}
return null
},
rgb2hls(rgb) {
// RGB2HLS by Garry Tan
// http://axonflux.com/handy-rgb-to-hsl-and-rgb-to-hsv-color-model-c
const r = rgb.r / 255
const g = rgb.g / 255
const b = rgb.b / 255
const max = Math.max(r, g, b)
const min = Math.min(r, g, b)
let h
let s
const l = (max + min) / 2
if (max === min) {
h = s = 0 // achromatic
} else {
const d = max - min
s = l > 0.5 ? d / (2 - max - min) : d / (max + min)
switch (max) {
case r:
h = (g - b) / d + (g < b ? 6 : 0)
break
case g:
h = (b - r) / d + 2
break
case b:
h = (r - g) / d + 4
break
}
h /= 6
}
return {
h, l, s,
}
},
textColor(hex) { textColor(hex) {
return chroma(hex).get('lab.l') > 50 ? '#000000' : '#ffffff'
const rgb = this.hexToRgb(hex)
if (rgb === null) {
return '#000000'
}
const { l } = this.rgb2hls(rgb)
if (l < 0.5) {
return '#ffffff'
} else {
return '#000000'
}
}, },
colorIsValid(hex) { colorIsValid(hex) {
const re = /[A-Fa-f0-9]{6}/ const re = /[A-Fa-f0-9]{6}/
if (re.test(hex)) { if (re.test(hex)) {
return true return true
} }
return false return false
}, },
randomColor() { randomColor() {
return Math.floor(Math.random() * (0xffffff + 1)).toString(16).padStart(6, '0') return Math.floor(Math.random() * (0xffffff + 1)).toString(16).padStart(6, '0')