Makes the navbar and layout clicks

This commit is contained in:
2023-08-14 21:38:34 +02:00
parent 3cc52e37b2
commit 28e118c5ec
14 changed files with 270 additions and 130 deletions

View File

@@ -12,21 +12,21 @@ body {
background-position: center;
color: var(--text);
transition: background-color cubic-bezier(0.165, 0.84, 0.44, 1) 300ms;
}
a {
color: var(--link);
&:hover {
color: var(--link-hover);
}
a {
color: var(--link);
&:hover {
color: var(--link-hover);
}
}
.title {
color: var(--text-title);
}
.title {
color: var(--text-title);
}
.subtitle {
color: var(--text-subtitle);
}
.subtitle {
color: var(--text-subtitle);
}
h1 {
@@ -37,69 +37,50 @@ h2 {
font-size: 1.7rem;
margin-top: 2rem;
margin-bottom: 1rem;
.fas,
.fab,
.far {
margin-right: 10px;
}
span {
font-weight: bold;
color: var(--highlight-secondary);
}
}
header {
color: var(--text-header);
.dashboard-title {
padding-left: 16px;
}
.first-line {
background-color: var(--highlight-primary);
.container {
padding-top: 24px;
display: flex;
flex-flow: row wrap;
align-items: center;
gap: 16px;
}
min-height: 100px;
background-color: var(--highlight-primary);
min-height: 104px;
h1 {
margin-top: -12px;
font-size: 2rem;
}
.headline {
font-size: 0.9rem;
height: min-content;
}
.container {
min-height: 80px;
padding: 10px 0;
}
.logo {
i {
vertical-align: top;
padding: 8px 15px;
font-size: 48px;
width: 48px;
height: 48px;
}
.dashboard-title {
display: flex;
flex-flow: column wrap;
justify-content: space-between;
height: 48px;
h2 {
font-size: 0.9em;
line-height: 1em;
}
img {
max-height: 70px;
max-width: 70px;
h1 {
font-size: 2em;
line-height: 1em;
}
}
}
.navbar {
nav.navbar {
background-color: var(--highlight-secondary);
a {
color: var(--text-header);
padding: 8px 12px;
&:hover,
&:focus {
@@ -108,12 +89,62 @@ header {
}
}
.navbar-item {
.link-label {
margin-left: 8px;
}
padding: 8px 16px;
}
.navbar-item:first-child {
margin-left: -16px;
}
.navbar-item input {
width: 160px;
transition: width cubic-bezier(0.165, 0.84, 0.44, 1) 300ms;
}
.navbar-item input:focus {
width: 240px;
}
.navbar-menu {
background-color: inherit;
}
}
}
.navbar-end {
text-align: right;
main {
.layout-column {
display: grid;
grid-template-columns: var(--layout-template);
gap: 32px;
}
.layout-row {
display: flex;
flex-flow: column nowrap;
gap: 32px;
}
.service-group {
background: lime;
}
.service-group.layout-column {
display: flex;
flex-flow: column nowrap;
gap: 16px;
}
.service-group.layout-row {
display: grid;
grid-template-columns: var(--layout-template);
gap: 16px;
}
.service-card {
background: orange;
}
}

View File

@@ -4,9 +4,13 @@ logo: 'icon-any.svg'
icon: 'fa fa-cloud'
asmask: true
colors:
dark:
test: red
links:
- title: 'Cloud'
icon: 'fa fa-cloud'
url: 'https://example.com'
- title: 'Hard drive'
icon: 'fa fa-hard-drive'
url: 'https://example.com'
services:
- title: '/Cloud'
@@ -29,3 +33,24 @@ services:
url: '/pihole'
type: 'pihole'
keywords: 'cloud storage files'
- title: '/Cloud'
subtitle: 'Private Cloud Utilities'
icon: 'fas fa-cloud'
items:
- title: 'NAS'
subtitle: 'Network Attached Storage'
icon: 'fas fa-hard-drive'
target: '_blank'
url: '/NAS'
type: prowlarr
keywords: 'cloud storage files'
- title: 'PiHole'
subtitle: 'A DNS Hole'
icon: 'fas fa-hard-drive'
target: '_blank'
url: '/pihole'
type: 'pihole'
keywords: 'cloud storage files'

View File

@@ -2,7 +2,6 @@
import type { BrandConfig } from '$lib/config';
export let brand: BrandConfig = {};
export let size = 48;
export let color = 'var(--text)';
console.log(brand);
@@ -11,11 +10,7 @@
}
</script>
<div
class="container"
style="--size: {size}px; --mask: url({brand.logo || ''}); --mask-color: {color}"
class:masked={masked()}
>
<div style="--mask: url({brand.logo || ''}); --mask-color: {color}" class:masked={masked()}>
{#if brand.logo}
{#if brand.asmask != true}
<img src={brand.logo} alt="logo" />
@@ -26,25 +21,21 @@
</div>
<style>
.container {
width: var(--size);
min-width: min-content;
height: var(--size);
div {
width: 100%;
height: 100%;
color: var(--mask-color);
}
img {
width: 100%;
}
.masked {
background: var(--mask-color);
mask: var(--mask) center no-repeat;
mask-size: var(--size);
mask-size: 100%;
-webkit-mask: var(--mask) center no-repeat;
-webkit-mask-size: var(--size);
}
div {
display: flex;
flex-flow: row wrap;
align-items: center;
justify-content: center;
-webkit-mask-size: 100%;
}
</style>

View File

@@ -0,0 +1,54 @@
<script lang="ts">
import type { LinkConfig, SectionConfig } from '$lib/config';
import Brand from './Brand.svelte';
import LayoutButton from './LayoutButton.svelte';
import ThemeVariantButton from './ThemeVariantButton.svelte';
export let section: SectionConfig;
export let links: Array<LinkConfig>;
</script>
<header>
<section class="first-line">
<div class="container">
<div class="logo">
<a href="/">
<Brand brand={section} color="var(--text-header)" />
</a>
</div>
<div class="dashboard-title">
{#if section.subtitle}
<h2>{section.subtitle}</h2>
{/if}
<h1>{section.title}</h1>
</div>
</div>
</section>
<nav class="navbar" aria-label="main navigation">
<div class="container">
<div class="navbar-start">
{#each links as link}
<a class="navbar-item" href={link.url}>
{#if link.icon}
<i class={link.icon} />
{/if}
<span class="link-label">{link.title}</span>
</a>
{/each}
</div>
<div class="navbar-end">
<ThemeVariantButton class="navbar-item" />
<LayoutButton class="navbar-item" />
<div class="navbar-item">
<div class="control has-icons-left">
<input class="input" type="text" placeholder="Filter" />
<span class="icon is-small is-left">
<i class="fa fa-search" />
</span>
</div>
</div>
</div>
</div>
</nav>
</header>

View File

@@ -0,0 +1,19 @@
<script lang="ts">
import { layoutDirection } from '$lib/stores/layout';
function toggle() {
if ($layoutDirection == 'column') {
layoutDirection.set('row');
} else {
layoutDirection.set('column');
}
}
</script>
<a on:click={toggle} class={$$restProps.class || ''}>
{#if $layoutDirection == 'column'}
<i class="fa fa-table-columns" />
{:else}
<i class="fa fa-list-ul" />
{/if}
</a>

View File

@@ -8,7 +8,9 @@
const component = getServiceComponent(service.type || '');
</script>
<p>{service.title}</p>
{#if component != undefined}
<svelte:component this={component} {data} />
{/if}
<div class="service-card">
<p>{service.title}</p>
{#if component != undefined}
<svelte:component this={component} {data} />
{/if}
</div>

View File

@@ -1,11 +1,14 @@
<script lang="ts">
import type { ServiceGroupConfig } from '$lib/config';
import { layoutDirection } from '$lib/stores/layout';
import ServiceCard from './ServiceCard.svelte';
export let group: ServiceGroupConfig;
export let groupData: Array<unknown>;
</script>
{#each group.items as service, i}
<ServiceCard {service} data={groupData[i]} />
{/each}
<div class="service-group layout-{$layoutDirection}">
{#each group.items as service, i}
<ServiceCard {service} data={groupData[i]} />
{/each}
</div>

View File

@@ -1,5 +1,5 @@
<script lang="ts">
import { themeVariant, type ThemeVariant } from '$lib/themeVariant';
import { themeVariant, type ThemeVariant } from '$lib/stores/themeVariant';
const variants: Array<ThemeVariant> = ['default', 'dark', 'light'];
@@ -9,14 +9,12 @@
}
</script>
<a on:click={next}>
<span class="icon">
{#if $themeVariant == 'default'}
<i class="fas fa-circle-half-stroke" />
{:else if $themeVariant == 'light'}
<i class="fa-regular fa-circle" />
{:else if $themeVariant == 'dark'}
<i class="fa-solid fa-circle" />
{/if}
</span>
<a on:click={next} class={$$restProps.class || ''}>
{#if $themeVariant == 'default'}
<i class="fa fa-circle-half-stroke" />
{:else if $themeVariant == 'light'}
<i class="fa-regular fa-circle" />
{:else if $themeVariant == 'dark'}
<i class="fa-solid fa-circle" />
{/if}
</a>

View File

@@ -9,6 +9,10 @@ export interface SectionConfig extends BrandConfig {
subtitle?: string;
}
export interface LinkConfig extends SectionConfig {
url: string;
}
export interface ServiceConfig extends SectionConfig {
url: string;
target?: string;
@@ -28,10 +32,13 @@ export type ColorConfig = Record<string, string>;
export interface Config extends SectionConfig {
services: Array<ServiceGroupConfig>;
columns: number;
colors: {
light: ColorConfig;
dark: ColorConfig;
};
links: Array<LinkConfig>;
[x: string]: unknown;
}

View File

@@ -1,7 +1,13 @@
import configData from '../../config.yml';
import { readFile, watch } from 'fs/promises';
import * as yml from 'js-yaml';
import type { ColorConfig, Config, ServiceConfig, ServiceGroupConfig } from '$lib/config';
import type {
ColorConfig,
Config,
LinkConfig,
ServiceConfig,
ServiceGroupConfig
} from '$lib/config';
const requiredService: Required<ServiceConfig> = {
title: '',
@@ -23,16 +29,27 @@ const requiredServiceGroup: Required<ServiceGroupConfig> = {
items: [requiredService]
};
const requiredLink: Required<LinkConfig> = {
title: '',
subtitle: '',
logo: '',
icon: '',
asmask: false,
url: ''
};
export const requiredConfig: Required<Config> = {
title: '',
subtitle: '',
logo: '',
icon: '',
asmask: false,
columns: 3,
colors: {
light: {},
dark: {}
},
links: [requiredLink],
services: [requiredServiceGroup]
};
@@ -95,6 +112,7 @@ defaultDarkConfig['background-image'] = '';
export const defaultConfig: Config = {
title: 'Flanders',
columns: 3,
colors: {
light: defaultLightConfig,
dark: defaultDarkConfig

6
src/lib/stores/layout.ts Normal file
View File

@@ -0,0 +1,6 @@
import { persistentWritable } from '$lib/persistentStore';
import type { Writable } from 'svelte/store';
export type LayoutDirection = 'column' | 'row';
export const layoutDirection: Writable<LayoutDirection> = persistentWritable('layout', 'column');

View File

@@ -1,4 +1,4 @@
import { persistentWritable } from './persistentStore';
import { persistentWritable } from '$lib/persistentStore';
export type ThemeVariant = 'default' | 'dark' | 'light';

View File

@@ -3,7 +3,7 @@
import 'bulma/css/bulma.min.css';
import '@fortawesome/fontawesome-free/css/all.min.css';
import { themeVariant } from '$lib/themeVariant';
import { themeVariant } from '$lib/stores/themeVariant';
import { browser } from '$app/environment';
$: {
if (browser) {

View File

@@ -1,13 +1,14 @@
<script lang="ts">
import Brand from '$lib/components/Brand.svelte';
import Header from '$lib/components/Header.svelte';
import ServiceGroup from '$lib/components/ServiceGroup.svelte';
import ThemeVariantButton from '$lib/components/ThemeVariantButton.svelte';
import type { ColorConfig } from '$lib/config';
import { layoutDirection } from '$lib/stores/layout';
import type { PageData } from './$types';
export let data: PageData;
function cssVariables(colors: ColorConfig): string {
function colorVariables(colors: ColorConfig): string {
const cssVars: Array<string> = [];
for (const [key, value] of Object.entries(colors)) {
cssVars.push(`--${key}:${value}`);
@@ -15,24 +16,27 @@
return cssVars.join(';');
}
function renderColorVariables() {
function renderCssVariables() {
return `<style>
:root, body.is-light {
${cssVariables(data.config.colors.light)}
${colorVariables(data.config.colors.light)}
}
@media (prefers-color-scheme: light), (prefers-color-scheme: no-preference) {
:root, body {
${cssVariables(data.config.colors.light)}
${colorVariables(data.config.colors.light)}
}
}
:root, body.is-dark {
${cssVariables(data.config.colors.dark)}
${colorVariables(data.config.colors.dark)}
}
@media (prefers-color-scheme: dark) {
:root, body {
${cssVariables(data.config.colors.dark)}
${colorVariables(data.config.colors.dark)}
}
}
:root {
--layout-template: repeat(${data.config.columns},1fr);
}
</style>`;
}
</script>
@@ -40,35 +44,17 @@
<svelte:head>
<!-- it is safe here because config is comming from config.yml and thus is trusted. -->
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
{@html renderColorVariables()}
{@html renderCssVariables()}
</svelte:head>
<header>
<section class="first-line">
<div class="container">
<div class="logo">
<a href="/">
<Brand brand={data.config} color="var(--text-header)" />
</a>
</div>
<div class="dashboard-title">
{#if data.config.subtitle}
<span class="headline">{data.config.subtitle}</span>
{/if}
<h1>{data.config.title}</h1>
</div>
</div>
</section>
<nav class="navbar" aria-label="main navigation">
<div class="container">
<div class="navbar-start" />
<div class="navbar-end"><ThemeVariantButton /></div>
</div>
</nav>
</header>
<Header section={data.config} links={data.config.links} />
<main class="section">
{#each data.config.services as group, i}
<ServiceGroup {group} groupData={data.serviceData[i]} />
{/each}
<div class="container layout-{$layoutDirection}">
{#each data.config.services as group, i}
<ServiceGroup {group} groupData={data.serviceData[i]} />
{/each}
</div>
</main>
<footer />