Merge branch 'main' into main
continuous-integration/drone/pr Build is passing Details

This commit is contained in:
cernst 2023-03-29 21:56:45 +00:00
commit 85f91c784f
21 changed files with 3041 additions and 3218 deletions

14
.npmrc
View File

@ -1,2 +1,14 @@
fetch-timeout=100000
# pnpm settings
# The following settings prepare for the new default value of pnpm 8
# they can be removed directly after having moved to pnpm 8
auto-install-peers=true
fetch-timeout=100000
dedupe-peer-dependents=true
resolve-peers-from-workspace-root=true
save-workspace-protocol=rolling
resolution-mode=lowest-direct
publishConfig.linkDirectory=true
# remove some time after having moved to pnpm 8
use-lockfile-v6=true

View File

@ -120,7 +120,7 @@ describe('Namepaces', () => {
.should('not.contain', 'Archived')
// Show archived
cy.get('[data-cy="show-archived-check"] label.check span')
cy.get('[data-cy="show-archived-check"] .fancycheckbox__content')
.should('be.visible')
.click()
cy.get('[data-cy="show-archived-check"] input')
@ -129,7 +129,7 @@ describe('Namepaces', () => {
.should('contain', 'Archived')
// Don't show archived
cy.get('[data-cy="show-archived-check"] label.check span')
cy.get('[data-cy="show-archived-check"] .fancycheckbox__content')
.should('be.visible')
.click()
cy.get('[data-cy="show-archived-check"] input')

View File

@ -22,10 +22,10 @@ describe('Project View Table', () => {
cy.get('.project-table .filter-container .items .button')
.contains('Columns')
.click()
cy.get('.project-table .filter-container .card.columns-filter .card-content .fancycheckbox .check')
cy.get('.project-table .filter-container .card.columns-filter .card-content .fancycheckbox')
.contains('Priority')
.click()
cy.get('.project-table .filter-container .card.columns-filter .card-content .fancycheckbox .check')
cy.get('.project-table .filter-container .card.columns-filter .card-content .fancycheckbox')
.contains('Done')
.click()

View File

@ -96,7 +96,7 @@ describe('Task', () => {
TaskFactory.create(1)
cy.visit('/projects/1/list')
cy.get('.tasks .task .fancycheckbox label.check')
cy.get('.tasks .task .fancycheckbox')
.first()
.click()
cy.get('.global-notification')

10
env.d.ts vendored
View File

@ -3,6 +3,16 @@
/// <reference types="cypress" />
/// <reference types="@histoire/plugin-vue/components" />
declare module 'postcss-focus-within/browser' {
import focusWithinInit from 'postcss-focus-within/browser'
export default focusWithinInit
}
declare module 'css-has-pseudo/browser' {
import cssHasPseudo from 'css-has-pseudo/browser'
export default cssHasPseudo
}
interface ImportMetaEnv {
readonly VITE_IS_ONLINE: boolean
}

View File

@ -2,11 +2,11 @@
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1664753041,
"narHash": "sha256-0ogaD8PaGHluARFeupofvk1Nq9gpVeZdlFM0Kcwguys=",
"lastModified": 1680030621,
"narHash": "sha256-qQa1NeS5Rvk2lgK5lSk986PC6I72yIHejzM8PFu+dHs=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "a62844b302507c7531ad68a86cb7aa54704c9cb4",
"rev": "402cc3633cc60dfc50378197305c984518b30773",
"type": "github"
},
"original": {

View File

@ -55,9 +55,6 @@
"@kyvg/vue3-notification": "2.9.0",
"@sentry/tracing": "7.45.0",
"@sentry/vue": "7.45.0",
"@types/is-touch-device": "1.0.0",
"@types/lodash.clonedeep": "4.5.7",
"@types/sortablejs": "1.15.1",
"@vueuse/core": "9.13.0",
"axios": "1.3.4",
"blurhash": "2.0.5",
@ -72,7 +69,6 @@
"flatpickr": "4.6.13",
"flexsearch": "0.7.31",
"floating-vue": "2.0.0-beta.20",
"focus-within": "3.0.2",
"highlight.js": "11.7.0",
"is-touch-device": "1.0.1",
"klona": "2.0.6",
@ -102,11 +98,12 @@
"@types/codemirror": "5.60.7",
"@types/dompurify": "3.0.0",
"@types/flexsearch": "0.7.3",
"@types/focus-within": "1.0.1",
"@types/is-touch-device": "1.0.0",
"@types/lodash.debounce": "4.0.7",
"@types/marked": "4.0.8",
"@types/node": "18.15.10",
"@types/node": "18.15.11",
"@types/postcss-preset-env": "7.7.0",
"@types/sortablejs": "1.15.1",
"@typescript-eslint/eslint-plugin": "5.57.0",
"@typescript-eslint/parser": "5.57.0",
"@vitejs/plugin-legacy": "4.0.2",
@ -117,10 +114,11 @@
"autoprefixer": "10.4.14",
"browserslist": "4.21.5",
"caniuse-lite": "1.0.30001470",
"css-has-pseudo": "5.0.2",
"csstype": "3.1.1",
"cypress": "12.8.1",
"cypress": "12.9.0",
"esbuild": "0.17.14",
"eslint": "8.36.0",
"eslint": "8.37.0",
"eslint-plugin-vue": "9.10.0",
"happy-dom": "8.9.0",
"histoire": "0.15.9",
@ -128,7 +126,8 @@
"postcss": "8.4.21",
"postcss-easing-gradients": "3.0.1",
"postcss-easings": "3.0.1",
"postcss-preset-env": "8.1.0",
"postcss-focus-within": "7.0.2",
"postcss-preset-env": "8.2.0",
"rollup": "3.20.2",
"rollup-plugin-visualizer": "5.9.0",
"sass": "1.60.0",
@ -136,9 +135,9 @@
"typescript": "5.0.2",
"vite": "4.2.1",
"vite-plugin-inject-preload": "1.3.1",
"vite-plugin-pwa": "0.14.6",
"vite-plugin-pwa": "0.14.7",
"vite-svg-loader": "4.0.0",
"vitest": "0.29.7",
"vitest": "0.29.8",
"vue-tsc": "1.2.0",
"wait-on": "7.0.1",
"workbox-cli": "6.5.4"

File diff suppressed because it is too large Load Diff

4
src/assets/checkbox.svg Normal file
View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="18px" height="18px" viewBox="0 0 18 18" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5">
<path d="M1,9 L1,3.5 C1,2 2,1 3.5,1 L14.5,1 C16,1 17,2 17,3.5 L17,14.5 C17,16 16,17 14.5,17 L3.5,17 C2,17 1,16 1,14.5 L1,9 Z" stroke-dasharray="60"></path>
<polyline points="1 9 7 14 15 4" stroke-dasharray="22" stroke-dashoffset="66"></polyline>
</svg>

After

Width:  |  Height:  |  Size: 420 B

View File

@ -0,0 +1,54 @@
<template>
<div class="base-checkbox" v-cy="'checkbox'">
<input
type="checkbox"
:id="checkboxId"
class="is-sr-only"
:checked="modelValue"
@change="(event) => emit('update:modelValue', (event.target as HTMLInputElement).checked)"
:disabled="disabled || undefined"
/>
<slot name="label" :checkboxId="checkboxId">
<label :for="checkboxId" class="base-checkbox__label">
<slot/>
</label>
</slot>
</div>
</template>
<script setup lang="ts">
import {ref} from 'vue'
import {createRandomID} from '@/helpers/randomId'
defineProps({
modelValue: {
type: Boolean,
default: false,
},
disabled: {
type: Boolean,
default: false,
},
})
const emit = defineEmits<{
(event: 'update:modelValue', value: boolean): void
}>()
const checkboxId = ref(`fancycheckbox_${createRandomID()}`)
</script>
<style lang="scss" scoped>
.base-checkbox__label {
cursor: pointer;
user-select: none;
-webkit-tap-highlight-color: transparent;
display: inline-flex;
}
.base-checkbox:has(input:disabled) .base-checkbox__label {
cursor:not-allowed;
pointer-events: none;
}
</style>

View File

@ -0,0 +1,71 @@
<script lang="ts" setup>
import {ref} from 'vue'
import {logEvent} from 'histoire/client'
import FancyCheckbox from './fancycheckbox.vue'
const isDisabled = ref<boolean | undefined>()
const isChecked = ref(false)
const isCheckedInitiallyEnabled = ref(true)
const isCheckedDisabled = ref(false)
const withoutInitialState = ref<boolean | undefined>()
</script>
<template>
<Story :layout="{ type: 'grid', width: '200px' }">
<Variant title="Default">
<FancyCheckbox
v-model="isChecked"
:disabled="isDisabled"
>
This is probably not important
</FancyCheckbox>
Visualisation
<input type="checkbox" v-model="isChecked">
{{ isChecked }}
</Variant>
<Variant title="Enabled Initially">
<FancyCheckbox
:disabled="isDisabled"
v-model="isCheckedInitiallyEnabled"
>
We want you to use this option
</FancyCheckbox>
Visualisation
<input type="checkbox" v-model="isCheckedInitiallyEnabled">
{{ isCheckedInitiallyEnabled }}
</Variant>
<Variant title="Disabled">
<FancyCheckbox
disabled
:modelValue="isCheckedDisabled"
@update:model-value="logEvent('Setting disabled: This should never happen', $event)"
>
You can't change this
</FancyCheckbox>
Visualisation
<input type="checkbox" v-model="isCheckedDisabled" disabled>
{{ isCheckedDisabled }}
</Variant>
<Variant title="Undefined initial State">
<FancyCheckbox
v-model="withoutInitialState"
:disabled="isDisabled"
>
Not sure what the value should be
</FancyCheckbox>
Visualisation
<input type="checkbox" v-model="withoutInitialState" disabled>
{{ withoutInitialState }}
</Variant>
</Story>
</template>

View File

@ -1,66 +1,42 @@
<template>
<div :class="{'is-disabled': disabled}" class="fancycheckbox">
<input
:checked="checked"
:disabled="disabled || undefined"
:id="checkBoxId"
@change="(event: Event) => updateData((event.target as HTMLInputElement).checked)"
type="checkbox"
/>
<label :for="checkBoxId" class="check" @click.prevent="check">
<svg height="18px" viewBox="0 0 18 18" width="18px">
<path
d="M1,9 L1,3.5 C1,2 2,1 3.5,1 L14.5,1 C16,1 17,2 17,3.5 L17,14.5 C17,16 16,17 14.5,17 L3.5,17 C2,17 1,16 1,14.5 L1,9 Z"></path>
<polyline points="1 9 7 14 15 4"></polyline>
</svg>
<span>
<slot></slot>
</span>
</label>
</div>
<BaseCheckbox
class="fancycheckbox"
:class="{
'is-disabled': disabled,
'is-block': isBlock,
}"
:disabled="disabled"
:model-value="modelValue"
@update:model-value="value => emit('update:modelValue', value)"
>
<CheckboxIcon class="fancycheckbox__icon" />
<span v-if="$slots.default" class="fancycheckbox__content">
<slot/>
</span>
</BaseCheckbox>
</template>
<script setup lang="ts">
import {ref, toRef, watch} from 'vue'
import CheckboxIcon from '@/assets/checkbox.svg?component'
import {createRandomID} from '@/helpers/randomId'
import BaseCheckbox from '@/components/base/BaseCheckbox.vue'
const checked = ref(false)
const checkBoxId = `fancycheckbox_${createRandomID()}`
const props = defineProps({
defineProps({
modelValue: {
type: Boolean,
required: false,
},
disabled: {
type: Boolean,
required: false,
},
isBlock: {
type: Boolean,
default: false,
},
})
const emit = defineEmits(['update:modelValue', 'change'])
const modelValue = toRef(props, 'modelValue')
watch(
modelValue,
newValue => {
checked.value = newValue
},
{immediate: true},
)
function updateData(newChecked: boolean) {
checked.value = newChecked
emit('update:modelValue', newChecked)
emit('change', newChecked)
}
function check() {
checked.value = !checked.value
updateData(checked.value)
}
const emit = defineEmits<{
(event: 'update:modelValue', value: boolean): void
}>()
</script>
@ -70,75 +46,54 @@ function check() {
padding-right: 5px;
padding-top: 3px;
// FIXME: should be a prop
&.is-block {
display: block;
margin: .5rem .2rem;
}
}
input[type=checkbox] {
display: none;
}
.check {
cursor: pointer;
position: relative;
margin: auto;
width: 18px;
height: 18px;
-webkit-tap-highlight-color: transparent;
transform: translate3d(0, 0, 0);
}
span {
.fancycheckbox__content {
font-size: 0.8rem;
vertical-align: top;
padding-left: .5rem;
}
svg {
.fancycheckbox__icon:deep() {
position: relative;
z-index: 1;
fill: none;
stroke-linecap: round;
stroke-linejoin: round;
stroke: #c8ccd4;
stroke-width: 1.5;
stroke: var(--stroke-color, #c8ccd4);
transform: translate3d(0, 0, 0);
transition: all 0.2s ease;
}
.check:hover svg {
stroke: var(--primary);
}
.is-disabled .check:hover svg {
stroke: #c8ccd4;
}
path {
stroke-dasharray: 60;
stroke-dashoffset: 0;
}
polyline {
stroke-dasharray: 22;
stroke-dashoffset: 66;
}
input[type=checkbox]:checked + .check {
svg {
stroke: var(--primary);
path,
polyline {
transition: all 0.2s linear, color 0.2s ease;
}
}
.fancycheckbox:not(:has(input:disabled)):hover .fancycheckbox__icon,
.fancycheckbox:has(input:checked) .fancycheckbox__icon {
--stroke-color: var(--primary);
}
</style>
<style lang="scss">
// Since css-has-pseudo doesn't work with deep classes,
// the following rules can't be scoped
.fancycheckbox:has(:not(input:checked)) .fancycheckbox__icon {
path {
transition-delay: 0.05s;
}
}
.fancycheckbox:has(input:checked) .fancycheckbox__icon {
path {
stroke-dashoffset: 60;
transition: all 0.3s linear;
}
polyline {
stroke-dashoffset: 42;
transition: all 0.2s linear;
transition-delay: 0.15s;
}
}

View File

@ -1,12 +1,8 @@
<template>
<router-link
:to="taskDetailRoute"
:class="{'is-loading': taskService.loading}"
class="task loader-container"
>
<div :class="{'is-loading': taskService.loading}" class="task loader-container">
<fancycheckbox
:disabled="(isArchived || disabled) && !canMarkAsDone"
@change="markAsDone"
@update:model-value="markAsDone"
v-model="task.done"
/>
@ -16,7 +12,8 @@
class="mr-1"
/>
<div
<router-link
:to="taskDetailRoute"
:class="{ 'done': task.done, 'show-project': showProject && project !== null}"
class="tasktext"
>
@ -96,7 +93,7 @@
</span>
<checklist-summary :task="task"/>
</div>
</router-link>
<progress
class="progress is-small"
@ -117,14 +114,14 @@
<BaseButton
:class="{'is-favorite': task.isFavorite}"
@click.prevent="toggleFavorite"
@click="toggleFavorite"
class="favorite"
>
<icon icon="star" v-if="task.isFavorite"/>
<icon :icon="['far', 'star']" v-else/>
</BaseButton>
<slot />
</router-link>
</div>
</template>
<script setup lang="ts">
@ -288,11 +285,7 @@ function hideDeferDueDatePopup(e) {
border-radius: $radius;
border: 2px solid transparent;
color: var(--text);
transition: color ease $transition-duration;
&:hover {
color: var(--grey-900);
background-color: var(--grey-100);
}
@ -338,6 +331,15 @@ function hideDeferDueDatePopup(e) {
}
a {
color: var(--text);
transition: color ease $transition-duration;
&:hover {
color: var(--grey-900);
}
}
.favorite {
opacity: 1;
text-align: center;

View File

@ -6,13 +6,11 @@ declare global {
}
}
const cypressDirective: Directive = {
mounted(el, {value}) {
if (
(window.Cypress || import.meta.env.DEV) &&
value
) {
el.setAttribute('data-cy', value)
const cypressDirective = <Directive<HTMLElement,string>>{
mounted(el, {arg, value}) {
const testingId = arg || value
if ((window.Cypress || import.meta.env.DEV) && testingId) {
el.setAttribute('data-cy', testingId)
}
},
beforeUnmount(el) {

View File

@ -1,4 +1,5 @@
import { defineSetupVue3 } from '@histoire/plugin-vue'
import './polyfills'
import {defineSetupVue3} from '@histoire/plugin-vue'
import {i18n} from './i18n'
// import './histoire.css' // Import global CSS
@ -6,18 +7,21 @@ import './styles/global.scss'
import {createPinia} from 'pinia'
import cypress from '@/directives/cypress'
import FontAwesomeIcon from '@/components/misc/Icon'
import XButton from '@/components/input/button.vue'
import Modal from '@/components/misc/modal.vue'
import Card from '@/components/misc/card.vue'
export const setupVue3 = defineSetupVue3(({ app }) => {
// Add Pinia store
const pinia = createPinia()
app.use(pinia)
app.use(i18n)
app.directive('cy', cypress)
app.component('icon', FontAwesomeIcon)
app.component('XButton', XButton)
app.component('modal', Modal)

View File

@ -15,7 +15,7 @@ export const SUPPORTED_LOCALES = {
'pt-PT': 'Português',
'zh-CN': 'Chinese',
'no-NO': 'Norsk Bokmål',
} as Record<string, string>
} as const
export type SupportedLocale = keyof typeof SUPPORTED_LOCALES
@ -23,12 +23,12 @@ export const DEFAULT_LANGUAGE: SupportedLocale= 'en'
export type ISOLanguage = string
// we load all messsages async
// we load all messages async
export const i18n = createI18n({
fallbackLocale: DEFAULT_LANGUAGE,
legacy: false,
messages: {
en: langEN,
[DEFAULT_LANGUAGE]: langEN,
} as Record<SupportedLocale, any>,
})
@ -54,16 +54,16 @@ export async function setLanguage(lang: SupportedLocale = getCurrentLanguage()):
}
export function getCurrentLanguage(): SupportedLocale {
const savedLanguage = localStorage.getItem('language')
const savedLanguage = localStorage.getItem('language') as SupportedLocale | null
if (savedLanguage !== null) {
return savedLanguage
}
const browserLanguage = navigator.language
const language: SupportedLocale | undefined = Object.keys(SUPPORTED_LOCALES).find(langKey => {
const language = Object.keys(SUPPORTED_LOCALES).find(langKey => {
return langKey === browserLanguage || langKey.startsWith(browserLanguage + '-')
})
}) as SupportedLocale | undefined
return language || DEFAULT_LANGUAGE
}

View File

@ -1,4 +1,6 @@
// in order to use postcss-preset-env correctly we need some client side plugins
import focusWithin from 'focus-within'
import focusWithinInit from 'postcss-focus-within/browser'
import cssHasPseudo from 'css-has-pseudo/browser'
focusWithin(document)
focusWithinInit()
cssHasPseudo(document)

View File

@ -22,7 +22,7 @@
<x-button @click="setDefaultFilters">Reset</x-button>
</div>
</div>
<fancycheckbox class="is-block" v-model="filters.showTasksWithoutDates">
<fancycheckbox is-block v-model="filters.showTasksWithoutDates">
{{ $t('project.gantt.showTasksWithoutDates') }}
</fancycheckbox>
</div>

View File

@ -415,6 +415,7 @@ async function updateTaskPosition(e) {
: e.newIndex
const task = newBucket.tasks[newTaskIndex]
const oldBucket = buckets.value.find(b => b.id === task.bucketId)
const taskBefore = newBucket.tasks[newTaskIndex - 1] ?? null
const taskAfter = newBucket.tasks[newTaskIndex + 1] ?? null
taskUpdating.value[task.id] = true
@ -425,6 +426,13 @@ async function updateTaskPosition(e) {
taskBefore !== null ? taskBefore.kanbanPosition : null,
taskAfter !== null ? taskAfter.kanbanPosition : null,
)
if (
oldBucket !== undefined && // This shouldn't actually be `undefined`, but let's play it safe.
newBucket.id !== oldBucket.id &&
newBucket.isDoneBucket !== oldBucket.isDoneBucket
) {
newTask.done = newBucket.isDoneBucket
}
try {
await taskStore.update(newTask)

View File

@ -11,10 +11,10 @@
</x-button>
</template>
</datepicker-with-range>
<fancycheckbox @change="setShowNulls" class="mr-2">
<fancycheckbox @update:model-value="setShowNulls" class="mr-2">
{{ $t('task.show.noDates') }}
</fancycheckbox>
<fancycheckbox @change="setShowOverdue">
<fancycheckbox @update:model-value="setShowOverdue">
{{ $t('task.show.overdue') }}
</fancycheckbox>
</p>

View File

@ -81,6 +81,7 @@ export default defineConfig(({mode}) => {
// See also './src/polyfills.ts'
features: {
'focus-within-pseudo-class': true,
'has-pseudo-class': true,
},
}),
],