1
0

fix: migration icons are not resolved properly (#864)

Co-authored-by: kolaente <k@knt.li>
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: https://kolaente.dev/vikunja/frontend/pulls/864
Co-authored-by: konrad <k@knt.li>
Co-committed-by: konrad <k@knt.li>
This commit is contained in:
konrad
2021-10-26 20:21:19 +00:00
parent 6394485524
commit e1a7fb4999
10 changed files with 303 additions and 345 deletions

View File

@ -2,13 +2,18 @@
<div class="content">
<h1>{{ $t('migrate.title') }}</h1>
<p>{{ $t('migrate.description') }}</p>
<div class="migration-services-overview">
<div class="migration-services">
<router-link
v-for="{name, identifier} in availableMigrators"
:key="identifier"
:to="{name: 'migrate.service', params: {service: identifier}}"
v-for="{name, id, icon} in availableMigrators"
:key="id"
class="migration-service-link"
:to="{name: 'migrate.service', params: {service: id}}"
>
<img :alt="name" :src="serviceIconSources[identifier]"/>
<img
class="migration-service-image"
:alt="name"
:src="icon"
/>
{{ name }}
</router-link>
</div>
@ -16,38 +21,37 @@
</template>
<script>
import {getMigratorFromSlug, SERVICE_ICONS} from '../../helpers/migrator'
import {MIGRATORS} from './migrators'
export default {
name: 'migrate.service',
name: 'Migrate',
mounted() {
this.setTitle(this.$t('migrate.title'))
},
computed: {
availableMigrators() {
return this.$store.state.config.availableMigrators.map(getMigratorFromSlug)
},
serviceIconSources() {
return this.availableMigrators.map(({identifier}) => SERVICE_ICONS[identifier]())
return this.$store.state.config.availableMigrators
.map((id) => MIGRATORS[id])
.filter((item) => Boolean(item))
},
},
}
</script>
<style lang="scss" scoped>
.migration-services-overview {
.migration-services {
text-align: center;
}
a {
.migration-service-link {
display: inline-block;
width: 100px;
text-transform: capitalize;
margin-right: 1rem;
img {
display: block;
}
}
}
.migration-service-image {
display: block;
}
</style>

View File

@ -1,40 +1,252 @@
<template>
<migration
:identifier="identifier"
:name="name"
:is-file-migrator="isFileMigrator"
/>
<div class="content">
<h1>{{ $t('migrate.titleService', {name: migrator.name}) }}</h1>
<p>{{ $t('migrate.descriptionDo') }}</p>
<template v-if="message === '' && lastMigrationDate === null">
<template v-if="isMigrating === false">
<template v-if="migrator.isFileMigrator">
<p>{{ $t('migrate.importUpload', {name: migrator.name}) }}</p>
<input
@change="migrate"
class="is-hidden"
ref="uploadInput"
type="file"
/>
<x-button
:loading="migrationService.loading"
:disabled="migrationService.loading || null"
@click="$refs.uploadInput.click()"
>
{{ $t('migrate.upload') }}
</x-button>
</template>
<template v-else>
<p>{{ $t('migrate.authorize', {name: migrator.name}) }}</p>
<x-button
:loading="migrationService.loading"
:disabled="migrationService.loading || null"
:href="authUrl"
>
{{ $t('migrate.getStarted') }}
</x-button>
</template>
</template>
<div
v-else
class="migration-in-progress-container"
>
<div class="migration-in-progress">
<img :alt="migrator.name" :src="migrator.icon"/>
<div class="progress-dots">
<span v-for="i in progressDotsCount" :key="i" />
</div>
<img alt="Vikunja" :src="logoUrl" />
</div>
<p>{{ $t('migrate.inProgress') }}</p>
</div>
</template>
<div v-else-if="lastMigrationDate">
<p>
{{ $t('migrate.alreadyMigrated1', {name: migrator.name, date: formatDate(lastMigrationDate)}) }}<br/>
{{ $t('migrate.alreadyMigrated2') }}
</p>
<div class="buttons">
<x-button @click="migrate">{{ $t('migrate.confirm') }}</x-button>
<x-button :to="{name: 'home'}" type="tertary" class="has-text-danger">{{ $t('misc.cancel') }}</x-button>
</div>
</div>
<div v-else>
<div class="message is-primary">
<div class="message-body">
{{ message }}
</div>
</div>
<x-button :to="{name: 'home'}">{{ $t('misc.refresh') }}</x-button>
</div>
</div>
</template>
<script>
import Migration from '../../components/migrator/migration'
import {getMigratorFromSlug} from '../../helpers/migrator'
import AbstractMigrationService from '@/services/migrator/abstractMigration'
import AbstractMigrationFileService from '@/services/migrator/abstractMigrationFile'
import {MIGRATORS} from './migrators'
import logoUrl from '@/assets/logo.svg'
const PROGRESS_DOTS_COUNT = 8
export default {
name: 'migrateService',
components: {
Migration,
},
name: 'MigrateService',
data() {
return {
name: '',
identifier: '',
isFileMigrator: false,
progressDotsCount: PROGRESS_DOTS_COUNT,
authUrl: '',
isMigrating: false,
lastMigrationDate: null,
message: '',
migratorAuthCode: '',
migrationService: null,
logoUrl,
}
},
mounted() {
this.setTitle(this.$t('migrate.titleService', {name: this.name}))
computed: {
migrator() {
return MIGRATORS[this.$route.params.service]
},
},
beforeRouteEnter(to) {
if (MIGRATORS[to.params.service] === undefined) {
return { name: 'not-found' }
}
},
created() {
try {
const {name, identifier, isFileMigrator} = getMigratorFromSlug(this.$route.params.service)
this.name = name
this.identifier = identifier
this.isFileMigrator = isFileMigrator
} catch (e) {
this.$router.push({name: 'not-found'})
}
this.initMigration()
},
mounted() {
this.setTitle(this.$t('migrate.titleService', {name: this.migrator.name}))
},
methods: {
async initMigration() {
this.migrationService = this.migrator.isFileMigrator
? new AbstractMigrationFileService(this.migrator.id)
: new AbstractMigrationService(this.migrator.id)
if (this.migrator.isFileMigrator) {
return
}
this.authUrl = await this.migrationService.getAuthUrl().then(({url}) => url)
this.migratorAuthCode = location.hash.startsWith('#token=')
? location.hash.substring(7)
: this.$route.query.code
if (!this.migratorAuthCode) {
return
}
const {time} = await this.migrationService.getStatus()
if (time) {
this.lastMigrationDate = typeof time === 'string' && time?.startsWith('0001-')
? null
: new Date(time)
if (this.lastMigrationDate) {
return
}
}
await this.migrate()
},
async migrate() {
this.isMigrating = true
this.lastMigrationDate = null
this.message = ''
let migrationConfig = { code: this.migratorAuthCode }
if (this.migrator.isFileMigrator) {
if (this.$refs.uploadInput.files.length === 0) {
return
}
migrationConfig = this.$refs.uploadInput.files[0]
}
try {
const { message } = await this.migrationService.migrate(migrationConfig)
this.message = message
return this.$store.dispatch('namespaces/loadNamespaces')
} finally {
this.isMigrating = false
}
},
},
}
</script>
<style lang="scss" scoped>
.migration-in-progress-container {
max-width: 400px;
margin: 4rem auto 0;
text-align: center;
}
.migration-in-progress {
text-align: center;
display: flex;
max-width: 400px;
justify-content: space-between;
align-items: center;
margin-bottom: 2rem;
img {
display: block;
max-height: 100px;
}
}
.progress-dots {
height: 40px;
width: 140px;
overflow: visible;
span {
transition: all 500ms ease;
background: $grey-500;
height: 10px;
width: 10px;
display: inline-block;
border-radius: 10px;
animation: wave 2s ease infinite;
margin-right: 5px;
&:nth-child(1) {
animation-delay: 0;
}
&:nth-child(2) {
animation-delay: 100ms;
}
&:nth-child(3) {
animation-delay: 200ms;
}
&:nth-child(4) {
animation-delay: 300ms;
}
&:nth-child(5) {
animation-delay: 400ms;
}
&:nth-child(6) {
animation-delay: 500ms;
}
&:nth-child(7) {
animation-delay: 600ms;
}
&:nth-child(8) {
animation-delay: 700ms;
}
}
}
@keyframes wave {
0%, 40%, 100% {
transform: translate(0, 0);
background-color: $primary;
}
10% {
transform: translate(0, -15px);
background-color: $primary-dark;
}
}
</style>

View File

@ -0,0 +1,44 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 1007.9 821.8">
<defs>
<radialGradient id="c" cx="410.2" cy="853.3" r="85" gradientTransform="rotate(45 546.8 785.4)" gradientUnits="userSpaceOnUse">
<stop offset=".5" stop-opacity=".1"/>
<stop offset="1" stop-opacity="0"/>
</radialGradient>
<radialGradient id="e" cx="1051.1" cy="1265.9" r="85" gradientTransform="rotate(-135 769.6 767.5)" xlink:href="#c"/>
<radialGradient id="h" cx="27.6" cy="2001.4" r="85" gradientTransform="scale(1 -1) rotate(45 2979.2 860.2)" xlink:href="#c"/>
<linearGradient id="a" x1="700.8" y1="597" x2="749.8" y2="597" gradientTransform="matrix(.867 0 0 1.307 86.6 -142.3)" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-opacity=".1"/>
<stop offset="1" stop-opacity="0"/>
</linearGradient>
<linearGradient id="f" x1="1880.8" y1="34.3" x2="1929.8" y2="34.3" gradientTransform="matrix(.867 0 0 -.796 -1446 767.1)" xlink:href="#a"/>
<linearGradient id="i" x1="308.4" y1="811.6" x2="919.3" y2="200.7" gradientTransform="rotate(-45 613.8 506.2)" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#2987e6"/>
<stop offset="1" stop-color="#58c1f5"/>
</linearGradient>
<mask id="b" x="317.1" y="651.8" width="170" height="205.2" maskUnits="userSpaceOnUse">
<path class="a" transform="rotate(45 546.8 845.5)" d="M367.7 871h85v85h-85z"/>
</mask>
<mask id="d" x="837.9" y="95.8" width="205.2" height="205.2" maskUnits="userSpaceOnUse">
<path class="a" transform="rotate(-135 932.9 246)" d="M876 260h170v85H876z"/>
</mask>
<mask id="g" x="-35.2" y="299.5" width="205.2" height="205.2" maskUnits="userSpaceOnUse">
<path class="a" transform="rotate(-45 -81.7 457.6)" d="M-22 463.7h170v85H-22z"/>
</mask>
<style>
.a{fill:#fff}
</style>
</defs>
<path transform="rotate(45 852.3 570)" style="fill:url(#a)" d="M694.4 269.8h42.5v736.5h-42.5z"/>
<g style="mask:url(#b)">
<circle cx="402.1" cy="736.8" r="85" style="fill:url(#c)"/>
</g>
<g style="mask:url(#d)">
<circle cx="922.9" cy="216" r="85" style="fill:url(#e)"/>
</g>
<path transform="rotate(135 226.7 680)" style="fill:url(#f)" d="M185.3 515.6h42.5v448.5h-42.5z"/>
<g style="mask:url(#g)">
<circle cx="85" cy="419.7" r="85" style="fill:url(#h)"/>
</g>
<rect x="164.4" y="320" width="288" height="576" rx="42.5" transform="rotate(-45 163.7 559.5)" style="fill:#195abd"/>
<rect x="469.8" y="74.2" width="288" height="864" rx="42.5" transform="rotate(45 750.5 438.2)" style="fill:url(#i)"/>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -0,0 +1,6 @@
<svg width="256" height="256" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid">
<path d="M224 0H32A32 32 0 0 0 0 32v192a32 32 0 0 0 32 32h192a32 32 0 0 0 32-32V32a32 32 0 0 0-32-32" fill="#E44332"/>
<path d="m54.1 120.8 102.6-59.6c2.2-1.3 2.3-5.2-.2-6.6l-8.8-5.1a8 8 0 0 0-8 0c-1.2.8-83.1 48.3-85.8 50-3.3 1.8-7.4 1.8-10.6 0L0 74v21.6l43 25.2c3.8 2.2 7.5 2.1 11.1 0" fill="#FFF"/>
<path d="M54.1 161.6 156.7 102c2.2-1.3 2.3-5.2-.2-6.6l-8.8-5.1a8 8 0 0 0-8 0l-85.8 50c-3.3 1.8-7.4 1.8-10.6 0L0 114.7v21.6l43 25.2c3.8 2.2 7.5 2.1 11.1 0" fill="#FFF"/>
<path d="m54.1 205 102.6-59.6c2.2-1.3 2.3-5.2-.2-6.7l-8.8-5a8 8 0 0 0-8 0L54 183.6c-3.3 1.9-7.4 1.9-10.6 0L0 158.2v21.6L43 205c3.8 2.1 7.5 2 11.1 0" fill="#FFF"/>
</svg>

After

Width:  |  Height:  |  Size: 745 B

View File

@ -0,0 +1,11 @@
<svg width="256" height="256" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid">
<defs>
<linearGradient x1="50%" y1="0%" x2="50%" y2="100%" id="a">
<stop stop-color="#0091E6" offset="0%"/>
<stop stop-color="#0079BF" offset="100%"/>
</linearGradient>
</defs>
<rect fill="url(#a)" width="256" height="256" rx="25"/>
<rect fill="#FFF" x="144.6" y="33.3" width="78.1" height="112" rx="12"/>
<rect fill="#FFF" x="33.3" y="33.3" width="78.1" height="176" rx="12"/>
</svg>

After

Width:  |  Height:  |  Size: 512 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

View File

@ -0,0 +1,45 @@
import wunderlistIcon from './icons/wunderlist.jpg'
import todoistIcon from './icons/todoist.svg'
import trelloIcon from './icons/trello.svg'
import microsoftTodoIcon from './icons/microsoft-todo.svg'
import vikunjaFileIcon from './icons/vikunja-file.png'
export interface Migrator {
id: string
name: string
isFileMigrator?: boolean
icon: string
}
interface IMigratorRecord {
[key: Migrator['id']]: Migrator
}
export const MIGRATORS: IMigratorRecord = {
wunderlist: {
id: 'wunderlist',
name: 'Wunderlist',
icon: wunderlistIcon,
},
todoist: {
id: 'todoist',
name: 'Todoist',
icon: todoistIcon,
},
trello: {
id: 'trello',
name: 'Trello',
icon: trelloIcon,
},
'microsoft-todo': {
id: 'microsoft-todo',
name: 'Microsoft Todo',
icon: microsoftTodoIcon,
},
'vikunja-file': {
id: 'vikunja-file',
name: 'Vikunja Export',
icon: vikunjaFileIcon,
isFileMigrator: true,
},
}