mirror of
https://github.com/Lissy93/dashy.git
synced 2025-07-27 07:34:43 +02:00
🚧 Created files for Minimal view
This commit is contained in:
parent
f22b1b0d42
commit
ea33436e9a
115
src/components/MinimalView/MinimalSection.vue
Normal file
115
src/components/MinimalView/MinimalSection.vue
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
<template>
|
||||||
|
<div class="minimal-section-inner">
|
||||||
|
<Item
|
||||||
|
v-for="(item, index) in items"
|
||||||
|
:id="`${index}_${makeId(item.title)}`"
|
||||||
|
:key="`${index}_${makeId(item.title)}`"
|
||||||
|
:url="item.url"
|
||||||
|
:title="item.title"
|
||||||
|
:description="item.description"
|
||||||
|
:icon="item.icon"
|
||||||
|
:target="item.target"
|
||||||
|
:color="item.color"
|
||||||
|
:backgroundColor="item.backgroundColor"
|
||||||
|
:statusCheckUrl="item.statusCheckUrl"
|
||||||
|
:statusCheckHeaders="item.statusCheckHeaders"
|
||||||
|
:itemSize="itemSize"
|
||||||
|
:hotkey="item.hotkey"
|
||||||
|
:enableStatusCheck="shouldEnableStatusCheck(item.statusCheck)"
|
||||||
|
:statusCheckInterval="getStatusCheckInterval()"
|
||||||
|
@itemClicked="$emit('itemClicked')"
|
||||||
|
@triggerModal="triggerModal"
|
||||||
|
/>
|
||||||
|
<div ref="modalContainer"></div>
|
||||||
|
<IframeModal
|
||||||
|
:ref="`iframeModal-${groupId}`"
|
||||||
|
:name="`iframeModal-${groupId}`"
|
||||||
|
@closed="$emit('itemClicked')"
|
||||||
|
@modalChanged="modalChanged"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Item from '@/components/LinkItems/Item.vue';
|
||||||
|
import IframeModal from '@/components/LinkItems/IframeModal.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ItemGroup',
|
||||||
|
inject: ['config'],
|
||||||
|
props: {
|
||||||
|
groupId: String,
|
||||||
|
title: String,
|
||||||
|
icon: String,
|
||||||
|
displayData: Object,
|
||||||
|
items: Array,
|
||||||
|
itemSize: String,
|
||||||
|
modalOpen: Boolean,
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
Item,
|
||||||
|
IframeModal,
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
/* Returns a unique lowercase string, based on name, for section ID */
|
||||||
|
makeId(str) {
|
||||||
|
return str.replace(/\s+/g, '-').replace(/[^a-zA-Z ]/g, '').toLowerCase();
|
||||||
|
},
|
||||||
|
/* Opens the iframe modal */
|
||||||
|
triggerModal(url) {
|
||||||
|
this.$refs[`iframeModal-${this.groupId}`].show(url);
|
||||||
|
},
|
||||||
|
modalChanged(changedTo) {
|
||||||
|
this.$emit('change-modal-visibility', changedTo);
|
||||||
|
},
|
||||||
|
shouldEnableStatusCheck(itemPreference) {
|
||||||
|
const globalPreference = this.config.appConfig.statusCheck || false;
|
||||||
|
return itemPreference !== undefined ? itemPreference : globalPreference;
|
||||||
|
},
|
||||||
|
getStatusCheckInterval() {
|
||||||
|
let interval = this.config.appConfig.statusCheckInterval;
|
||||||
|
if (!interval) return 0;
|
||||||
|
if (interval > 60) interval = 60;
|
||||||
|
if (interval < 1) interval = 0;
|
||||||
|
return interval;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@import '@/styles/media-queries.scss';
|
||||||
|
@import '@/styles/style-helpers.scss';
|
||||||
|
|
||||||
|
.minimal-section-inner {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
&.item-group-grid {
|
||||||
|
display: grid;
|
||||||
|
overflow: auto;
|
||||||
|
@extend .scroll-bar;
|
||||||
|
@include phone { grid-template-columns: repeat(1, 1fr); }
|
||||||
|
@include tablet { grid-template-columns: repeat(2, 1fr); }
|
||||||
|
@include laptop { grid-template-columns: repeat(2, 1fr); }
|
||||||
|
@include monitor { grid-template-columns: repeat(3, 1fr); }
|
||||||
|
@include big-screen { grid-template-columns: repeat(4, 1fr); }
|
||||||
|
@include big-screen-up { grid-template-columns: repeat(5, 1fr); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.orientation-horizontal {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
.there-are-items {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(5, 1fr);
|
||||||
|
@include phone { grid-template-columns: repeat(2, 1fr); }
|
||||||
|
@include tablet { grid-template-columns: repeat(4, 1fr); }
|
||||||
|
@include laptop { grid-template-columns: repeat(6, 1fr); }
|
||||||
|
@include monitor { grid-template-columns: repeat(8, 1fr); }
|
||||||
|
@include big-screen { grid-template-columns: repeat(10, 1fr); }
|
||||||
|
@include big-screen-up { grid-template-columns: repeat(12, 1fr); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
@ -4,6 +4,7 @@ import Router from 'vue-router';
|
|||||||
import Home from '@/views/Home.vue';
|
import Home from '@/views/Home.vue';
|
||||||
import Login from '@/views/Login.vue';
|
import Login from '@/views/Login.vue';
|
||||||
import Workspace from '@/views/Workspace.vue';
|
import Workspace from '@/views/Workspace.vue';
|
||||||
|
import Minimal from '@/views/Minimal.vue';
|
||||||
import DownloadConfig from '@/views/DownloadConfig.vue';
|
import DownloadConfig from '@/views/DownloadConfig.vue';
|
||||||
import { isLoggedIn } from '@/utils/Auth';
|
import { isLoggedIn } from '@/utils/Auth';
|
||||||
import { config } from '@/utils/ConfigHelpers';
|
import { config } from '@/utils/ConfigHelpers';
|
||||||
@ -38,6 +39,16 @@ const router = new Router({
|
|||||||
metaTags: metaTagData,
|
metaTags: metaTagData,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/minimal',
|
||||||
|
name: 'minimal',
|
||||||
|
component: Minimal,
|
||||||
|
props: config,
|
||||||
|
meta: {
|
||||||
|
title: config.pageInfo.title || 'Dashy Start Page',
|
||||||
|
metaTags: metaTagData,
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/login',
|
path: '/login',
|
||||||
name: 'login',
|
name: 'login',
|
||||||
|
214
src/views/Minimal.vue
Normal file
214
src/views/Minimal.vue
Normal file
@ -0,0 +1,214 @@
|
|||||||
|
<template>
|
||||||
|
<div class="minimal-home" :style="getBackgroundImage()">
|
||||||
|
<!-- Main content, section for each group of items -->
|
||||||
|
<div v-if="checkTheresData(sections)"
|
||||||
|
:class="`item-group-container orientation-${layout} item-size-${itemSizeBound}`">
|
||||||
|
<MinimalSection
|
||||||
|
v-for="(section, index) in getSections(sections)"
|
||||||
|
:key="index"
|
||||||
|
:title="section.name"
|
||||||
|
:icon="section.icon || undefined"
|
||||||
|
:groupId="`section-${index}`"
|
||||||
|
:items="filterTiles(section.items)"
|
||||||
|
itemSize="small"
|
||||||
|
@itemClicked="finishedSearching()"
|
||||||
|
@change-modal-visibility="updateModalVisibility"
|
||||||
|
:class="(filterTiles(section.items).length === 0 && searchValue) ? 'no-results' : ''"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
import MinimalSection from '@/components/MinimalView/MinimalSection.vue';
|
||||||
|
import Defaults, { localStorageKeys } from '@/utils/defaults';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'home',
|
||||||
|
props: {
|
||||||
|
sections: Array, // Main site content
|
||||||
|
appConfig: Object, // Main site configuation (optional)
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
MinimalSection,
|
||||||
|
},
|
||||||
|
data: () => ({
|
||||||
|
searchValue: '',
|
||||||
|
layout: '',
|
||||||
|
itemSizeBound: '',
|
||||||
|
modalOpen: false, // When true, keybindings are disabled
|
||||||
|
}),
|
||||||
|
methods: {
|
||||||
|
/* Returns true if there is one or more sections in the config */
|
||||||
|
checkTheresData(sections) {
|
||||||
|
const localSections = localStorage[localStorageKeys.CONF_SECTIONS];
|
||||||
|
return (sections && sections.length >= 1) || (localSections && localSections.length >= 1);
|
||||||
|
},
|
||||||
|
/* Returns sections from local storage if available, otherwise uses the conf.yml */
|
||||||
|
getSections(sections) {
|
||||||
|
// If the user has stored sections in local storage, return those
|
||||||
|
const localSections = localStorage[localStorageKeys.CONF_SECTIONS];
|
||||||
|
if (localSections) {
|
||||||
|
const json = JSON.parse(localSections);
|
||||||
|
if (json.length >= 1) return json;
|
||||||
|
}
|
||||||
|
// Otherwise, return the usuall data from conf.yml
|
||||||
|
return sections;
|
||||||
|
},
|
||||||
|
/* Updates local data with search value, triggered from filter comp */
|
||||||
|
searching(searchValue) {
|
||||||
|
this.searchValue = searchValue || '';
|
||||||
|
},
|
||||||
|
/* Clears input field, once a searched item is opened */
|
||||||
|
finishedSearching() {
|
||||||
|
this.$refs.filterComp.clearFilterInput();
|
||||||
|
},
|
||||||
|
/* Extracts the site name from domain, used for the searching functionality */
|
||||||
|
getDomainFromUrl(url) {
|
||||||
|
if (!url) return '';
|
||||||
|
const urlPattern = /^(?:https?:\/\/)?(?:w{3}\.)?([a-z\d.-]+)\.(?:[a-z.]{2,10})(?:[/\w.-]*)*/;
|
||||||
|
const domainPattern = url.match(urlPattern);
|
||||||
|
return domainPattern ? domainPattern[1] : '';
|
||||||
|
},
|
||||||
|
/* Returns only the tiles that match the users search query */
|
||||||
|
filterTiles(allTiles) {
|
||||||
|
if (!allTiles) return [];
|
||||||
|
return allTiles.filter((tile) => {
|
||||||
|
const {
|
||||||
|
title, description, provider, url,
|
||||||
|
} = tile;
|
||||||
|
const searchTerm = this.searchValue.toLowerCase();
|
||||||
|
return (title && title.toLowerCase().includes(searchTerm))
|
||||||
|
|| (provider && provider.toLowerCase().includes(searchTerm))
|
||||||
|
|| (description && description.toLowerCase().includes(searchTerm))
|
||||||
|
|| this.getDomainFromUrl(url).includes(searchTerm);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
/* Update data when modal is open (so that key bindings can be disabled) */
|
||||||
|
updateModalVisibility(modalState) {
|
||||||
|
this.modalOpen = modalState;
|
||||||
|
},
|
||||||
|
/* Checks if any of the icons are Font Awesome glyphs */
|
||||||
|
checkIfFontAwesomeNeeded() {
|
||||||
|
let isNeeded = false;
|
||||||
|
if (!this.sections) return false;
|
||||||
|
this.sections.forEach((section) => {
|
||||||
|
if (section.icon && section.icon.includes('fa-')) isNeeded = true;
|
||||||
|
section.items.forEach((item) => {
|
||||||
|
if (item.icon && item.icon.includes('fa-')) isNeeded = true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return isNeeded;
|
||||||
|
},
|
||||||
|
/* Injects font-awesome's script tag, only if needed */
|
||||||
|
initiateFontAwesome() {
|
||||||
|
if (this.appConfig.enableFontAwesome || this.checkIfFontAwesomeNeeded()) {
|
||||||
|
const fontAwesomeScript = document.createElement('script');
|
||||||
|
const faKey = this.appConfig.fontAwesomeKey || Defaults.fontAwesomeKey;
|
||||||
|
fontAwesomeScript.setAttribute('src', `https://kit.fontawesome.com/${faKey}.js`);
|
||||||
|
document.head.appendChild(fontAwesomeScript);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/* Returns true if there is more than 1 sub-result visible during searching */
|
||||||
|
checkIfResults() {
|
||||||
|
if (!this.sections) return false;
|
||||||
|
else {
|
||||||
|
let itemsFound = true;
|
||||||
|
this.sections.forEach((section) => {
|
||||||
|
if (this.filterTiles(section.items).length > 0) itemsFound = false;
|
||||||
|
});
|
||||||
|
return itemsFound;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getBackgroundImage() {
|
||||||
|
if (this.appConfig && this.appConfig.backgroundImg) {
|
||||||
|
return `background: url('${this.appConfig.backgroundImg}');background-size:cover;`;
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.initiateFontAwesome();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import '@/styles/media-queries.scss';
|
||||||
|
@import '@/styles/style-helpers.scss';
|
||||||
|
|
||||||
|
.home {
|
||||||
|
padding-bottom: 1px;
|
||||||
|
background: var(--background);
|
||||||
|
// min-height: calc(100vh - 126px);
|
||||||
|
min-height: calc(100vh - var(--footer-height));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Outside container wrapping the item groups*/
|
||||||
|
.item-group-container {
|
||||||
|
display: grid;
|
||||||
|
gap: 0.5rem;
|
||||||
|
margin: 0 auto;
|
||||||
|
max-width: 90%;
|
||||||
|
overflow: auto;
|
||||||
|
@extend .scroll-bar;
|
||||||
|
@include monitor-up {
|
||||||
|
max-width: 1400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Options for alternate layouts, triggered by buttons */
|
||||||
|
&.orientation-horizontal {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
&.orientation-vertical {
|
||||||
|
max-width: 100%;
|
||||||
|
@include tablet-up {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Specify number of columns, based on screen size */
|
||||||
|
@include phone {
|
||||||
|
grid-template-columns: repeat(1, 1fr);
|
||||||
|
}
|
||||||
|
@include tablet {
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
}
|
||||||
|
@include laptop {
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
}
|
||||||
|
@include monitor {
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
}
|
||||||
|
@include big-screen {
|
||||||
|
grid-template-columns: repeat(4, 1fr);
|
||||||
|
}
|
||||||
|
@include big-screen-up {
|
||||||
|
grid-template-columns: repeat(5, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hide when search term returns nothing */
|
||||||
|
.no-results { display: none; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-data {
|
||||||
|
font-size: 2rem;
|
||||||
|
color: var(--background);
|
||||||
|
background: #ffffffeb;
|
||||||
|
width: fit-content;
|
||||||
|
margin: 2rem auto;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
border-radius: var(--curve-factor);
|
||||||
|
}
|
||||||
|
|
||||||
|
section.filter-container {
|
||||||
|
border-bottom: 1px solid var(--outline-color);
|
||||||
|
@include phone {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
Loading…
x
Reference in New Issue
Block a user