Many big improvments to items + sections

This commit is contained in:
Alicia Sykes 2022-04-14 14:34:20 +01:00
parent b1de7bc7e5
commit a6f3c90722
14 changed files with 84 additions and 56 deletions

View File

@ -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>

View File

@ -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;
} }

View File

@ -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>

View File

@ -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;

View File

@ -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>

View File

@ -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();
}, },
}, },
}; };

View File

@ -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"

View File

@ -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"

View File

@ -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;

View File

@ -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);
});
}); });
}, },
}, },

View File

@ -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;
} }
} }

View File

@ -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>

View File

@ -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':

View File

@ -55,6 +55,7 @@ module.exports = {
'dracula', 'dracula',
'one-dark', 'one-dark',
'lissy', 'lissy',
'cherry-blossom',
'nord-frost', 'nord-frost',
'nord', 'nord',
'oblivion', 'oblivion',