| <script lang="ts">
|
| import {
|
| WALLPAPER_BANNER,
|
| WALLPAPER_NONE,
|
| WALLPAPER_OVERLAY,
|
| } from "@constants/constants";
|
| import I18nKey from "@i18n/i18nKey";
|
| import { i18n } from "@i18n/translation";
|
| import {
|
| getDefaultHue,
|
| getHue,
|
| getStoredWallpaperMode,
|
| setHue,
|
| setWallpaperMode,
|
| } from "@utils/setting-utils";
|
| import { onMount } from "svelte";
|
| import Icon from "@/components/common/Icon.svelte";
|
| import { backgroundWallpaper, siteConfig } from "@/config";
|
| import type { WALLPAPER_MODE } from "@/types/config";
|
|
|
| let hue = $state(getHue());
|
| const defaultHue = getDefaultHue();
|
| let wallpaperMode: WALLPAPER_MODE = $state(backgroundWallpaper.mode);
|
| const defaultWallpaperMode = backgroundWallpaper.mode;
|
| let currentLayout: "list" | "grid" = $state("list");
|
| const defaultLayout = siteConfig.postListLayout.defaultMode;
|
| let mounted = $state(false);
|
| let isSmallScreen = $state(
|
| typeof window !== "undefined" ? window.innerWidth < 1200 : false,
|
| );
|
| let isSwitching = $state(false);
|
|
|
| const isWallpaperSwitchable = backgroundWallpaper.switchable ?? true;
|
| const allowLayoutSwitch = siteConfig.postListLayout.allowSwitch;
|
| const showThemeColor = !siteConfig.themeColor.fixed;
|
| const hasAnyContent =
|
| showThemeColor || isWallpaperSwitchable || allowLayoutSwitch;
|
|
|
| function resetHue() {
|
| hue = getDefaultHue();
|
| }
|
|
|
| function resetWallpaperMode() {
|
| wallpaperMode = defaultWallpaperMode;
|
| setWallpaperMode(defaultWallpaperMode);
|
| }
|
|
|
| function resetLayout() {
|
| currentLayout = defaultLayout;
|
| localStorage.setItem("postListLayout", defaultLayout);
|
|
|
|
|
| const event = new CustomEvent("layoutChange", {
|
| detail: { layout: defaultLayout },
|
| });
|
| window.dispatchEvent(event);
|
| }
|
|
|
| function switchWallpaperMode(newMode: WALLPAPER_MODE) {
|
| wallpaperMode = newMode;
|
| setWallpaperMode(newMode);
|
| }
|
|
|
| function checkScreenSize() {
|
| isSmallScreen = window.innerWidth < 1200;
|
| if (isSmallScreen) {
|
| currentLayout = "list";
|
| }
|
| }
|
|
|
| function switchLayout() {
|
| if (!mounted || isSmallScreen || isSwitching) return;
|
|
|
| isSwitching = true;
|
| currentLayout = currentLayout === "list" ? "grid" : "list";
|
| localStorage.setItem("postListLayout", currentLayout);
|
|
|
|
|
| const event = new CustomEvent("layoutChange", {
|
| detail: { layout: currentLayout },
|
| });
|
| window.dispatchEvent(event);
|
|
|
|
|
| setTimeout(() => {
|
| isSwitching = false;
|
| }, 500);
|
| }
|
|
|
| onMount(() => {
|
| mounted = true;
|
| checkScreenSize();
|
|
|
|
|
| wallpaperMode = getStoredWallpaperMode();
|
|
|
|
|
| const savedLayout = localStorage.getItem("postListLayout");
|
| if (savedLayout && (savedLayout === "list" || savedLayout === "grid")) {
|
| currentLayout = savedLayout;
|
| } else {
|
| currentLayout = siteConfig.postListLayout.defaultMode;
|
| }
|
|
|
|
|
| window.addEventListener("resize", checkScreenSize);
|
|
|
| return () => {
|
| window.removeEventListener("resize", checkScreenSize);
|
| };
|
| });
|
|
|
|
|
| onMount(() => {
|
| const handleCustomEvent = (event: Event) => {
|
| const customEvent = event as CustomEvent<{ layout: "list" | "grid" }>;
|
| currentLayout = customEvent.detail.layout;
|
| };
|
|
|
| window.addEventListener("layoutChange", handleCustomEvent);
|
|
|
| return () => {
|
| window.removeEventListener("layoutChange", handleCustomEvent);
|
| };
|
| });
|
|
|
| $effect(() => {
|
| if (hue || hue === 0) {
|
| setHue(hue);
|
| }
|
| });
|
| </script>
|
|
|
| {#if hasAnyContent}
|
| <div id="display-setting" class="float-panel float-panel-closed absolute transition-all w-80 right-4 px-4 py-2">
|
|
|
| {#if showThemeColor}
|
| <div class="mt-2 mb-2">
|
| <div class="flex flex-row gap-2 mb-2 items-center justify-between">
|
| <div class="flex gap-2 font-bold text-lg text-neutral-900 dark:text-neutral-100 transition relative ml-3
|
| before:w-1 before:h-4 before:rounded-md before:bg-(--primary)
|
| before:absolute before:-left-3 before:top-1/2 before:-translate-y-1/2"
|
| >
|
| {i18n(I18nKey.themeColor)}
|
| <button aria-label="Reset to Default" class="btn-regular w-7 h-7 rounded-md active:scale-90"
|
| class:opacity-0={hue === defaultHue} class:pointer-events-none={hue === defaultHue} onclick={resetHue}>
|
| <div class="text-(--btn-content)">
|
| <Icon icon="fa7-solid:arrow-rotate-left" class="text-[0.875rem]"></Icon>
|
| </div>
|
| </button>
|
| </div>
|
| <div class="flex gap-1">
|
| <div id="hueValue" class="transition bg-(--btn-regular-bg) w-10 h-7 rounded-md flex justify-center
|
| font-bold text-sm items-center text-(--btn-content)">
|
| {hue}
|
| </div>
|
| </div>
|
| </div>
|
| <div class="w-full h-6 px-1 bg-[oklch(0.80_0.10_0)] dark:bg-[oklch(0.70_0.10_0)] rounded select-none">
|
| <input aria-label={i18n(I18nKey.themeColor)} type="range" min="0" max="360" bind:value={hue}
|
| class="slider" id="colorSlider" step="5" style="width: 100%">
|
| </div>
|
| </div>
|
| {/if}
|
|
|
|
|
| {#if isWallpaperSwitchable}
|
| <div class="mt-2 mb-2">
|
| <div class="flex gap-2 font-bold text-lg text-neutral-900 dark:text-neutral-100 transition relative ml-3 mb-2
|
| before:w-1 before:h-4 before:rounded-md before:bg-(--primary)
|
| before:absolute before:-left-3 before:top-1/2 before:-translate-y-1/2"
|
| >
|
| {i18n(I18nKey.wallpaperMode)}
|
| <button aria-label="Reset to Default" class="btn-regular w-7 h-7 rounded-md active:scale-90"
|
| class:opacity-0={wallpaperMode === defaultWallpaperMode} class:pointer-events-none={wallpaperMode === defaultWallpaperMode} onclick={resetWallpaperMode}>
|
| <div class="text-(--btn-content)">
|
| <Icon icon="fa7-solid:arrow-rotate-left" class="text-[0.875rem]"></Icon>
|
| </div>
|
| </button>
|
| </div>
|
| <div class="space-y-1 px-1">
|
| <button
|
| class="w-full btn-regular rounded-md py-2 px-3 flex items-center gap-3 text-left active:scale-95 transition-all relative overflow-hidden"
|
| class:opacity-60={wallpaperMode !== WALLPAPER_BANNER}
|
| class:bg-(--btn-regular-bg-hover)={wallpaperMode === WALLPAPER_BANNER}
|
| onclick={() => switchWallpaperMode(WALLPAPER_BANNER)}
|
| >
|
| <Icon icon="material-symbols:image-outline" class="text-[1.25rem] shrink-0"></Icon>
|
| <span class="text-sm flex-1">{i18n(I18nKey.wallpaperBannerMode)}</span>
|
| {#if wallpaperMode === WALLPAPER_BANNER}
|
| <Icon icon="material-symbols:check-circle" class="text-[1rem] shrink-0 text-(--primary)"></Icon>
|
| {/if}
|
| </button>
|
| <button
|
| class="w-full btn-regular rounded-md py-2 px-3 flex items-center gap-3 text-left active:scale-95 transition-all relative overflow-hidden"
|
| class:opacity-60={wallpaperMode !== WALLPAPER_OVERLAY}
|
| class:bg-(--btn-regular-bg-hover)={wallpaperMode === WALLPAPER_OVERLAY}
|
| onclick={() => switchWallpaperMode(WALLPAPER_OVERLAY)}
|
| >
|
| <Icon icon="material-symbols:wallpaper" class="text-[1.25rem] shrink-0"></Icon>
|
| <span class="text-sm flex-1">{i18n(I18nKey.wallpaperOverlayMode)}</span>
|
| {#if wallpaperMode === WALLPAPER_OVERLAY}
|
| <Icon icon="material-symbols:check-circle" class="text-[1rem] shrink-0 text-(--primary)"></Icon>
|
| {/if}
|
| </button>
|
| <button
|
| class="w-full btn-regular rounded-md py-2 px-3 flex items-center gap-3 text-left active:scale-95 transition-all relative overflow-hidden"
|
| class:opacity-60={wallpaperMode !== WALLPAPER_NONE}
|
| class:bg-(--btn-regular-bg-hover)={wallpaperMode === WALLPAPER_NONE}
|
| onclick={() => switchWallpaperMode(WALLPAPER_NONE)}
|
| >
|
| <Icon icon="material-symbols:hide-image-outline" class="text-[1.25rem] shrink-0"></Icon>
|
| <span class="text-sm flex-1">{i18n(I18nKey.wallpaperNoneMode)}</span>
|
| {#if wallpaperMode === WALLPAPER_NONE}
|
| <Icon icon="material-symbols:check-circle" class="text-[1rem] shrink-0 text-(--primary)"></Icon>
|
| {/if}
|
| </button>
|
| </div>
|
| </div>
|
| {/if}
|
|
|
|
|
| {#if allowLayoutSwitch && !isSmallScreen}
|
| <div class="mt-2 mb-2">
|
| <div class="flex gap-2 font-bold text-lg text-neutral-900 dark:text-neutral-100 transition relative ml-3 mb-2
|
| before:w-1 before:h-4 before:rounded-md before:bg-(--primary)
|
| before:absolute before:-left-3 before:top-1/2 before:-translate-y-1/2"
|
| >
|
| {i18n(I18nKey.postListLayout)}
|
| <button aria-label="Reset to Default" class="btn-regular w-7 h-7 rounded-md active:scale-90"
|
| class:opacity-0={currentLayout === defaultLayout} class:pointer-events-none={currentLayout === defaultLayout} onclick={resetLayout}>
|
| <div class="text-(--btn-content)">
|
| <Icon icon="fa7-solid:arrow-rotate-left" class="text-[0.875rem]"></Icon>
|
| </div>
|
| </button>
|
| </div>
|
| <div class="flex gap-2">
|
| <button
|
| aria-label={i18n(I18nKey.postListLayoutList)}
|
| class="flex-1 btn-regular rounded-md py-2 px-3 flex items-center justify-center gap-2 active:scale-95 transition-all relative overflow-hidden"
|
| class:opacity-60={currentLayout !== 'list'}
|
| class:bg-(--btn-regular-bg-hover)={currentLayout === 'list'}
|
| disabled={isSwitching}
|
| onclick={switchLayout}
|
| title={i18n(I18nKey.postListLayoutList)}
|
| >
|
| <svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24">
|
| <path d="M4 6h16v2H4zm0 5h16v2H4zm0 5h16v2H4z"/>
|
| </svg>
|
| <span class="text-xs font-medium">{i18n(I18nKey.postListLayoutList)}</span>
|
| {#if currentLayout === 'list'}
|
| <Icon icon="material-symbols:check-circle" class="text-[1rem] shrink-0 text-(--primary)"></Icon>
|
| {/if}
|
| </button>
|
| <button
|
| aria-label={i18n(I18nKey.postListLayoutGrid)}
|
| class="flex-1 btn-regular rounded-md py-2 px-3 flex items-center justify-center gap-2 active:scale-95 transition-all relative overflow-hidden"
|
| class:opacity-60={currentLayout !== 'grid'}
|
| class:bg-(--btn-regular-bg-hover)={currentLayout === 'grid'}
|
| disabled={isSwitching}
|
| onclick={switchLayout}
|
| title={i18n(I18nKey.postListLayoutGrid)}
|
| >
|
| <svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24">
|
| <path d="M3 3h7v7H3V3zm0 11h7v7H3v-7zm11-11h7v7h-7V3zm0 11h7v7h-7v-7z"/>
|
| </svg>
|
| <span class="text-xs font-medium">{i18n(I18nKey.postListLayoutGrid)}</span>
|
| {#if currentLayout === 'grid'}
|
| <Icon icon="material-symbols:check-circle" class="text-[1rem] shrink-0 text-(--primary)"></Icon>
|
| {/if}
|
| </button>
|
| </div>
|
| </div>
|
| {/if}
|
| </div>
|
| {/if}
|
|
|
|
|
| <style lang="stylus">
|
| #display-setting
|
| input[type="range"]
|
| -webkit-appearance none
|
| height 1.5rem
|
| background-image var(--color-selection-bar)
|
| transition background-image 0.15s ease-in-out
|
|
|
|
|
| &::-webkit-slider-thumb
|
| -webkit-appearance none
|
| height 1rem
|
| width 0.5rem
|
| border-radius 0.125rem
|
| background rgba(255, 255, 255, 0.7)
|
| box-shadow none
|
| &:hover
|
| background rgba(255, 255, 255, 0.8)
|
| &:active
|
| background rgba(255, 255, 255, 0.6)
|
|
|
| &::-moz-range-thumb
|
| -webkit-appearance none
|
| height 1rem
|
| width 0.5rem
|
| border-radius 0.125rem
|
| border-width 0
|
| background rgba(255, 255, 255, 0.7)
|
| box-shadow none
|
| &:hover
|
| background rgba(255, 255, 255, 0.8)
|
| &:active
|
| background rgba(255, 255, 255, 0.6)
|
|
|
| &::-ms-thumb
|
| -webkit-appearance none
|
| height 1rem
|
| width 0.5rem
|
| border-radius 0.125rem
|
| background rgba(255, 255, 255, 0.7)
|
| box-shadow none
|
| &:hover
|
| background rgba(255, 255, 255, 0.8)
|
| &:active
|
| background rgba(255, 255, 255, 0.6)
|
|
|
| </style>
|
|
|