All checks were successful
ci/woodpecker/push/build-and-publish Pipeline was successful
254 lines
6.3 KiB
Vue
254 lines
6.3 KiB
Vue
<template>
|
|
<div>
|
|
<h1>Appearance</h1>
|
|
|
|
<h2>Themes</h2>
|
|
<div class="themes">
|
|
<p class="subtitle">STYLES</p>
|
|
<div class="styles">
|
|
<div v-for="style of styles" class="theme-preview-container">
|
|
<span class="theme-instance"
|
|
:title="style.displayName"
|
|
@click="changeTheme(StyleLayout.style, style)">
|
|
<div class="theme-content-container">
|
|
<span class="style-background"
|
|
:style="{background:`linear-gradient(${style.previewGradient})`}"
|
|
></span>
|
|
<span class="theme-title" :style="{color:`${style.complementaryColor}`}">
|
|
{{ style.displayName }}
|
|
</span>
|
|
</div>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<p class="subtitle">LAYOUTS</p>
|
|
<div class="layouts">
|
|
<div v-for="layout of layouts" class="theme-preview-container">
|
|
<div class="theme-instance"
|
|
:title="layout.displayName"
|
|
@click="changeTheme(StyleLayout.layout, layout)">
|
|
<div class="theme-content-container">
|
|
<span class="layout-background"
|
|
:style="{backgroundImage:`url(${layout.previewImageUrl})`}"
|
|
></span>
|
|
<span class="theme-title" :style="{color:`${layout.complementaryColor}`}">
|
|
{{ layout.displayName }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- <p class="subtitle">Icons</p>
|
|
<div class="icons">
|
|
</div> -->
|
|
|
|
<p class="subtitle">TIME FORMAT</p>
|
|
<div class="icons">
|
|
<RadioButtons :button-count="3" :text-strings="timeFormatTextStrings"
|
|
:default-button-index="settingsLoad().timeFormat?.index ?? 0" :callback="onTimeFormatClicked"></RadioButtons>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script lang="ts" setup>
|
|
import RadioButtons from '~/components/UserInterface/RadioButtons.vue';
|
|
import type { TimeFormat } from '~/types/settings';
|
|
import { settingSave, settingsLoad } from '#imports';
|
|
|
|
const runtimeConfig = useRuntimeConfig()
|
|
const baseURL = runtimeConfig.app.baseURL;
|
|
const styleFolder = `${baseURL}themes/style`
|
|
const layoutFolder = `${baseURL}themes/layout`
|
|
|
|
const timeFormatTextStrings = ["Auto", "12-Hour", "24-Hour"]
|
|
|
|
enum StyleLayout {
|
|
style,
|
|
layout
|
|
}
|
|
|
|
interface Theme {
|
|
displayName: string
|
|
complementaryColor: string
|
|
cssData: string
|
|
themeUrl: string
|
|
previewGradient?: string
|
|
previewImageUrl?: string
|
|
}
|
|
|
|
async function parseTheme(url: string): Promise<Theme | void> {
|
|
const styleData: any = await $fetch(url)
|
|
|
|
if (typeof styleData != "string") {
|
|
return
|
|
}
|
|
|
|
const metadataMatch = styleData.match(/\/\*([\s\S]*?)\*\//);
|
|
if (!metadataMatch) {
|
|
alert(`Failed to fetch metadata for a theme, panicing`)
|
|
return
|
|
}
|
|
|
|
const commentContent = metadataMatch[0].trim().split("\n");
|
|
const cssData = styleData.substring(metadataMatch[0].length).trim();
|
|
|
|
let displayName: string | undefined
|
|
let complementaryColor: string | undefined
|
|
let previewGradient: string | undefined
|
|
let previewImageUrl: string | undefined
|
|
|
|
for (const line of commentContent) {
|
|
const line_array = line.split("=")
|
|
if (line_array.length === 2) {
|
|
switch (line_array[0].trim()) {
|
|
case "displayName":
|
|
displayName = line_array[1].trim()
|
|
break
|
|
case "complementaryColor":
|
|
complementaryColor = line_array[1].trim()
|
|
break
|
|
case "previewGradient":
|
|
previewGradient = line_array[1].trim()
|
|
break
|
|
case "previewImageUrl":
|
|
previewImageUrl = `${layoutFolder}/${line_array[1].trim()}`
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
console.log(displayName, complementaryColor, previewGradient, previewImageUrl, cssData)
|
|
if (!(displayName && complementaryColor && cssData && (previewGradient || previewImageUrl))) {
|
|
return
|
|
}
|
|
|
|
return {
|
|
displayName,
|
|
complementaryColor,
|
|
cssData,
|
|
themeUrl: url,
|
|
previewGradient,
|
|
previewImageUrl,
|
|
}
|
|
}
|
|
|
|
async function parseThemeLayout(
|
|
folder: string,
|
|
incomingThemeList: Array<string>,
|
|
outputThemeList: Array<Theme>) {
|
|
for (const theme of incomingThemeList) {
|
|
const parsedThemeData = await parseTheme(`${folder}/${theme}`)
|
|
|
|
if (parsedThemeData) {
|
|
outputThemeList.push(parsedThemeData)
|
|
}
|
|
}
|
|
}
|
|
|
|
const styles: Array<Theme> = [];
|
|
const layouts: Array<Theme> = [];
|
|
|
|
const styleList: any = await $fetch(`${styleFolder}/styles.json`)
|
|
const layoutList: any = await $fetch(`${layoutFolder}/layouts.json`)
|
|
|
|
if (Array.isArray(styleList)) {
|
|
await parseThemeLayout(styleFolder, styleList, styles)
|
|
}
|
|
if (Array.isArray(layoutList)) {
|
|
await parseThemeLayout(layoutFolder, layoutList, layouts)
|
|
}
|
|
|
|
function changeTheme(themeType: StyleLayout, theme: Theme) {
|
|
if (themeType == StyleLayout.style) {
|
|
settingSave("selectedThemeStyle", theme.themeUrl)
|
|
} else {
|
|
settingSave("selectedThemeLayout", theme.themeUrl)
|
|
}
|
|
loadPreferredThemes()
|
|
}
|
|
|
|
async function onTimeFormatClicked(index: number) {
|
|
let format: "auto" | "12" | "24" = "auto"
|
|
|
|
if (index == 0) {
|
|
format = "auto"
|
|
} else if (index == 1) {
|
|
format = "12"
|
|
} else if (index == 2) {
|
|
format = "24"
|
|
}
|
|
|
|
const timeFormat: TimeFormat = {index, format}
|
|
settingSave("timeFormat", timeFormat)
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.themes {
|
|
--instance-size: 5em;
|
|
}
|
|
.styles, .layouts {
|
|
display: flex;
|
|
}
|
|
|
|
.theme-preview-container {
|
|
margin: .5em;
|
|
width: var(--instance-size);
|
|
height: var(--instance-size);
|
|
}
|
|
|
|
.theme-instance {
|
|
width: var(--instance-size);
|
|
height: var(--instance-size);
|
|
border-radius: 100%;
|
|
border: .1em solid var(--primary-color);
|
|
|
|
display: inline-block;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.theme-content-container {
|
|
position: relative;
|
|
|
|
text-align: center;
|
|
align-content: center;
|
|
}
|
|
|
|
.style-background, .layout-background {
|
|
position: absolute;
|
|
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
|
|
width: var(--instance-size);
|
|
height: var(--instance-size);
|
|
|
|
border-radius: 100%;
|
|
}
|
|
|
|
.layout-background {
|
|
background-size: cover;
|
|
background-repeat: no-repeat;
|
|
filter: brightness(35%);
|
|
}
|
|
|
|
.theme-title {
|
|
position: absolute;
|
|
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
|
|
width: 100%;
|
|
height: 100%;
|
|
|
|
font-size: .8em;
|
|
/* i CANNOT explain this line height calculation, but it works for a font size of .8em no matter what size the instances are */
|
|
line-height: calc(var(--instance-size) * 1.25);
|
|
}
|
|
</style>
|