mirror of https://github.com/lissy93/dashy
⚡ Improved layout for items and sub-items
This commit is contained in:
parent
151028c8cf
commit
57abd67cf9
|
@ -1,5 +1,5 @@
|
||||||
<template ref="container">
|
<template ref="container">
|
||||||
<div :class="`item-wrapper wrap-size-${size}`" >
|
<div :class="`item-wrapper wrap-size-${size} span-${makeColumnCount}`" >
|
||||||
<a @click="itemClicked"
|
<a @click="itemClicked"
|
||||||
@long-press="openContextMenu"
|
@long-press="openContextMenu"
|
||||||
@contextmenu.prevent
|
@contextmenu.prevent
|
||||||
|
@ -11,8 +11,7 @@
|
||||||
v-tooltip="getTooltipOptions()"
|
v-tooltip="getTooltipOptions()"
|
||||||
rel="noopener noreferrer" tabindex="0"
|
rel="noopener noreferrer" tabindex="0"
|
||||||
:id="`link-${item.id}`"
|
:id="`link-${item.id}`"
|
||||||
:style=
|
:style="customStyle"
|
||||||
"`--open-icon:${unicodeOpeningIcon};color:${item.color};background:${item.backgroundColor}`"
|
|
||||||
>
|
>
|
||||||
<!-- Item Text -->
|
<!-- Item Text -->
|
||||||
<div :class="`tile-title ${!item.icon? 'bounce no-icon': ''}`" :id="`tile-${item.id}`" >
|
<div :class="`tile-title ${!item.icon? 'bounce no-icon': ''}`" :id="`tile-${item.id}`" >
|
||||||
|
@ -77,6 +76,8 @@ export default {
|
||||||
itemSize: String,
|
itemSize: String,
|
||||||
parentSectionTitle: String, // Title of parent section (for add new)
|
parentSectionTitle: String, // Title of parent section (for add new)
|
||||||
isAddNew: Boolean, // Only set if 'fake' item used as Add New button
|
isAddNew: Boolean, // Only set if 'fake' item used as Add New button
|
||||||
|
sectionWidth: Number, // Width of parent section
|
||||||
|
sectionDisplayData: Object,
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
Icon,
|
Icon,
|
||||||
|
@ -88,6 +89,15 @@ export default {
|
||||||
EditModeIcon,
|
EditModeIcon,
|
||||||
},
|
},
|
||||||
computed: {
|
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 */
|
/* Based on item props, adjust class names */
|
||||||
makeClassList() {
|
makeClassList() {
|
||||||
const { isAddNew, isEditMode, size } = this;
|
const { isAddNew, isEditMode, size } = this;
|
||||||
|
@ -183,6 +193,17 @@ export default {
|
||||||
&.wrap-size-large {
|
&.wrap-size-large {
|
||||||
flex-basis: 12rem;
|
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 {
|
.item {
|
||||||
|
@ -292,7 +313,6 @@ p.description {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
height: 2rem;
|
height: 2rem;
|
||||||
padding-top: 4px;
|
padding-top: 4px;
|
||||||
max-width: 14rem;
|
|
||||||
div img {
|
div img {
|
||||||
width: 2rem;
|
width: 2rem;
|
||||||
}
|
}
|
||||||
|
|
|
@ -77,6 +77,7 @@ export default {
|
||||||
posX: Number, // The X coordinate for positioning
|
posX: Number, // The X coordinate for positioning
|
||||||
posY: Number, // The Y coordinate for positioning
|
posY: Number, // The Y coordinate for positioning
|
||||||
show: Boolean, // Should show or hide the menu
|
show: Boolean, // Should show or hide the menu
|
||||||
|
disableEdit: Boolean, // Disable editing for certain items
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
isMenuDisabled() {
|
isMenuDisabled() {
|
||||||
|
@ -86,6 +87,7 @@ export default {
|
||||||
return this.$store.state.editMode;
|
return this.$store.state.editMode;
|
||||||
},
|
},
|
||||||
isEditAllowed() {
|
isEditAllowed() {
|
||||||
|
if (this.disableEdit) return false;
|
||||||
return this.$store.getters.permissions.allowViewConfig;
|
return this.$store.getters.permissions.allowViewConfig;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -11,7 +11,8 @@
|
||||||
:cutToHeight="displayData.cutToHeight"
|
:cutToHeight="displayData.cutToHeight"
|
||||||
@openEditSection="openEditSection"
|
@openEditSection="openEditSection"
|
||||||
@openContextMenu="openContextMenu"
|
@openContextMenu="openContextMenu"
|
||||||
:id="`section-outer-${groupId}`"
|
:id="sectionRef"
|
||||||
|
:ref="sectionRef"
|
||||||
>
|
>
|
||||||
<!-- If no items, show message -->
|
<!-- If no items, show message -->
|
||||||
<div v-if="isEmpty" class="no-items">
|
<div v-if="isEmpty" class="no-items">
|
||||||
|
@ -23,17 +24,13 @@
|
||||||
:style="gridStyle" :id="`section-${groupId}`"
|
:style="gridStyle" :id="`section-${groupId}`"
|
||||||
> <!-- Show for each item -->
|
> <!-- Show for each item -->
|
||||||
<template v-for="(item) in sortedItems">
|
<template v-for="(item) in sortedItems">
|
||||||
<div v-if="item.subItems" :key="item.id" class="sub-items-group">
|
<SubItemGroup
|
||||||
<template v-for="(subItem, subIndex) in item.subItems">
|
v-if="item.subItems"
|
||||||
<SubItem
|
:key="item.id"
|
||||||
:key="subIndex"
|
:itemId="item.id"
|
||||||
:id="`${item.id}-sub-${subIndex}`"
|
:title="item.title"
|
||||||
:url="subItem.url"
|
:subItems="item.subItems"
|
||||||
:icon="subItem.icon"
|
/>
|
||||||
:title="subItem.title"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
<Item
|
<Item
|
||||||
v-else
|
v-else
|
||||||
:item="item"
|
:item="item"
|
||||||
|
@ -43,6 +40,8 @@
|
||||||
@itemClicked="$emit('itemClicked')"
|
@itemClicked="$emit('itemClicked')"
|
||||||
@triggerModal="triggerModal"
|
@triggerModal="triggerModal"
|
||||||
:isAddNew="false"
|
:isAddNew="false"
|
||||||
|
:sectionWidth="sectionWidth"
|
||||||
|
:sectionDisplayData="displayData"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<!-- When in edit mode, show additional item, for Add New item -->
|
<!-- When in edit mode, show additional item, for Add New item -->
|
||||||
|
@ -101,7 +100,7 @@
|
||||||
<script>
|
<script>
|
||||||
import router from '@/router';
|
import router from '@/router';
|
||||||
import Item from '@/components/LinkItems/Item.vue';
|
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 WidgetBase from '@/components/Widgets/WidgetBase';
|
||||||
import Collapsable from '@/components/LinkItems/Collapsable.vue';
|
import Collapsable from '@/components/LinkItems/Collapsable.vue';
|
||||||
import IframeModal from '@/components/LinkItems/IframeModal.vue';
|
import IframeModal from '@/components/LinkItems/IframeModal.vue';
|
||||||
|
@ -131,7 +130,7 @@ export default {
|
||||||
Collapsable,
|
Collapsable,
|
||||||
ContextMenu,
|
ContextMenu,
|
||||||
Item,
|
Item,
|
||||||
SubItem,
|
SubItemGroup,
|
||||||
WidgetBase,
|
WidgetBase,
|
||||||
IframeModal,
|
IframeModal,
|
||||||
EditSection,
|
EditSection,
|
||||||
|
@ -144,6 +143,8 @@ export default {
|
||||||
posX: undefined,
|
posX: undefined,
|
||||||
posY: undefined,
|
posY: undefined,
|
||||||
},
|
},
|
||||||
|
sectionWidth: 0,
|
||||||
|
resizeObserver: null,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -169,6 +170,9 @@ export default {
|
||||||
isEmpty() {
|
isEmpty() {
|
||||||
return !this.hasItems && !this.hasWidgets;
|
return !this.hasItems && !this.hasWidgets;
|
||||||
},
|
},
|
||||||
|
sectionRef() {
|
||||||
|
return `section-outer-${this.groupId}`;
|
||||||
|
},
|
||||||
/* If the sortBy attribute is specified, then return sorted data */
|
/* If the sortBy attribute is specified, then return sorted data */
|
||||||
sortedItems() {
|
sortedItems() {
|
||||||
let { items } = this;
|
let { items } = this;
|
||||||
|
@ -294,6 +298,22 @@ export default {
|
||||||
closeContextMenu() {
|
closeContextMenu() {
|
||||||
this.contextMenuOpen = false;
|
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>
|
</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>
|
</style>
|
||||||
|
|
|
@ -1,21 +1,36 @@
|
||||||
<template ref="container">
|
<template ref="container">
|
||||||
<div class="sub-item-wrapper">
|
<div class="sub-item-wrapper">
|
||||||
<a @click="beforeLaunchItem"
|
<a @click="itemClicked"
|
||||||
|
@contextmenu.prevent
|
||||||
|
@long-press="openContextMenu"
|
||||||
|
@mouseup.right="openContextMenu"
|
||||||
|
v-longPress="true"
|
||||||
:href="hyperLinkHref"
|
:href="hyperLinkHref"
|
||||||
:target="anchorTarget"
|
:target="anchorTarget"
|
||||||
class="sub-item-link item"
|
|
||||||
v-tooltip="subItemTooltip"
|
v-tooltip="subItemTooltip"
|
||||||
rel="noopener noreferrer" tabindex="0"
|
rel="noopener noreferrer" tabindex="0"
|
||||||
:id="`link-${id}`"
|
:id="`link-${id}`"
|
||||||
|
class="sub-item-link item"
|
||||||
>
|
>
|
||||||
<!-- Item Icon -->
|
<!-- Item Icon -->
|
||||||
<Icon :icon="icon" :url="url" size="small" class="sub-icon-img bounce" />
|
<Icon :icon="icon" :url="url" size="small" class="sub-icon-img bounce" />
|
||||||
</a>
|
</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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Icon from '@/components/LinkItems/ItemIcon.vue';
|
import Icon from '@/components/LinkItems/ItemIcon.vue';
|
||||||
|
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';
|
||||||
|
|
||||||
|
@ -34,6 +49,7 @@ export default {
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
Icon,
|
Icon,
|
||||||
|
ContextMenu,
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
subItemTooltip() {
|
subItemTooltip() {
|
||||||
|
@ -43,23 +59,7 @@ export default {
|
||||||
data() {
|
data() {
|
||||||
return {};
|
return {};
|
||||||
},
|
},
|
||||||
methods: {
|
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');
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -68,7 +68,7 @@ export default {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
flex-basis: 6rem;
|
flex-basis: 6rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
a {
|
a.sub-item-link {
|
||||||
border: none;
|
border: none;
|
||||||
margin: 0.2rem;
|
margin: 0.2rem;
|
||||||
.sub-icon-img {
|
.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