mirror of https://github.com/Lissy93/dashy.git
⚡ Many big improvments to items + sections
This commit is contained in:
parent
b1de7bc7e5
commit
a6f3c90722
|
@ -61,6 +61,7 @@ export default {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
height: fit-content;
|
height: fit-content;
|
||||||
margin: 10px;
|
margin: 10px;
|
||||||
|
min-width: 250px;
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -122,10 +122,6 @@ export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
editMenuOpen: false,
|
editMenuOpen: false,
|
||||||
customStyles: {
|
|
||||||
color: this.item.color,
|
|
||||||
background: this.item.backgroundColor,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -312,7 +308,8 @@ p.description {
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
height: 2rem;
|
height: 2rem;
|
||||||
padding-top: 4px;
|
padding-top: 0.25rem;
|
||||||
|
padding-left: 0.5rem;
|
||||||
div img {
|
div img {
|
||||||
width: 2rem;
|
width: 2rem;
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,7 @@
|
||||||
:itemId="item.id"
|
:itemId="item.id"
|
||||||
:title="item.title"
|
:title="item.title"
|
||||||
:subItems="item.subItems"
|
:subItems="item.subItems"
|
||||||
|
@triggerModal="triggerModal"
|
||||||
/>
|
/>
|
||||||
<Item
|
<Item
|
||||||
v-else
|
v-else
|
||||||
|
@ -56,6 +57,7 @@
|
||||||
:parentSectionTitle="title"
|
:parentSectionTitle="title"
|
||||||
key="add-new"
|
key="add-new"
|
||||||
class="add-new-item"
|
class="add-new-item"
|
||||||
|
:sectionWidth="sectionWidth"
|
||||||
:itemSize="itemSize"
|
:itemSize="itemSize"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -13,7 +13,8 @@
|
||||||
class="sub-item-link item"
|
class="sub-item-link item"
|
||||||
>
|
>
|
||||||
<!-- Item Icon -->
|
<!-- Item Icon -->
|
||||||
<Icon :icon="icon" :url="url" size="small" class="sub-icon-img bounce" />
|
<Icon :icon="item.icon" :url="item.url"
|
||||||
|
size="small" v-bind:style="customStyles" class="sub-icon-img bounce" />
|
||||||
</a>
|
</a>
|
||||||
<!-- Right-click context menu -->
|
<!-- Right-click context menu -->
|
||||||
<ContextMenu
|
<ContextMenu
|
||||||
|
@ -32,20 +33,14 @@
|
||||||
import Icon from '@/components/LinkItems/ItemIcon.vue';
|
import Icon from '@/components/LinkItems/ItemIcon.vue';
|
||||||
import ContextMenu from '@/components/LinkItems/ItemContextMenu';
|
import ContextMenu from '@/components/LinkItems/ItemContextMenu';
|
||||||
import ItemMixin from '@/mixins/ItemMixin';
|
import ItemMixin from '@/mixins/ItemMixin';
|
||||||
import { targetValidator } from '@/utils/ConfigHelpers';
|
// import { targetValidator } from '@/utils/ConfigHelpers';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Item',
|
name: 'Item',
|
||||||
mixins: [ItemMixin],
|
mixins: [ItemMixin],
|
||||||
props: {
|
props: {
|
||||||
id: String, // The unique ID of a tile (e.g. 001)
|
id: String, // The unique ID of a tile (e.g. 001)
|
||||||
title: String, // The main text of tile, required
|
item: Object,
|
||||||
icon: String, // Optional path to icon, within public/img/tile-icons
|
|
||||||
url: String, // URL to the resource, optional but recommended
|
|
||||||
target: { // Where resource will open, either 'newtab', 'sametab' or 'modal'
|
|
||||||
type: String,
|
|
||||||
validator: targetValidator,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
Icon,
|
Icon,
|
||||||
|
@ -69,7 +64,6 @@ export default {
|
||||||
flex-basis: 6rem;
|
flex-basis: 6rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
a.sub-item-link {
|
a.sub-item-link {
|
||||||
border: none;
|
|
||||||
margin: 0.2rem;
|
margin: 0.2rem;
|
||||||
.sub-icon-img {
|
.sub-icon-img {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
|
|
@ -5,9 +5,8 @@
|
||||||
v-for="(subItem, subIndex) in subItems"
|
v-for="(subItem, subIndex) in subItems"
|
||||||
:key="subIndex"
|
:key="subIndex"
|
||||||
:id="`${itemId}-sub-${subIndex}`"
|
:id="`${itemId}-sub-${subIndex}`"
|
||||||
:url="subItem.url"
|
:item="subItem"
|
||||||
:icon="subItem.icon"
|
@triggerModal="triggerModal"
|
||||||
:title="subItem.title"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -37,6 +36,12 @@ export default {
|
||||||
return 2;
|
return 2;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
methods: {
|
||||||
|
/* Pass open modal emit event up */
|
||||||
|
triggerModal(url) {
|
||||||
|
this.$emit('triggerModal', url);
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -1,30 +1,27 @@
|
||||||
<template>
|
<template>
|
||||||
<div :class="`minimal-section-inner ${selected ? 'selected' : ''} ${showAll ? 'show-all': ''}`">
|
<div :class="`minimal-section-inner ${selected ? 'selected' : ''} ${showAll ? 'show-all': ''}`">
|
||||||
<div class="section-items" v-if="items && (selected || showAll)">
|
<div class="section-items" v-if="items && (selected || showAll)">
|
||||||
<Item
|
<template v-for="(item) in items">
|
||||||
v-for="(item, index) in items"
|
<SubItemGroup
|
||||||
:item="item"
|
v-if="item.subItems"
|
||||||
:id="`${index}_${makeId(item.title)}`"
|
:key="item.id"
|
||||||
:key="`${index}_${makeId(item.title)}`"
|
:itemId="item.id"
|
||||||
:url="item.url"
|
:title="item.title"
|
||||||
:title="item.title"
|
:subItems="item.subItems"
|
||||||
:description="item.description"
|
@triggerModal="triggerModal"
|
||||||
:icon="item.icon"
|
/>
|
||||||
:target="item.target"
|
<Item
|
||||||
:color="item.color"
|
v-else
|
||||||
:backgroundColor="item.backgroundColor"
|
:item="item"
|
||||||
:statusCheckUrl="item.statusCheckUrl"
|
:key="item.id"
|
||||||
:statusCheckHeaders="item.statusCheckHeaders"
|
:itemSize="itemSize"
|
||||||
:itemSize="itemSize"
|
:parentSectionTitle="title"
|
||||||
:hotkey="item.hotkey"
|
@itemClicked="$emit('itemClicked')"
|
||||||
:enableStatusCheck="shouldEnableStatusCheck(item.statusCheck)"
|
@triggerModal="triggerModal"
|
||||||
:statusCheckAllowInsecure="item.statusCheckAllowInsecure"
|
:isAddNew="false"
|
||||||
:statusCheckAcceptCodes="item.statusCheckAcceptCodes"
|
:sectionDisplayData="displayData"
|
||||||
:statusCheckMaxRedirects="item.statusCheckMaxRedirects"
|
/>
|
||||||
:statusCheckInterval="getStatusCheckInterval()"
|
</template>
|
||||||
@itemClicked="$emit('itemClicked')"
|
|
||||||
@triggerModal="triggerModal"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div v-if="widgets && (selected && !showAll)" class="minimal-widget-wrap">
|
<div v-if="widgets && (selected && !showAll)" class="minimal-widget-wrap">
|
||||||
<WidgetBase
|
<WidgetBase
|
||||||
|
@ -50,6 +47,7 @@
|
||||||
import router from '@/router';
|
import router from '@/router';
|
||||||
import Item from '@/components/LinkItems/Item.vue';
|
import Item from '@/components/LinkItems/Item.vue';
|
||||||
import WidgetBase from '@/components/Widgets/WidgetBase';
|
import WidgetBase from '@/components/Widgets/WidgetBase';
|
||||||
|
import SubItemGroup from '@/components/LinkItems/SubItemGroup.vue';
|
||||||
import IframeModal from '@/components/LinkItems/IframeModal.vue';
|
import IframeModal from '@/components/LinkItems/IframeModal.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -75,6 +73,7 @@ export default {
|
||||||
components: {
|
components: {
|
||||||
Item,
|
Item,
|
||||||
WidgetBase,
|
WidgetBase,
|
||||||
|
SubItemGroup,
|
||||||
IframeModal,
|
IframeModal,
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -83,6 +82,7 @@ export default {
|
||||||
},
|
},
|
||||||
/* Returns a unique lowercase string, based on name, for section ID */
|
/* Returns a unique lowercase string, based on name, for section ID */
|
||||||
makeId(str) {
|
makeId(str) {
|
||||||
|
if (!str) return 'unnamed-item';
|
||||||
return str.replace(/\s+/g, '-').replace(/[^a-zA-Z ]/g, '').toLowerCase();
|
return str.replace(/\s+/g, '-').replace(/[^a-zA-Z ]/g, '').toLowerCase();
|
||||||
},
|
},
|
||||||
/* Opens the iframe modal */
|
/* Opens the iframe modal */
|
||||||
|
@ -105,7 +105,6 @@ export default {
|
||||||
const parse = (section) => section.replace(' ', '-').toLowerCase().trim();
|
const parse = (section) => section.replace(' ', '-').toLowerCase().trim();
|
||||||
const sectionIdentifier = parse(this.title);
|
const sectionIdentifier = parse(this.title);
|
||||||
router.push({ path: `/home/${sectionIdentifier}` });
|
router.push({ path: `/home/${sectionIdentifier}` });
|
||||||
this.closeContextMenu();
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="nav-outer">
|
<div class="nav-outer" v-if="links && links.length > 0">
|
||||||
<IconBurger
|
<IconBurger
|
||||||
:class="`burger ${!navVisible ? 'visible' : ''}`"
|
:class="`burger ${!navVisible ? 'visible' : ''}`"
|
||||||
@click="navVisible = !navVisible"
|
@click="navVisible = !navVisible"
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<section>
|
<section v-bind:class="{ 'settings-hidden': !settingsVisible }">
|
||||||
<SearchBar ref="SearchBar"
|
<SearchBar ref="SearchBar"
|
||||||
@user-is-searchin="userIsTypingSomething"
|
@user-is-searchin="userIsTypingSomething"
|
||||||
v-if="searchVisible"
|
v-if="searchVisible"
|
||||||
|
|
|
@ -520,6 +520,8 @@ export default {
|
||||||
.widget-base {
|
.widget-base {
|
||||||
position: relative;
|
position: relative;
|
||||||
padding: 0.75rem 0.5rem 0.5rem 0.5rem;
|
padding: 0.75rem 0.5rem 0.5rem 0.5rem;
|
||||||
|
background: var(--widget-base-background);
|
||||||
|
box-shadow: var(--widget-base-shadow, none);
|
||||||
// Refresh and full-page action buttons
|
// Refresh and full-page action buttons
|
||||||
button.action-btn {
|
button.action-btn {
|
||||||
height: 1rem;
|
height: 1rem;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<nav class="side-bar">
|
<nav class="side-bar">
|
||||||
<div v-for="(section, index) in sections" :key="index" class="side-bar-section">
|
<div v-for="(section, index) in sections" :key="index" class="side-bar-section">
|
||||||
|
<!-- Section button -->
|
||||||
<div @click="openSection(index)" class="side-bar-item-container">
|
<div @click="openSection(index)" class="side-bar-item-container">
|
||||||
<SideBarItem
|
<SideBarItem
|
||||||
class="item"
|
class="item"
|
||||||
|
@ -8,6 +9,7 @@
|
||||||
:title="section.name"
|
:title="section.name"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- Section inner -->
|
||||||
<transition name="slide">
|
<transition name="slide">
|
||||||
<SideBarSection
|
<SideBarSection
|
||||||
v-if="isOpen[index]"
|
v-if="isOpen[index]"
|
||||||
|
@ -16,6 +18,7 @@
|
||||||
/>
|
/>
|
||||||
</transition>
|
</transition>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- Show links for switching back to Home / Minimal views -->
|
||||||
<div class="switch-view-buttons">
|
<div class="switch-view-buttons">
|
||||||
<router-link to="/home">
|
<router-link to="/home">
|
||||||
<IconHome class="view-icon" v-tooltip="$t('alternate-views.default')" />
|
<IconHome class="view-icon" v-tooltip="$t('alternate-views.default')" />
|
||||||
|
@ -66,8 +69,12 @@ export default {
|
||||||
if (!this.initUrl) return;
|
if (!this.initUrl) return;
|
||||||
const process = (url) => (url ? url.replace(/[^\w\s]/gi, '').toLowerCase() : undefined);
|
const process = (url) => (url ? url.replace(/[^\w\s]/gi, '').toLowerCase() : undefined);
|
||||||
const compare = (item) => (process(item.url) === process(this.initUrl));
|
const compare = (item) => (process(item.url) === process(this.initUrl));
|
||||||
this.sections.forEach((section, secIndex) => {
|
this.sections.forEach((section, secIndx) => {
|
||||||
if (section.items && section.items.findIndex(compare) !== -1) this.openSection(secIndex);
|
if (!section.items) return; // Cancel if no items
|
||||||
|
if (section.items.findIndex(compare) !== -1) this.openSection(secIndx);
|
||||||
|
section.items.forEach((item) => { // Do the same for sub-items, if set
|
||||||
|
if (item.subItems && item.subItems.findIndex(compare) !== -1) this.openSection(secIndx);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
<div class="sub-side-bar">
|
<div class="sub-side-bar">
|
||||||
<div v-for="(item, index) in items" :key="index">
|
<div v-for="(item, index) in items" :key="index">
|
||||||
<SideBarItem
|
<SideBarItem
|
||||||
|
v-if="!item.subItems"
|
||||||
class="item"
|
class="item"
|
||||||
:icon="item.icon"
|
:icon="item.icon"
|
||||||
:title="item.title"
|
:title="item.title"
|
||||||
|
@ -9,6 +10,18 @@
|
||||||
:target="item.target"
|
:target="item.target"
|
||||||
@launch-app="launchApp"
|
@launch-app="launchApp"
|
||||||
/>
|
/>
|
||||||
|
<div v-if="item.subItems" class="sub-item-group">
|
||||||
|
<SideBarItem
|
||||||
|
v-for="(subItem, subIndex) in item.subItems"
|
||||||
|
:key="subIndex"
|
||||||
|
class="item sub-item"
|
||||||
|
:icon="subItem.icon"
|
||||||
|
:title="subItem.title"
|
||||||
|
:url="subItem.url"
|
||||||
|
:target="subItem.target"
|
||||||
|
@launch-app="launchApp"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -46,8 +59,10 @@ div.sub-side-bar {
|
||||||
color: var(--side-bar-color);
|
color: var(--side-bar-color);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
z-index: 3;
|
z-index: 3;
|
||||||
.item:not(:last-child) {
|
.sub-item-group {
|
||||||
border-bottom: 1px dashed var(--side-bar-color);
|
border: 1px dotted var(--side-bar-color);
|
||||||
|
border-radius: 4px;
|
||||||
|
background: #00000033;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,7 @@ export default {
|
||||||
width: calc(100% - var(--side-bar-width));
|
width: calc(100% - var(--side-bar-width));
|
||||||
.workspace-widget {
|
.workspace-widget {
|
||||||
max-width: 800px;
|
max-width: 800px;
|
||||||
margin: 0 auto;
|
margin: 0.5rem auto 1rem auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -25,6 +25,10 @@ export default {
|
||||||
posX: undefined,
|
posX: undefined,
|
||||||
posY: undefined,
|
posY: undefined,
|
||||||
},
|
},
|
||||||
|
customStyles: {
|
||||||
|
color: this.item.color,
|
||||||
|
background: this.item.backgroundColor,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -71,7 +75,7 @@ export default {
|
||||||
/* Get href for anchor, if not in edit mode, or opening in modal/ workspace */
|
/* Get href for anchor, if not in edit mode, or opening in modal/ workspace */
|
||||||
hyperLinkHref() {
|
hyperLinkHref() {
|
||||||
const nothing = '#';
|
const nothing = '#';
|
||||||
const url = this.url || nothing;
|
const url = this.url || this.item.url || nothing;
|
||||||
if (this.isEditMode) return nothing;
|
if (this.isEditMode) return nothing;
|
||||||
const noAnchorNeeded = ['modal', 'workspace', 'clipboard'];
|
const noAnchorNeeded = ['modal', 'workspace', 'clipboard'];
|
||||||
return noAnchorNeeded.includes(this.accumulatedTarget) ? nothing : url;
|
return noAnchorNeeded.includes(this.accumulatedTarget) ? nothing : url;
|
||||||
|
@ -126,6 +130,7 @@ export default {
|
||||||
},
|
},
|
||||||
/* Called when an item is clicked, manages the opening of modal & resets the search field */
|
/* Called when an item is clicked, manages the opening of modal & resets the search field */
|
||||||
itemClicked(e) {
|
itemClicked(e) {
|
||||||
|
const url = this.url || this.item.url;
|
||||||
if (this.isEditMode) {
|
if (this.isEditMode) {
|
||||||
// If in edit mode, open settings, and don't launch app
|
// If in edit mode, open settings, and don't launch app
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
@ -135,16 +140,16 @@ export default {
|
||||||
// For certain opening methods, prevent default and manually navigate
|
// For certain opening methods, prevent default and manually navigate
|
||||||
if (e.ctrlKey) {
|
if (e.ctrlKey) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
window.open(this.url, '_blank');
|
window.open(url, '_blank');
|
||||||
} else if (e.altKey || this.accumulatedTarget === 'modal') {
|
} else if (e.altKey || this.accumulatedTarget === 'modal') {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.$emit('triggerModal', this.url);
|
this.$emit('triggerModal', url);
|
||||||
} else if (this.accumulatedTarget === 'workspace') {
|
} else if (this.accumulatedTarget === 'workspace') {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
router.push({ name: 'workspace', query: { url: this.url } });
|
router.push({ name: 'workspace', query: { url } });
|
||||||
} else if (this.accumulatedTarget === 'clipboard') {
|
} else if (this.accumulatedTarget === 'clipboard') {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
navigator.clipboard.writeText(this.url);
|
navigator.clipboard.writeText(url);
|
||||||
this.$toasted.show(this.$t('context-menus.item.copied-toast'));
|
this.$toasted.show(this.$t('context-menus.item.copied-toast'));
|
||||||
}
|
}
|
||||||
// Emit event to clear search field, etc
|
// Emit event to clear search field, etc
|
||||||
|
@ -157,7 +162,7 @@ export default {
|
||||||
},
|
},
|
||||||
/* Open item, using specified method */
|
/* Open item, using specified method */
|
||||||
launchItem(method, link) {
|
launchItem(method, link) {
|
||||||
const url = link || this.url;
|
const url = link || this.item.url;
|
||||||
this.contextMenuOpen = false;
|
this.contextMenuOpen = false;
|
||||||
switch (method) {
|
switch (method) {
|
||||||
case 'newtab':
|
case 'newtab':
|
||||||
|
|
|
@ -55,6 +55,7 @@ module.exports = {
|
||||||
'dracula',
|
'dracula',
|
||||||
'one-dark',
|
'one-dark',
|
||||||
'lissy',
|
'lissy',
|
||||||
|
'cherry-blossom',
|
||||||
'nord-frost',
|
'nord-frost',
|
||||||
'nord',
|
'nord',
|
||||||
'oblivion',
|
'oblivion',
|
||||||
|
|
Loading…
Reference in New Issue