Merge pull request #5045 from nextcloud/enh/dark-mode
This commit is contained in:
11
package-lock.json
generated
11
package-lock.json
generated
@@ -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
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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')
|
||||||
|
|||||||
Reference in New Issue
Block a user