feat: implement layout switching and saving
All checks were successful
ci/woodpecker/push/build-and-publish Pipeline was successful
All checks were successful
ci/woodpecker/push/build-and-publish Pipeline was successful
This commit is contained in:
parent
5191ac7df7
commit
37ebcb74aa
5 changed files with 72 additions and 48 deletions
3
app.vue
3
app.vue
|
@ -9,14 +9,13 @@
|
|||
<script lang="ts" setup>
|
||||
import ContextMenu from '~/components/UserInterface/ContextMenu.vue';
|
||||
import type { ContextMenuInterface } from './types/interfaces';
|
||||
import loadPreferredTheme from '~/utils/loadPreferredTheme';
|
||||
|
||||
const banner = useState("banner", () => false);
|
||||
|
||||
const contextMenu = useState<ContextMenuInterface>("contextMenu");
|
||||
|
||||
onMounted(() => {
|
||||
loadPreferredTheme()
|
||||
loadPreferredThemes()
|
||||
|
||||
document.removeEventListener("contextmenu", contextMenuHandler);
|
||||
document.addEventListener("contextmenu", (e) => {
|
||||
|
|
|
@ -7,7 +7,9 @@
|
|||
<p class="subtitle">STYLES</p>
|
||||
<div class="styles">
|
||||
<div v-for="style of styles" class="theme-preview-container">
|
||||
<div class="theme-instance" :title="style.displayName">
|
||||
<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})`}"
|
||||
|
@ -16,13 +18,15 @@
|
|||
{{ style.displayName }}
|
||||
</span>
|
||||
</div>
|
||||
</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">
|
||||
<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})`}"
|
||||
|
@ -51,8 +55,7 @@
|
|||
<script lang="ts" setup>
|
||||
import RadioButtons from '~/components/UserInterface/RadioButtons.vue';
|
||||
import type { TimeFormat } from '~/types/settings';
|
||||
import loadPreferredTheme from '~/utils/loadPreferredTheme';
|
||||
import settingSave from '~/utils/settingSave';
|
||||
import { settingSave, settingsLoad } from '#imports';
|
||||
|
||||
const runtimeConfig = useRuntimeConfig()
|
||||
const baseURL = runtimeConfig.app.baseURL;
|
||||
|
@ -61,15 +64,27 @@ 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 parseThemeCss(styleData: string): Promise<Theme | void> {
|
||||
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`)
|
||||
|
@ -113,18 +128,18 @@ async function parseThemeCss(styleData: string): Promise<Theme | void> {
|
|||
displayName,
|
||||
complementaryColor,
|
||||
cssData,
|
||||
themeUrl: url,
|
||||
previewGradient,
|
||||
previewImageUrl,
|
||||
}
|
||||
}
|
||||
|
||||
async function parseThemeData(
|
||||
async function parseThemeLayout(
|
||||
folder: string,
|
||||
incomingThemeList: Array<string>,
|
||||
outputThemeList: Array<Theme>) {
|
||||
for (const theme of incomingThemeList) {
|
||||
const themeData = await $fetch(`${folder}/${theme}`)
|
||||
const parsedThemeData = await parseThemeCss(themeData)
|
||||
const parsedThemeData = await parseTheme(`${folder}/${theme}`)
|
||||
|
||||
if (parsedThemeData) {
|
||||
outputThemeList.push(parsedThemeData)
|
||||
|
@ -139,18 +154,19 @@ const styleList: any = await $fetch(`${styleFolder}/styles.json`)
|
|||
const layoutList: any = await $fetch(`${layoutFolder}/layouts.json`)
|
||||
|
||||
if (Array.isArray(styleList)) {
|
||||
await parseThemeData(styleFolder, styleList, styles)
|
||||
await parseThemeLayout(styleFolder, styleList, styles)
|
||||
}
|
||||
if (Array.isArray(layoutList)) {
|
||||
await parseThemeData(layoutFolder, layoutList, layouts)
|
||||
await parseThemeLayout(layoutFolder, layoutList, layouts)
|
||||
}
|
||||
|
||||
console.log(layouts)
|
||||
|
||||
|
||||
function changeTheme(id: string, url: string) {
|
||||
settingSave("selectedThemeStyle", id)
|
||||
loadPreferredTheme()
|
||||
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) {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
export interface ClientSettings {
|
||||
selectedThemeId?: string, // the ID of the theme, not the URL, for example "dark"
|
||||
timeFormat?: TimeFormat
|
||||
selectedThemeStyle?: string // URL
|
||||
selectedThemeLayout?: string // URL
|
||||
}
|
||||
|
||||
export interface TimeFormat {
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
let themeLinkElement: HTMLLinkElement | null;
|
||||
|
||||
export default function loadPreferredTheme() {
|
||||
const currentTheme = settingsLoad().selectedThemeId ?? "dark"
|
||||
|
||||
if (themeLinkElement) {
|
||||
themeLinkElement.href = getThemeUrl(currentTheme);
|
||||
} else {
|
||||
// create the theme link if one doesn't already exist
|
||||
useHead({
|
||||
link: [{
|
||||
id: "main-theme",
|
||||
rel: "stylesheet",
|
||||
href: getThemeUrl(currentTheme)
|
||||
}]
|
||||
})
|
||||
|
||||
themeLinkElement = document.getElementById('main-theme') as HTMLLinkElement;
|
||||
}
|
||||
}
|
||||
|
||||
function getThemeUrl(id: string): string {
|
||||
const runtimeConfig = useRuntimeConfig()
|
||||
const baseURL = runtimeConfig.app.baseURL;
|
||||
|
||||
// this should preferrably use version hash, but that's not implemented yet
|
||||
return `${baseURL}themes/style/${id}.css?v=${runtimeConfig.public.buildTimeString}`
|
||||
}
|
36
utils/loadPreferredThemes.ts
Normal file
36
utils/loadPreferredThemes.ts
Normal file
|
@ -0,0 +1,36 @@
|
|||
let styleLinkElement: HTMLLinkElement | null;
|
||||
let layoutLinkElement: HTMLLinkElement | null;
|
||||
|
||||
|
||||
export default function loadPreferredThemes() {
|
||||
const runtimeConfig = useRuntimeConfig()
|
||||
const baseURL = runtimeConfig.app.baseURL;
|
||||
|
||||
const currentStyle = settingsLoad().selectedThemeStyle ?? `${baseURL}themes/style/dark.css`
|
||||
const currentLayout = settingsLoad().selectedThemeLayout ?? `${baseURL}themes/layout/gorb.css`
|
||||
|
||||
if (styleLinkElement) {
|
||||
styleLinkElement.href = currentStyle;
|
||||
} else {
|
||||
createStyleHead("style-theme", currentStyle)
|
||||
styleLinkElement = document.getElementById('style-theme') as HTMLLinkElement;
|
||||
}
|
||||
|
||||
if (layoutLinkElement) {
|
||||
layoutLinkElement.href = currentLayout;
|
||||
} else {
|
||||
createStyleHead("style-layout", currentLayout)
|
||||
layoutLinkElement = document.getElementById('style-layout') as HTMLLinkElement;
|
||||
}
|
||||
}
|
||||
|
||||
// create a new theme link if one doesn't already exist
|
||||
function createStyleHead(id: string, themeUrl: string) {
|
||||
useHead({
|
||||
link: [{
|
||||
id: id,
|
||||
rel: "stylesheet",
|
||||
href: themeUrl
|
||||
}]
|
||||
})
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue