mirror of
https://github.com/bastienwirtz/homer.git
synced 2026-07-25 08:54:09 +00:00
Merge 7c555fa2d7 into 6972395b62
This commit is contained in:
commit
265eadc994
8 changed files with 208 additions and 13 deletions
|
|
@ -82,6 +82,14 @@
|
|||
"dark"
|
||||
],
|
||||
"description": "One of 'auto', 'light', or 'dark'"
|
||||
},
|
||||
"density": {
|
||||
"enum": [
|
||||
"normal",
|
||||
"compact",
|
||||
"ultracompact"
|
||||
],
|
||||
"description": "Density of the dashboard, one of 'normal', 'compact', or 'ultracompact'"
|
||||
}
|
||||
},
|
||||
"title": "Defaults"
|
||||
|
|
|
|||
19
src/App.vue
19
src/App.vue
|
|
@ -44,6 +44,12 @@
|
|||
@updated="vlayout = $event"
|
||||
/>
|
||||
|
||||
<DensityToggle
|
||||
name="homer-density"
|
||||
:default-value="configDensity"
|
||||
@updated="onDensityUpdated"
|
||||
/>
|
||||
|
||||
<SearchInput
|
||||
class="navbar-item is-inline-block-mobile"
|
||||
:hotkey="searchHotkey()"
|
||||
|
|
@ -114,6 +120,7 @@ import SearchInput from "./components/SearchInput.vue";
|
|||
import SettingToggle from "./components/SettingToggle.vue";
|
||||
import DarkMode from "./components/DarkMode.vue";
|
||||
import DynamicTheme from "./components/DynamicTheme.vue";
|
||||
import DensityToggle from "./components/DensityToggle.vue";
|
||||
|
||||
import defaultConfig from "./assets/defaults.yml?raw";
|
||||
|
||||
|
|
@ -129,6 +136,7 @@ export default {
|
|||
SettingToggle,
|
||||
DarkMode,
|
||||
DynamicTheme,
|
||||
DensityToggle,
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
|
|
@ -153,6 +161,14 @@ export default {
|
|||
configurationNeeded: function () {
|
||||
return (this.loaded && !this.services) || this.configNotFound;
|
||||
},
|
||||
configDensity: function() {
|
||||
// Lese die Density aus der Config, Fallback auf "normal"
|
||||
const density = this.config?.defaults?.density || "normal";
|
||||
|
||||
// Validiere den Wert
|
||||
const validDensities = ["normal", "compact", "ultracompact"];
|
||||
return validDensities.includes(density) ? density : "normal";
|
||||
},
|
||||
},
|
||||
created: async function () {
|
||||
this.buildDashboard();
|
||||
|
|
@ -189,6 +205,9 @@ export default {
|
|||
this.config = merge(defaults, config);
|
||||
this.services = this.config.services;
|
||||
|
||||
const savedDensity = localStorage.getItem("homer-density") || localStorage["homer-density"];
|
||||
document.documentElement.setAttribute("data-density", savedDensity);
|
||||
|
||||
document.title =
|
||||
this.config.documentTitle ||
|
||||
[this.config.title, this.config.subtitle].filter(Boolean).join(" | ");
|
||||
|
|
|
|||
|
|
@ -9,6 +9,54 @@
|
|||
}
|
||||
|
||||
@layer base {
|
||||
/* =========================================================
|
||||
Normal (current Style)
|
||||
========================================================= */
|
||||
|
||||
html[data-density='normal'] {
|
||||
--density-card-height: 85px;
|
||||
--density-card-padding: 1.3rem;
|
||||
--density-title-size: 1.1em;
|
||||
--density-subtitle-size: 0.9em;
|
||||
--density-group-spacing: 1.2rem;
|
||||
--density-column-gap: 0.75rem;
|
||||
--density-tag-offset: 1rem;
|
||||
--density-icon-size: 32px;
|
||||
--density-icon-box: 48px;
|
||||
}
|
||||
|
||||
/* =========================================================
|
||||
Compact
|
||||
========================================================= */
|
||||
|
||||
html[data-density='compact'] {
|
||||
--density-card-height: 63.75px;
|
||||
--density-card-padding: 0.9rem;
|
||||
--density-title-size: 0.825em;
|
||||
--density-subtitle-size: 0.675em;
|
||||
--density-group-spacing: 0.9rem;
|
||||
--density-column-gap: 0.5625rem;
|
||||
--density-tag-offset: 0.75rem;
|
||||
--density-icon-size: 24px;
|
||||
--density-icon-box: 36px;
|
||||
}
|
||||
|
||||
/* =========================================================
|
||||
Ultra Compact
|
||||
========================================================= */
|
||||
|
||||
html[data-density='ultracompact'] {
|
||||
--density-card-height: 42.5px;
|
||||
--density-card-padding: 0.65rem;
|
||||
--density-title-size: 0.55em;
|
||||
--density-subtitle-size: 0.45em;
|
||||
--density-group-spacing: 0.6rem;
|
||||
--density-column-gap: 0.375rem;
|
||||
--density-tag-offset: 0.5rem;
|
||||
--density-icon-size: 16px;
|
||||
--density-icon-box: 24px;
|
||||
}
|
||||
|
||||
html,
|
||||
body,
|
||||
body #app-mount,
|
||||
|
|
@ -35,6 +83,7 @@
|
|||
|
||||
a {
|
||||
color: var(--link);
|
||||
|
||||
&:hover {
|
||||
color: var(--link-hover);
|
||||
}
|
||||
|
|
@ -44,6 +93,7 @@
|
|||
font-weight: 500;
|
||||
color: var(--text-title);
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
color: var(--text-subtitle);
|
||||
}
|
||||
|
|
@ -51,10 +101,15 @@
|
|||
.card {
|
||||
background-color: var(--card-background);
|
||||
box-shadow: 0 2px 15px 0 var(--card-shadow);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--card-background);
|
||||
}
|
||||
|
||||
transition: all 0.2s ease-in-out;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.component-error .card {
|
||||
border: 1px solid rgba(255, 33, 33, 0.664);
|
||||
background-color: rgba(255, 58, 58, 0.24);
|
||||
|
|
@ -128,6 +183,7 @@
|
|||
|
||||
.logo {
|
||||
float: left;
|
||||
|
||||
i {
|
||||
vertical-align: top;
|
||||
padding: 8px 15px;
|
||||
|
|
@ -141,22 +197,26 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.navbar {
|
||||
background-color: var(--highlight-secondary);
|
||||
|
||||
a {
|
||||
color: var(--text-header);
|
||||
padding: 8px 12px;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
color: var(--text-header);
|
||||
background-color: var(--highlight-hover);
|
||||
}
|
||||
}
|
||||
|
||||
.navbar-menu {
|
||||
background-color: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
.navbar-end {
|
||||
text-align: right;
|
||||
}
|
||||
|
|
@ -168,13 +228,14 @@
|
|||
h2 {
|
||||
padding-bottom: 0px;
|
||||
@include ellipsis();
|
||||
|
||||
i {
|
||||
color: var(--highlight-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 1.1em;
|
||||
font-size: var(--density-title-size);
|
||||
line-height: 1.3em;
|
||||
font-weight: 500;
|
||||
margin-bottom: 3px;
|
||||
|
|
@ -182,13 +243,13 @@
|
|||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 0.9em;
|
||||
font-size: var(--density-subtitle-size);
|
||||
font-weight: 300;
|
||||
@include ellipsis();
|
||||
}
|
||||
|
||||
.container {
|
||||
padding: 1.2rem 0.75rem;
|
||||
padding: var(--density-group-spacing) var(--density-column-gap);
|
||||
}
|
||||
|
||||
.message {
|
||||
|
|
@ -221,7 +282,7 @@
|
|||
.tag {
|
||||
color: #ffffff;
|
||||
position: absolute;
|
||||
bottom: 1rem;
|
||||
bottom: var(--density-tag-offset);
|
||||
right: -0.2rem;
|
||||
width: 3px;
|
||||
overflow: hidden;
|
||||
|
|
@ -263,13 +324,13 @@
|
|||
}
|
||||
|
||||
.card-content {
|
||||
height: 85px;
|
||||
padding: 1.3rem;
|
||||
height: var(--density-card-height);
|
||||
padding: var(--density-card-padding);
|
||||
}
|
||||
|
||||
.layout-vertical {
|
||||
h2.group-title {
|
||||
padding-bottom: 0.75rem;
|
||||
padding-bottom: var(--density-group-spacing);
|
||||
}
|
||||
|
||||
.card {
|
||||
|
|
@ -313,6 +374,7 @@
|
|||
.search-bar {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
|
||||
input {
|
||||
border: none;
|
||||
background-color: var(--highlight-hover);
|
||||
|
|
@ -369,4 +431,4 @@
|
|||
.group-logo {
|
||||
float: left;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -15,6 +15,8 @@ defaults:
|
|||
layout: columns
|
||||
# auto, light, dark
|
||||
colorTheme: auto
|
||||
# normal, compact, ultracompact
|
||||
density: normal
|
||||
|
||||
theme: default
|
||||
colors: ~
|
||||
|
|
|
|||
92
src/components/DensityToggle.vue
Normal file
92
src/components/DensityToggle.vue
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
<template>
|
||||
<a
|
||||
class="navbar-item is-inline-block-mobile"
|
||||
@click.prevent="cycleDensity()"
|
||||
:title="densityLabel"
|
||||
>
|
||||
<span>
|
||||
<i :class="['fas', 'fa-fw', densityIcon]"></i>
|
||||
</span>
|
||||
</a>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "DensityToggle",
|
||||
props: {
|
||||
name: {
|
||||
type: String,
|
||||
default: "density",
|
||||
},
|
||||
defaultValue: {
|
||||
type: String,
|
||||
default: "normal",
|
||||
validator: (value) => ["normal", "compact", "ultracompact"].includes(value),
|
||||
},
|
||||
},
|
||||
emits: ["updated"],
|
||||
data() {
|
||||
return {
|
||||
densities: ["normal", "compact", "ultracompact"],
|
||||
currentDensity: "normal",
|
||||
icons: {
|
||||
normal: "fa-th-large",
|
||||
compact: "fa-th",
|
||||
ultracompact: "fa-bars",
|
||||
},
|
||||
labels: {
|
||||
normal: "Normal density",
|
||||
compact: "Compact density",
|
||||
ultracompact: "Ultra compact density",
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
densityIcon() {
|
||||
return this.icons[this.currentDensity] || "fa-th-large";
|
||||
},
|
||||
densityLabel() {
|
||||
return this.labels[this.currentDensity] || "Normal density";
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.initializeDensity();
|
||||
},
|
||||
mounted() {
|
||||
this.applyDensity(this.currentDensity);
|
||||
},
|
||||
methods: {
|
||||
initializeDensity() {
|
||||
let density = this.defaultValue;
|
||||
|
||||
// Priority: localStorage > defaultValue (from config.yml)
|
||||
if (this.name && localStorage[this.name]) {
|
||||
const stored = localStorage[this.name];
|
||||
if (this.densities.includes(stored)) {
|
||||
density = stored;
|
||||
}
|
||||
}
|
||||
|
||||
this.currentDensity = density;
|
||||
console.log(`[DensityToggle] Initialized with: ${this.currentDensity}`);
|
||||
},
|
||||
applyDensity(density) {
|
||||
document.documentElement.setAttribute("data-density", density);
|
||||
this.$emit("updated", density);
|
||||
},
|
||||
cycleDensity() {
|
||||
const currentIndex = this.densities.indexOf(this.currentDensity);
|
||||
const nextIndex = (currentIndex + 1) % this.densities.length;
|
||||
const newDensity = this.densities[nextIndex];
|
||||
|
||||
this.currentDensity = newDensity;
|
||||
|
||||
if (this.name) {
|
||||
localStorage[this.name] = newDensity;
|
||||
}
|
||||
|
||||
this.applyDensity(newDensity);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
@ -2,7 +2,10 @@
|
|||
<h2 :class="group.class">
|
||||
<i v-if="group.icon" :class="['fa-fw', group.icon]"></i>
|
||||
<div v-else-if="group.logo" class="group-logo media-left">
|
||||
<figure class="image is-48x48">
|
||||
<figure
|
||||
class="image"
|
||||
style="width: var(--density-icon-box); height: var(--density-icon-box);"
|
||||
>
|
||||
<img :src="group.logo" :alt="`${group.name} logo`" />
|
||||
</figure>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -6,13 +6,22 @@
|
|||
<div :class="mediaClass">
|
||||
<slot name="icon">
|
||||
<div v-if="item.logo" class="media-left">
|
||||
<figure class="image is-48x48">
|
||||
<figure
|
||||
class="image"
|
||||
style="width: var(--density-icon-box); height: var(--density-icon-box);"
|
||||
>
|
||||
<img :src="item.logo" :alt="`${item.name} logo`" />
|
||||
</figure>
|
||||
</div>
|
||||
<div v-if="item.icon" class="media-left">
|
||||
<figure class="image is-48x48">
|
||||
<i style="font-size: 32px" :class="['fa-fw', item.icon]"></i>
|
||||
<figure
|
||||
class="image"
|
||||
style="width: var(--density-icon-box); height: var(--density-icon-box);"
|
||||
>
|
||||
<i
|
||||
style="font-size: var(--density-icon-size)"
|
||||
:class="['fa-fw', item.icon]"
|
||||
></i>
|
||||
</figure>
|
||||
</div>
|
||||
</slot>
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
<div class="card-content">
|
||||
<div class="media">
|
||||
<div v-if="icon" class="media-left" :class="item.background">
|
||||
<figure class="image is-48x48">
|
||||
<figure class="image" style="width: var(--density-icon-box); height: var(--density-icon-box);">
|
||||
<img
|
||||
:src="`https://openweathermap.org/img/wn/${icon}@2x.png`"
|
||||
:alt="conditions"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue