mirror of
https://git.hmsn.ink/kospo/svcm/oa.git
synced 2026-03-20 08:03:34 +09:00
680 lines
20 KiB
Vue
680 lines
20 KiB
Vue
<script lang="ts">
|
|
import { defineBasicLoader } from 'unplugin-vue-router/data-loaders/basic'
|
|
/**
|
|
* This is an example of data loader (experimental feature)
|
|
* Name the loader however you want **and export it**
|
|
*
|
|
* Note that it should be defined outside of script setup
|
|
*
|
|
* @see https://github.com/vuejs/rfcs/discussions/460
|
|
* @see https://uvr.esm.is/rfcs/data-loaders/
|
|
*/
|
|
export const useRoadmapData = defineBasicLoader(async (to) => {
|
|
console.log('useRoadmapData defineLoader', to)
|
|
|
|
// this is a fake loader, you may want to load data with fetch
|
|
const [roadmap, releases] = await Promise.all([
|
|
import('/@src/data/apps/roadmap').then(module => module.roadmap),
|
|
import('/@src/data/apps/changelog').then(module => module.changelog),
|
|
])
|
|
|
|
// we use the query params to filters the data,
|
|
// they are executed each time router path change
|
|
const releaseWithBugFixes = releases.filter((release) => {
|
|
if (to.query.type) {
|
|
const firstBugFix = release.changelog.find(item => item.type === to.query.type)
|
|
return firstBugFix !== undefined
|
|
}
|
|
|
|
return true
|
|
})
|
|
|
|
const changelog = releaseWithBugFixes.reduce(
|
|
(accumulator, item) => {
|
|
const month = item.date.split(' ')[0]
|
|
accumulator[month] = accumulator[month] ?? { month: `${month} 2022`, releases: [] }
|
|
accumulator[month].releases.push(item)
|
|
|
|
return accumulator
|
|
},
|
|
{} as Record<string, any>,
|
|
)
|
|
|
|
// return anything you want to expose
|
|
return {
|
|
roadmap,
|
|
changelog,
|
|
}
|
|
}, {
|
|
// used for SSR only
|
|
key: 'roadmap-data',
|
|
})
|
|
</script>
|
|
|
|
<script setup lang="ts">
|
|
import { useRouteQuery } from '@vueuse/router'
|
|
|
|
const { data, isLoading } = useRoadmapData()
|
|
const years = ['2022', '2021', '2020', '2019']
|
|
const changeTypes = ['All', 'Enhancements', 'Features', 'Bug fixes']
|
|
|
|
const selectedYear = useRouteQuery<string>('year', '2022')
|
|
const selectedQuarter = useRouteQuery<string>('quarter', '3')
|
|
const activeTab = useRouteQuery<string>('tab', 'roadmap')
|
|
const selectedChangeType = useRouteQuery<string>('type', 'All')
|
|
|
|
const activeYearProgress = computed(
|
|
() => data.value?.roadmap?.find(x => x.year === selectedYear.value)?.progress,
|
|
)
|
|
|
|
useHead({
|
|
title: 'Utility Roadmap - Sidebar - Vuero',
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<MinimalLayout>
|
|
<LandingGrids class="is-contrasted" />
|
|
<!-- Roadmap -->
|
|
<div class="roadmap-wrapper is-relative">
|
|
<!--Top header-->
|
|
<div class="roadmap-top">
|
|
<RouterLink
|
|
to="/"
|
|
class="logo"
|
|
>
|
|
<AnimatedLogo
|
|
width="38px"
|
|
height="38px"
|
|
/>
|
|
</RouterLink>
|
|
<div>
|
|
<RouterLink
|
|
class="action-link mx-4"
|
|
to="/sidebar/dashboards"
|
|
>
|
|
Home
|
|
</RouterLink>
|
|
<RouterLink
|
|
class="action-link mx-4"
|
|
to="/status"
|
|
>
|
|
App Status
|
|
</RouterLink>
|
|
</div>
|
|
</div>
|
|
|
|
<VTabs
|
|
slider
|
|
align="centered"
|
|
:selected="activeTab"
|
|
:tabs="[
|
|
{
|
|
label: 'Roadmap',
|
|
value: 'roadmap',
|
|
to: '/roadmap?tab=roadmap',
|
|
},
|
|
{
|
|
label: 'Changelog',
|
|
value: 'changelog',
|
|
to: '/roadmap?tab=changelog',
|
|
},
|
|
]"
|
|
>
|
|
<template #tab="{ activeValue }">
|
|
<div v-if="activeValue === 'roadmap'">
|
|
<div
|
|
id="roadmap"
|
|
class="roadmap-outer"
|
|
>
|
|
<div class="roadmap-header has-text-centered">
|
|
<h2 class="title is-2 is-bold">
|
|
Our Roadmap
|
|
</h2>
|
|
<p>
|
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Omnis enim est
|
|
natura diligens sui. Philosophi autem in suis lectulis plerumque
|
|
moriuntur.
|
|
</p>
|
|
|
|
<div class="currently-planned">
|
|
<div>
|
|
<span>
|
|
Q{{ selectedQuarter }} {{ selectedYear }} Planned:
|
|
<span class="count">22 features</span>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="roadmap-inner">
|
|
<div class="roadmap-toolbar is-responsive">
|
|
<div class="start">
|
|
<VField
|
|
v-slot="{ id }"
|
|
class="is-autocomplete-select"
|
|
>
|
|
<VLabel>Year</VLabel>
|
|
<VControl icon="lucide:search">
|
|
<Multiselect
|
|
v-model="selectedYear"
|
|
:attrs="{ id }"
|
|
:options="years"
|
|
placeholder="Select a Year..."
|
|
:searchable="true"
|
|
:loading="isLoading"
|
|
/>
|
|
</VControl>
|
|
</VField>
|
|
|
|
<VField>
|
|
<VLabel>Quarterly</VLabel>
|
|
<VField addons>
|
|
<VControl>
|
|
<VButton
|
|
:class="selectedQuarter === '1' && 'is-active'"
|
|
@click="selectedQuarter = '1'"
|
|
>
|
|
Q1
|
|
</VButton>
|
|
</VControl>
|
|
<VControl>
|
|
<VButton
|
|
:class="selectedQuarter === '2' && 'is-active'"
|
|
@click="selectedQuarter = '2'"
|
|
>
|
|
Q2
|
|
</VButton>
|
|
</VControl>
|
|
<VControl>
|
|
<VButton
|
|
:class="selectedQuarter === '3' && 'is-active'"
|
|
@click="selectedQuarter = '3'"
|
|
>
|
|
Q3
|
|
</VButton>
|
|
</VControl>
|
|
<VControl>
|
|
<VButton
|
|
:class="selectedQuarter === '4' && 'is-active'"
|
|
@click="selectedQuarter = '4'"
|
|
>
|
|
Q4
|
|
</VButton>
|
|
</VControl>
|
|
</VField>
|
|
</VField>
|
|
</div>
|
|
<div class="end">
|
|
<VField>
|
|
<div class="progress-meta">
|
|
<VLabel>Year milestone</VLabel>
|
|
<span class="title is-6 is-bold">
|
|
{{ activeYearProgress }}%
|
|
</span>
|
|
</div>
|
|
<VProgress
|
|
size="smaller"
|
|
:value="activeYearProgress"
|
|
/>
|
|
</VField>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="roadmap-list">
|
|
<!--Item-->
|
|
<template
|
|
v-for="(year, index) in data.roadmap"
|
|
:key="index"
|
|
>
|
|
<template
|
|
v-for="quarter in year.quarters"
|
|
:key="quarter.id"
|
|
>
|
|
<div
|
|
v-if="
|
|
selectedYear.includes(quarter.year) &&
|
|
String(quarter.quarter) === String(selectedQuarter)
|
|
"
|
|
class="roadmap-item"
|
|
>
|
|
<VCollapse
|
|
:items="quarter.features"
|
|
with-chevron
|
|
>
|
|
<template #collapse-item-head="item">
|
|
<div class="head-info">
|
|
<div class="head-progress">
|
|
<span class="text">Progress</span>
|
|
<span class="value">{{ item.item.value }}%</span>
|
|
</div>
|
|
<VProgress
|
|
size="tiny"
|
|
:value="item.item.value"
|
|
/>
|
|
</div>
|
|
</template>
|
|
<template #collapse-item-content="item">
|
|
<div class="body-inner-content">
|
|
<p>{{ item.item.content }}</p>
|
|
<div v-if="item.item.url !== undefined">
|
|
<a
|
|
class="action-link"
|
|
:href="item.item.url"
|
|
>Read More</a>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</VCollapse>
|
|
</div>
|
|
</template>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div v-else-if="activeValue === 'changelog'">
|
|
<div
|
|
id="changelog"
|
|
class="roadmap-outer"
|
|
>
|
|
<div class="roadmap-header has-text-centered">
|
|
<h2 class="title is-2 is-bold">
|
|
Changelog
|
|
</h2>
|
|
<p>
|
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Omnis enim est
|
|
natura diligens sui. Philosophi autem in suis lectulis plerumque
|
|
moriuntur.
|
|
</p>
|
|
|
|
<div class="currently-planned">
|
|
<div>
|
|
<span>Last 30 days: <span class="count">22 new</span></span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="roadmap-inner">
|
|
<div class="roadmap-toolbar">
|
|
<div class="start">
|
|
<VField
|
|
v-slot="{ id }"
|
|
class="is-autocomplete-select"
|
|
>
|
|
<VLabel>Entry types</VLabel>
|
|
<VControl icon="lucide:search">
|
|
<Multiselect
|
|
v-model="selectedChangeType"
|
|
:attrs="{ id }"
|
|
:options="changeTypes"
|
|
placeholder="Select a Type..."
|
|
:searchable="true"
|
|
/>
|
|
</VControl>
|
|
</VField>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="roadmap-list">
|
|
<div
|
|
v-for="(block, index) in data.changelog"
|
|
:key="index"
|
|
class="changelog-items-outer"
|
|
>
|
|
<div class="has-text-centered">
|
|
<h2 class="title is-4 mb-5">
|
|
{{ block.month }}
|
|
</h2>
|
|
</div>
|
|
<div class="changelog-items-inner">
|
|
<!--Item-->
|
|
<template
|
|
v-for="(item, r) in block.releases"
|
|
:key="r"
|
|
>
|
|
<div class="changelog-item">
|
|
<VCardAdvanced>
|
|
<template #header-left>
|
|
<h3 class="title is-6 py-2">
|
|
{{ item.date }}
|
|
</h3>
|
|
</template>
|
|
<template #header-right>
|
|
<VTag
|
|
:label="item.tag"
|
|
curved
|
|
/>
|
|
</template>
|
|
<template #content>
|
|
<div
|
|
v-for="(line, i) in item.changelog"
|
|
:key="i"
|
|
class="changelog-line"
|
|
:style="{
|
|
opacity:
|
|
selectedChangeType === 'All' ||
|
|
selectedChangeType === line.type
|
|
? 1
|
|
: 0.3,
|
|
}"
|
|
>
|
|
<VTag
|
|
v-if="line.type === 'Enhancements'"
|
|
color="primary"
|
|
:label="line.type"
|
|
curved
|
|
outlined
|
|
/>
|
|
<VTag
|
|
v-else-if="line.type === 'Features'"
|
|
color="info"
|
|
:label="line.type"
|
|
curved
|
|
outlined
|
|
/>
|
|
<VTag
|
|
v-else
|
|
color="danger"
|
|
:label="line.type"
|
|
curved
|
|
outlined
|
|
/>
|
|
<span>{{ line.title }}</span>
|
|
</div>
|
|
</template>
|
|
</VCardAdvanced>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</VTabs>
|
|
|
|
<div class="roadmap-footer">
|
|
<VDarkmodeToggle />
|
|
<div>
|
|
<a href="#">Legal</a>
|
|
<a href="#">About</a>
|
|
<a href="#">Jobs</a>
|
|
</div>
|
|
<div class="copyright">
|
|
<span
|
|
role="img"
|
|
aria-label="copyright"
|
|
>©</span>
|
|
<span>2020-{{ new Date().getFullYear() }} cssninjaStudio</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</MinimalLayout>
|
|
</template>
|
|
|
|
<style lang="scss" scoped>
|
|
.roadmap-wrapper {
|
|
max-width: 940px;
|
|
margin: 0 auto;
|
|
padding: 4rem 1rem;
|
|
|
|
.roadmap-top {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
margin-bottom: 1.5rem;
|
|
|
|
.logo {
|
|
display: block;
|
|
width: 50px;
|
|
min-width: 50px;
|
|
height: 50px;
|
|
}
|
|
}
|
|
|
|
.roadmap-outer {
|
|
padding: 2rem 0;
|
|
|
|
.roadmap-header {
|
|
margin-bottom: 3rem;
|
|
|
|
p {
|
|
font-size: 1.25rem;
|
|
max-width: 480px;
|
|
margin: 0 auto 2rem;
|
|
}
|
|
|
|
.currently-planned {
|
|
font-family: var(--font);
|
|
color: var(--medium-text);
|
|
|
|
.count {
|
|
color: var(--primary);
|
|
}
|
|
}
|
|
}
|
|
|
|
.roadmap-inner {
|
|
.roadmap-toolbar {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
margin-bottom: 1.5rem;
|
|
|
|
.start,
|
|
.end {
|
|
display: flex;
|
|
align-items: center;
|
|
}
|
|
|
|
.start {
|
|
max-width: 50%;
|
|
|
|
:deep(.field) {
|
|
min-width: 200px;
|
|
margin-bottom: 0;
|
|
|
|
&:first-child {
|
|
margin-inline-end: 2rem;
|
|
}
|
|
}
|
|
}
|
|
|
|
.end {
|
|
justify-content: flex-end;
|
|
|
|
:deep(.field) {
|
|
min-width: 220px;
|
|
margin-bottom: 0;
|
|
|
|
.progress-meta {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
margin-bottom: 0.75rem;
|
|
|
|
label {
|
|
font-family: var(--font);
|
|
font-size: 0.9rem;
|
|
color: var(--light-text) !important;
|
|
font-weight: 400;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
.roadmap-list {
|
|
.roadmap-item {
|
|
.head-info {
|
|
.head-progress {
|
|
font-family: var(--font);
|
|
border-inline-end: 1px solid var(--border);
|
|
padding-inline-end: 1rem;
|
|
margin-inline-end: 1rem;
|
|
|
|
.text {
|
|
color: var(--light-text);
|
|
margin-inline-end: 0.75rem;
|
|
}
|
|
|
|
.value {
|
|
color: var(--primary);
|
|
font-weight: 600;
|
|
}
|
|
}
|
|
}
|
|
|
|
.body-inner-content {
|
|
padding-top: 1rem;
|
|
|
|
p {
|
|
color: var(--medium-text);
|
|
}
|
|
}
|
|
|
|
:deep(.progress) {
|
|
position: absolute !important;
|
|
bottom: 0;
|
|
inset-inline-start: 0;
|
|
width: 100%;
|
|
}
|
|
}
|
|
|
|
.changelog-items-outer {
|
|
.changelog-items-inner {
|
|
.changelog-item {
|
|
:deep(.s-card-advanced) {
|
|
.changelog-line {
|
|
font-family: var(--font);
|
|
color: var(--medium-text);
|
|
|
|
.tag {
|
|
border-width: 2px;
|
|
margin-inline-end: 1rem;
|
|
}
|
|
|
|
+ .changelog-line {
|
|
margin-top: 1rem;
|
|
}
|
|
}
|
|
|
|
.card-foot {
|
|
display: none !important;
|
|
}
|
|
}
|
|
|
|
+ .changelog-item {
|
|
margin-top: 1rem;
|
|
}
|
|
}
|
|
}
|
|
|
|
+ .changelog-items-outer {
|
|
margin-top: 5rem;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
.roadmap-footer {
|
|
display: flex;
|
|
padding: 20px;
|
|
align-items: center;
|
|
|
|
.dark-mode {
|
|
display: inline-block;
|
|
transform: scale(0.5);
|
|
}
|
|
|
|
a {
|
|
font-family: var(--font);
|
|
color: color-mix(in oklab, var(--light-text), black 8%);
|
|
padding: 0 10px;
|
|
transition: color 0.3s;
|
|
|
|
&:hover,
|
|
&:focus {
|
|
font-weight: 500;
|
|
color: var(--primary);
|
|
}
|
|
}
|
|
|
|
.copyright {
|
|
margin-inline-start: auto;
|
|
font-family: var(--font);
|
|
color: var(--light-text);
|
|
}
|
|
}
|
|
}
|
|
|
|
@media only screen and (width <= 767px) {
|
|
.roadmap-wrapper {
|
|
.roadmap-outer {
|
|
.roadmap-inner {
|
|
.roadmap-toolbar {
|
|
&.is-responsive {
|
|
flex-direction: column;
|
|
|
|
.start,
|
|
.end {
|
|
flex-direction: column;
|
|
width: 100%;
|
|
max-width: 100% !important;
|
|
}
|
|
|
|
:deep(.field) {
|
|
max-width: 100%;
|
|
width: 100%;
|
|
margin-inline-start: 0 !important;
|
|
margin-inline-end: 0 !important;
|
|
margin-bottom: 1rem !important;
|
|
|
|
.control {
|
|
flex: 1 1 0;
|
|
|
|
.v-button {
|
|
width: 100%;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
.roadmap-list {
|
|
.roadmap-item {
|
|
.head-info {
|
|
.head-progress {
|
|
display: none;
|
|
}
|
|
}
|
|
}
|
|
|
|
.changelog-items-outer {
|
|
.changelog-items-inner {
|
|
.changelog-item {
|
|
:deep(.s-card-advanced) {
|
|
.changelog-line {
|
|
span {
|
|
display: block;
|
|
padding-top: 0.5rem;
|
|
}
|
|
|
|
+ .changelog-line {
|
|
margin-top: 2rem;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
</style>
|