mirror of https://github.com/Lissy93/dashy.git
⚡ Improved layout for items and sub-items
This commit is contained in:
parent
151028c8cf
commit
57abd67cf9
|
@ -1,5 +1,5 @@
|
|||
<template ref="container">
|
||||
<div :class="`item-wrapper wrap-size-${size}`" >
|
||||
<div :class="`item-wrapper wrap-size-${size} span-${makeColumnCount}`" >
|
||||
<a @click="itemClicked"
|
||||
@long-press="openContextMenu"
|
||||
@contextmenu.prevent
|
||||
|
@ -11,8 +11,7 @@
|
|||
v-tooltip="getTooltipOptions()"
|
||||
rel="noopener noreferrer" tabindex="0"
|
||||
:id="`link-${item.id}`"
|
||||
:style=
|
||||
"`--open-icon:${unicodeOpeningIcon};color:${item.color};background:${item.backgroundColor}`"
|
||||
:style="customStyle"
|
||||
>
|
||||
<!-- Item Text -->
|
||||
<div :class="`tile-title ${!item.icon? 'bounce no-icon': ''}`" :id="`tile-${item.id}`" >
|
||||
|
@ -77,6 +76,8 @@ export default {
|
|||
itemSize: String,
|
||||
parentSectionTitle: String, // Title of parent section (for add new)
|
||||
isAddNew: Boolean, // Only set if 'fake' item used as Add New button
|
||||
sectionWidth: Number, // Width of parent section
|
||||
sectionDisplayData: Object,
|
||||
},
|
||||
components: {
|
||||
Icon,
|
||||
|
@ -88,6 +89,15 @@ export default {
|
|||
EditModeIcon,
|
||||
},
|
||||
computed: {
|
||||
makeColumnCount() {
|
||||
if ((this.sectionDisplayData || {}).itemCountX) return this.sectionDisplayData.itemCountX;
|
||||
if (this.sectionWidth < 380) return 1;
|
||||
if (this.sectionWidth < 520) return 2;
|
||||
if (this.sectionWidth < 730) return 3;
|
||||
if (this.sectionWidth < 1000) return 4;
|
||||
if (this.sectionWidth < 1300) return 5;
|
||||
return 0;
|
||||
},
|
||||
/* Based on item props, adjust class names */
|
||||
makeClassList() {
|
||||
const { isAddNew, isEditMode, size } = this;
|
||||
|
@ -183,6 +193,17 @@ export default {
|
|||
&.wrap-size-large {
|
||||
flex-basis: 12rem;
|
||||
}
|
||||
&.wrap-size-small {
|
||||
flex-grow: revert;
|
||||
&.span-1 { min-width: 100%; }
|
||||
&.span-2 { min-width: 50%; }
|
||||
&.span-3 { min-width: 33%; }
|
||||
&.span-4 { min-width: 25%; }
|
||||
&.span-5 { min-width: 20%; }
|
||||
&.span-6 { min-width: 16%; }
|
||||
&.span-7 { min-width: 14%; }
|
||||
&.span-8 { min-width: 12.5%; }
|
||||
}
|
||||
}
|
||||
|
||||
.item {
|
||||
|
@ -292,7 +313,6 @@ p.description {
|
|||
align-items: center;
|
||||
height: 2rem;
|
||||
padding-top: 4px;
|
||||
max-width: 14rem;
|
||||
div img {
|
||||
width: 2rem;
|
||||
}
|
||||
|
|
|
@ -77,6 +77,7 @@ export default {
|
|||
posX: Number, // The X coordinate for positioning
|
||||
posY: Number, // The Y coordinate for positioning
|
||||
show: Boolean, // Should show or hide the menu
|
||||
disableEdit: Boolean, // Disable editing for certain items
|
||||
},
|
||||
computed: {
|
||||
isMenuDisabled() {
|
||||
|
@ -86,6 +87,7 @@ export default {
|
|||
return this.$store.state.editMode;
|
||||
},
|
||||
isEditAllowed() {
|
||||
if (this.disableEdit) return false;
|
||||
return this.$store.getters.permissions.allowViewConfig;
|
||||
},
|
||||
},
|
||||
|
|
|
@ -11,7 +11,8 @@
|
|||
:cutToHeight="displayData.cutToHeight"
|
||||
@openEditSection="openEditSection"
|
||||
@openContextMenu="openContextMenu"
|
||||
:id="`section-outer-${groupId}`"
|
||||
:id="sectionRef"
|
||||
:ref="sectionRef"
|
||||
>
|
||||
<!-- If no items, show message -->
|
||||
<div v-if="isEmpty" class="no-items">
|
||||
|
@ -23,17 +24,13 @@
|
|||
:style="gridStyle" :id="`section-${groupId}`"
|
||||
> <!-- Show for each item -->
|
||||
<template v-for="(item) in sortedItems">
|
||||
<div v-if="item.subItems" :key="item.id" class="sub-items-group">
|
||||
<template v-for="(subItem, subIndex) in item.subItems">
|
||||
<SubItem
|
||||
:key="subIndex"
|
||||
:id="`${item.id}-sub-${subIndex}`"
|
||||
:url="subItem.url"
|
||||
:icon="subItem.icon"
|
||||
:title="subItem.title"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
<SubItemGroup
|
||||
v-if="item.subItems"
|
||||
:key="item.id"
|
||||
:itemId="item.id"
|
||||
:title="item.title"
|
||||
:subItems="item.subItems"
|
||||
/>
|
||||
<Item
|
||||
v-else
|
||||
:item="item"
|
||||
|
@ -43,6 +40,8 @@
|
|||
@itemClicked="$emit('itemClicked')"
|
||||
@triggerModal="triggerModal"
|
||||
:isAddNew="false"
|
||||
:sectionWidth="sectionWidth"
|
||||
:sectionDisplayData="displayData"
|
||||
/>
|
||||
</template>
|
||||
<!-- When in edit mode, show additional item, for Add New item -->
|
||||
|
@ -101,7 +100,7 @@
|
|||
<script>
|
||||
import router from '@/router';
|
||||
import Item from '@/components/LinkItems/Item.vue';
|
||||
import SubItem from '@/components/LinkItems/SubItem.vue';
|
||||
import SubItemGroup from '@/components/LinkItems/SubItemGroup.vue';
|
||||
import WidgetBase from '@/components/Widgets/WidgetBase';
|
||||
import Collapsable from '@/components/LinkItems/Collapsable.vue';
|
||||
import IframeModal from '@/components/LinkItems/IframeModal.vue';
|
||||
|
@ -131,7 +130,7 @@ export default {
|
|||
Collapsable,
|
||||
ContextMenu,
|
||||
Item,
|
||||
SubItem,
|
||||
SubItemGroup,
|
||||
WidgetBase,
|
||||
IframeModal,
|
||||
EditSection,
|
||||
|
@ -144,6 +143,8 @@ export default {
|
|||
posX: undefined,
|
||||
posY: undefined,
|
||||
},
|
||||
sectionWidth: 0,
|
||||
resizeObserver: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
@ -169,6 +170,9 @@ export default {
|
|||
isEmpty() {
|
||||
return !this.hasItems && !this.hasWidgets;
|
||||
},
|
||||
sectionRef() {
|
||||
return `section-outer-${this.groupId}`;
|
||||
},
|
||||
/* If the sortBy attribute is specified, then return sorted data */
|
||||
sortedItems() {
|
||||
let { items } = this;
|
||||
|
@ -294,6 +298,22 @@ export default {
|
|||
closeContextMenu() {
|
||||
this.contextMenuOpen = false;
|
||||
},
|
||||
/* Calculate width of section, used to dynamically set number of columns */
|
||||
calculateSectionWidth() {
|
||||
const secElem = this.$refs[this.sectionRef];
|
||||
if (secElem) this.sectionWidth = secElem.$el.clientWidth;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
// Set the section width, and recalculate when section resized
|
||||
this.resizeObserver = new ResizeObserver(this.calculateSectionWidth)
|
||||
.observe(this.$refs[this.sectionRef].$el);
|
||||
},
|
||||
beforeDestroy() {
|
||||
// If resize observer set, and element still present, then de-register
|
||||
if (this.resizeObserver && this.$refs[this.sectionRef]) {
|
||||
this.resizeObserver.unobserve(this.$refs[this.sectionRef].$el);
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -375,18 +395,4 @@ export default {
|
|||
}
|
||||
}
|
||||
|
||||
.sub-items-group {
|
||||
display: grid;
|
||||
margin: 0.5rem;
|
||||
padding: 0.1rem;
|
||||
flex-grow: 1;
|
||||
flex-basis: 6rem;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
color: var(--item-text-color);
|
||||
border: 1px solid var(--outline-color);
|
||||
border-radius: var(--curve-factor);
|
||||
text-decoration: none;
|
||||
transition: all 0.2s ease-in-out 0s;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
|
@ -1,21 +1,36 @@
|
|||
<template ref="container">
|
||||
<div class="sub-item-wrapper">
|
||||
<a @click="beforeLaunchItem"
|
||||
<a @click="itemClicked"
|
||||
@contextmenu.prevent
|
||||
@long-press="openContextMenu"
|
||||
@mouseup.right="openContextMenu"
|
||||
v-longPress="true"
|
||||
:href="hyperLinkHref"
|
||||
:target="anchorTarget"
|
||||
class="sub-item-link item"
|
||||
v-tooltip="subItemTooltip"
|
||||
rel="noopener noreferrer" tabindex="0"
|
||||
:id="`link-${id}`"
|
||||
class="sub-item-link item"
|
||||
>
|
||||
<!-- Item Icon -->
|
||||
<Icon :icon="icon" :url="url" size="small" class="sub-icon-img bounce" />
|
||||
</a>
|
||||
<!-- Right-click context menu -->
|
||||
<ContextMenu
|
||||
:show="contextMenuOpen && !isAddNew"
|
||||
v-click-outside="closeContextMenu"
|
||||
:posX="contextPos.posX"
|
||||
:posY="contextPos.posY"
|
||||
:id="`context-menu-${id}`"
|
||||
:disableEdit="true"
|
||||
@launchItem="launchItem"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Icon from '@/components/LinkItems/ItemIcon.vue';
|
||||
import ContextMenu from '@/components/LinkItems/ItemContextMenu';
|
||||
import ItemMixin from '@/mixins/ItemMixin';
|
||||
import { targetValidator } from '@/utils/ConfigHelpers';
|
||||
|
||||
|
@ -34,6 +49,7 @@ export default {
|
|||
},
|
||||
components: {
|
||||
Icon,
|
||||
ContextMenu,
|
||||
},
|
||||
computed: {
|
||||
subItemTooltip() {
|
||||
|
@ -43,23 +59,7 @@ export default {
|
|||
data() {
|
||||
return {};
|
||||
},
|
||||
methods: {
|
||||
beforeLaunchItem(e) {
|
||||
if (this.isEditMode) return;
|
||||
if (e.altKey) {
|
||||
e.preventDefault();
|
||||
this.launchItem('modal');
|
||||
} else if (this.accumulatedTarget === 'modal') {
|
||||
this.launchItem('modal');
|
||||
} else if (this.accumulatedTarget === 'workspace') {
|
||||
this.launchItem('workspace');
|
||||
} else if (this.accumulatedTarget === 'clipboard') {
|
||||
this.launchItem('clipboard');
|
||||
}
|
||||
// Clear search bar
|
||||
this.$emit('itemClicked');
|
||||
},
|
||||
},
|
||||
methods: {},
|
||||
};
|
||||
</script>
|
||||
|
||||
|
@ -68,7 +68,7 @@ export default {
|
|||
flex-grow: 1;
|
||||
flex-basis: 6rem;
|
||||
display: flex;
|
||||
a {
|
||||
a.sub-item-link {
|
||||
border: none;
|
||||
margin: 0.2rem;
|
||||
.sub-icon-img {
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
<template>
|
||||
<div class="sub-items-group" :style="`--sub-item-col-count: ${columnCount}`">
|
||||
<p v-if="title" class="sub-item-group-title">{{ title }}</p>
|
||||
<SubItem
|
||||
v-for="(subItem, subIndex) in subItems"
|
||||
:key="subIndex"
|
||||
:id="`${itemId}-sub-${subIndex}`"
|
||||
:url="subItem.url"
|
||||
:icon="subItem.icon"
|
||||
:title="subItem.title"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SubItem from '@/components/LinkItems/SubItem.vue';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
itemId: String,
|
||||
subItems: Array,
|
||||
title: String,
|
||||
subItemGridSize: Number,
|
||||
},
|
||||
components: {
|
||||
SubItem,
|
||||
},
|
||||
computed: {
|
||||
/* Determine number of columns to split items into, depending on number of items */
|
||||
columnCount() {
|
||||
if (this.subItemGridSize) return this.subItemGridSize;
|
||||
const numItems = this.subItems.length;
|
||||
if (numItems >= 10) return 4;
|
||||
if (numItems >= 5) return 3;
|
||||
if (numItems >= 2) return 2;
|
||||
if (numItems === 1) return 1;
|
||||
return 2;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.sub-items-group {
|
||||
display: grid;
|
||||
margin: 0.5rem;
|
||||
padding: 0.1rem;
|
||||
flex-grow: 1;
|
||||
flex-basis: 6rem;
|
||||
grid-template-columns: repeat(var(--sub-item-col-count, 3), minmax(0, 1fr));
|
||||
color: var(--item-text-color);
|
||||
border: 1px solid var(--outline-color);
|
||||
border-radius: var(--curve-factor);
|
||||
text-decoration: none;
|
||||
transition: all 0.2s ease-in-out 0s;
|
||||
color: var(--item-text-color);
|
||||
p.sub-item-group-title {
|
||||
margin: 0 auto;
|
||||
cursor: default;
|
||||
grid-column-start: span var(--sub-item-col-count, 3);
|
||||
}
|
||||
}
|
||||
</style>
|
Loading…
Reference in New Issue