♻️ Refactors item so props is single object

This commit is contained in:
Alicia Sykes 2022-04-01 23:52:29 +01:00
parent 51de80a735
commit acfb7f267a
4 changed files with 100 additions and 92 deletions

View File

@ -1,30 +1,32 @@
<template ref="container"> <template ref="container">
<div :class="`item-wrapper wrap-size-${itemSize}`" > <div :class="`item-wrapper wrap-size-${size}`" >
<a @click="itemClicked" <a @click="itemClicked"
@long-press="openContextMenu" @long-press="openContextMenu"
v-longPress="500"
@mouseup.right="openContextMenu"
@contextmenu.prevent @contextmenu.prevent
:href="url" @mouseup.right="openContextMenu"
v-longPress="true"
:href="item.url"
:target="anchorTarget" :target="anchorTarget"
:class="`item ${makeClassList}`" :class="`item ${makeClassList}`"
v-tooltip="getTooltipOptions()" v-tooltip="getTooltipOptions()"
rel="noopener noreferrer" tabindex="0" rel="noopener noreferrer" tabindex="0"
:id="`link-${id}`" :id="`link-${item.id}`"
:style="`--open-icon:${unicodeOpeningIcon};color:${color};background:${backgroundColor}`" :style=
"`--open-icon:${unicodeOpeningIcon};color:${item.color};background:${item.backgroundColor}`"
> >
<!-- Item Text --> <!-- Item Text -->
<div :class="`tile-title ${!icon? 'bounce no-icon': ''}`" :id="`tile-${id}`" > <div :class="`tile-title ${!item.icon? 'bounce no-icon': ''}`" :id="`tile-${item.id}`" >
<span class="text">{{ title }}</span> <span class="text">{{ item.title }}</span>
<p class="description">{{ description }}</p> <p class="description">{{ item.description }}</p>
</div> </div>
<!-- Item Icon --> <!-- Item Icon -->
<Icon :icon="icon" :url="url" :size="itemSize" :color="color" <Icon :icon="item.icon" :url="item.url" :size="size" :color="item.color"
v-bind:style="customStyles" class="bounce" /> v-bind:style="customStyles" class="bounce" />
<!-- Small icon, showing opening method on hover --> <!-- Small icon, showing opening method on hover -->
<ItemOpenMethodIcon class="opening-method-icon" :isSmall="!icon || itemSize === 'small'" <ItemOpenMethodIcon class="opening-method-icon"
:isSmall="!item.icon || size === 'small'"
:openingMethod="accumulatedTarget" position="bottom right" :openingMethod="accumulatedTarget" position="bottom right"
:hotkey="hotkey" /> :hotkey="item.hotkey" />
<!-- Status indicator dot (if enabled) showing weather service is available --> <!-- Status indicator dot (if enabled) showing weather service is available -->
<StatusIndicator <StatusIndicator
class="status-indicator" class="status-indicator"
@ -41,15 +43,15 @@
v-click-outside="closeContextMenu" v-click-outside="closeContextMenu"
:posX="contextPos.posX" :posX="contextPos.posX"
:posY="contextPos.posY" :posY="contextPos.posY"
:id="`context-menu-${id}`" :id="`context-menu-${item.id}`"
@launchItem="launchItem" @launchItem="launchItem"
@openItemSettings="openItemSettings" @openItemSettings="openItemSettings"
@openMoveItemMenu="openMoveItemMenu" @openMoveItemMenu="openMoveItemMenu"
@openDeleteItem="openDeleteItem" @openDeleteItem="openDeleteItem"
/> />
<!-- Edit and move item menu modals --> <!-- Edit and move item menu modals -->
<MoveItemTo v-if="isEditMode" :itemId="id" /> <MoveItemTo v-if="isEditMode" :itemId="item.id" />
<EditItem v-if="editMenuOpen" :itemId="id" <EditItem v-if="editMenuOpen" :itemId="item.id"
@closeEditMenu="closeEditMenu" @closeEditMenu="closeEditMenu"
:isNew="isAddNew" :parentSectionTitle="parentSectionTitle" /> :isNew="isAddNew" :parentSectionTitle="parentSectionTitle" />
</div> </div>
@ -64,7 +66,7 @@ import MoveItemTo from '@/components/InteractiveEditor/MoveItemTo';
import ContextMenu from '@/components/LinkItems/ItemContextMenu'; import ContextMenu from '@/components/LinkItems/ItemContextMenu';
import StoreKeys from '@/utils/StoreMutations'; import StoreKeys from '@/utils/StoreMutations';
import ItemMixin from '@/mixins/ItemMixin'; import ItemMixin from '@/mixins/ItemMixin';
import { targetValidator } from '@/utils/ConfigHelpers'; // import { targetValidator } from '@/utils/ConfigHelpers';
import EditModeIcon from '@/assets/interface-icons/interactive-editor-edit-mode.svg'; import EditModeIcon from '@/assets/interface-icons/interactive-editor-edit-mode.svg';
import { modalNames } from '@/utils/defaults'; import { modalNames } from '@/utils/defaults';
@ -72,28 +74,7 @@ export default {
name: 'Item', name: 'Item',
mixins: [ItemMixin], mixins: [ItemMixin],
props: { props: {
id: String, // The unique ID of a tile (e.g. 001) itemSize: String,
title: String, // The main text of tile, required
subtitle: String, // Optional sub-text
description: String, // Optional tooltip hover text
icon: String, // Optional path to icon, within public/img/tile-icons
color: String, // Optional text and icon color, specified in hex code
backgroundColor: String, // Optional item background color
url: String, // URL to the resource, optional but recommended
provider: String, // Optional provider name, for external apps
hotkey: Number, // Shortcut for quickly launching app
target: { // Where resource will open, either 'newtab', 'sametab' or 'modal'
type: String,
validator: targetValidator,
},
itemSize: String, // Item size: small | medium | large
enableStatusCheck: Boolean, // Should run status checks
statusCheckHeaders: Object, // Custom status check headers
statusCheckUrl: String, // Custom URL for status check endpoint
statusCheckInterval: Number, // Num seconds beteween repeating checks
statusCheckAllowInsecure: Boolean, // Status check ignore SSL certs
statusCheckAcceptCodes: String, // Allow status checks to pass with a code other than 200
statusCheckMaxRedirects: Number, // Specify max number of redirects
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
}, },
@ -109,11 +90,10 @@ export default {
computed: { computed: {
/* Based on item props, adjust class names */ /* Based on item props, adjust class names */
makeClassList() { makeClassList() {
const { const { isAddNew, isEditMode, size } = this;
icon, itemSize, isAddNew, isEditMode, const { icon } = this.item;
} = this; return `size-${size} ${!icon ? 'short' : ''} `
return `size-${itemSize} ${!icon ? 'short' : ''} ` + `${isAddNew ? 'add-new' : ''} ${isEditMode ? 'is-edit-mode' : ''}`;
+ `${isAddNew ? 'add-new' : ''} ${isEditMode ? 'is-edit-mode' : ''}`;
}, },
/* Used by certain themes (material), to show animated CSS icon */ /* Used by certain themes (material), to show animated CSS icon */
unicodeOpeningIcon() { unicodeOpeningIcon() {
@ -133,19 +113,19 @@ export default {
return { return {
editMenuOpen: false, editMenuOpen: false,
customStyles: { customStyles: {
color: this.color, color: this.item.color,
background: this.backgroundColor, background: this.item.backgroundColor,
}, },
}; };
}, },
methods: { methods: {
/* Returns configuration object for the tooltip */ /* Returns configuration object for the tooltip */
getTooltipOptions() { getTooltipOptions() {
if (!this.description && !this.provider) return {}; // If no description, then skip if (!this.item.description && !this.item.provider) return {}; // If no description, then skip
const description = this.description ? this.description : ''; const description = this.item.description || '';
const providerText = this.provider ? `<b>Provider</b>: ${this.provider}` : ''; const providerText = this.item.provider ? `<b>Provider</b>: ${this.item.provider}` : '';
const lb1 = description && providerText ? '<br>' : ''; const lb1 = description && providerText ? '<br>' : '';
const hotkeyText = this.hotkey ? `<br>Press '${this.hotkey}' to launch` : ''; const hotkeyText = this.item.hotkey ? `<br>Press '${this.item.hotkey}' to launch` : '';
const tooltipText = providerText + lb1 + description + hotkeyText; const tooltipText = providerText + lb1 + description + hotkeyText;
const editText = this.$t('interactive-editor.edit-section.edit-tooltip'); const editText = this.$t('interactive-editor.edit-section.edit-tooltip');
return { return {
@ -155,7 +135,7 @@ export default {
html: true, html: true,
placement: this.statusResponse ? 'left' : 'auto', placement: this.statusResponse ? 'left' : 'auto',
delay: { show: 600, hide: 200 }, delay: { show: 600, hide: 200 },
classes: `item-description-tooltip tooltip-is-${this.itemSize}`, classes: `item-description-tooltip tooltip-is-${this.size}`,
}; };
}, },
openItemSettings() { openItemSettings() {
@ -172,14 +152,14 @@ export default {
}, },
/* Open the modal for moving/ copying item to other section */ /* Open the modal for moving/ copying item to other section */
openMoveItemMenu() { openMoveItemMenu() {
this.$modal.show(`${modalNames.MOVE_ITEM_TO}-${this.id}`); this.$modal.show(`${modalNames.MOVE_ITEM_TO}-${this.item.id}`);
this.$store.commit(StoreKeys.SET_MODAL_OPEN, true); this.$store.commit(StoreKeys.SET_MODAL_OPEN, true);
this.closeContextMenu(); this.closeContextMenu();
}, },
/* Deletes the current item from the state */ /* Deletes the current item from the state */
openDeleteItem() { openDeleteItem() {
const parentSection = this.$store.getters.getParentSectionOfItem(this.id); const parentSection = this.$store.getters.getParentSectionOfItem(this.item.id);
const payload = { itemId: this.id, sectionName: parentSection.name }; const payload = { itemId: this.item.id, sectionName: parentSection.name };
this.$store.commit(StoreKeys.REMOVE_ITEM, payload); this.$store.commit(StoreKeys.REMOVE_ITEM, payload);
this.closeContextMenu(); this.closeContextMenu();
}, },

View File

@ -36,26 +36,10 @@
</div> </div>
<Item <Item
v-else v-else
:id="item.id" :item="item"
:key="item.id" :key="item.id"
: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" :itemSize="itemSize"
:hotkey="item.hotkey"
:provider="item.provider"
:parentSectionTitle="title" :parentSectionTitle="title"
:enableStatusCheck="item.statusCheck !== undefined ? item.statusCheck : enableStatusCheck"
:statusCheckInterval="statusCheckInterval"
:statusCheckAllowInsecure="item.statusCheckAllowInsecure"
:statusCheckAcceptCodes="item.statusCheckAcceptCodes"
:statusCheckMaxRedirects="item.statusCheckMaxRedirects"
@itemClicked="$emit('itemClicked')" @itemClicked="$emit('itemClicked')"
@triggerModal="triggerModal" @triggerModal="triggerModal"
:isAddNew="false" :isAddNew="false"
@ -63,12 +47,14 @@
</template> </template>
<!-- When in edit mode, show additional item, for Add New item --> <!-- When in edit mode, show additional item, for Add New item -->
<Item v-if="isEditMode" <Item v-if="isEditMode"
:item="{
icon: ':heavy_plus_sign:',
title: 'Add New Item',
description: 'Click to add new item',
id: 'add-new',
}"
:isAddNew="true" :isAddNew="true"
:parentSectionTitle="title" :parentSectionTitle="title"
icon=":heavy_plus_sign:"
id="add-new"
title="Add New Item"
description="Click to add new item"
key="add-new" key="add-new"
class="add-new-item" class="add-new-item"
:itemSize="itemSize" :itemSize="itemSize"
@ -216,18 +202,18 @@ export default {
} }
return styles; return styles;
}, },
/* Determines if user has enabled online status checks */ // /* Determines if user has enabled online status checks */
enableStatusCheck() { // enableStatusCheck() {
return this.appConfig.statusCheck || false; // return this.appConfig.statusCheck || false;
}, // },
/* Determine how often to re-fire status checks */ // /* Determine how often to re-fire status checks */
statusCheckInterval() { // statusCheckInterval() {
let interval = this.appConfig.statusCheckInterval; // let interval = this.appConfig.statusCheckInterval;
if (!interval) return 0; // if (!interval) return 0;
if (interval > 60) interval = 60; // if (interval > 60) interval = 60;
if (interval < 1) interval = 0; // if (interval < 1) interval = 0;
return interval; // return interval;
}, // },
}, },
methods: { methods: {
/* Opens the iframe modal */ /* Opens the iframe modal */

View File

@ -1,8 +1,9 @@
<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 <Item
v-for="(item, index) in items" v-for="(item, index) in items"
:item="item"
:id="`${index}_${makeId(item.title)}`" :id="`${index}_${makeId(item.title)}`"
:key="`${index}_${makeId(item.title)}`" :key="`${index}_${makeId(item.title)}`"
:url="item.url" :url="item.url"
@ -34,6 +35,9 @@
@navigateToSection="navigateToSection" @navigateToSection="navigateToSection"
/> />
</div> </div>
<div v-if="selected && !showAll && !widgets && items.length < 1" class="empty-section">
<p>{{ $t('home.no-items-section') }}</p>
</div>
<IframeModal <IframeModal
:ref="`iframeModal-${groupId}`" :ref="`iframeModal-${groupId}`"
:name="`iframeModal-${groupId}`" :name="`iframeModal-${groupId}`"
@ -131,9 +135,18 @@ export default {
.minimal-widget-wrap { .minimal-widget-wrap {
padding: 1rem; padding: 1rem;
} }
.empty-section {
padding: 1rem;
margin: 0.5rem auto;
color: var(--minimal-view-group-color);
font-size: 1rem;
font-style: italic;
opacity: var(--dimming-factor);
}
&.selected { &.selected {
border: 1px solid var(--minimal-view-group-color); border: 1px solid var(--minimal-view-group-color);
grid-column-start: span var(--col-count, 3); grid-column-start: span var(--col-count, 3);
&:not(.show-all) { min-height: 300px; }
} }
&.show-all { &.show-all {
border: none; border: none;

View File

@ -6,12 +6,17 @@ import {
openingMethod as defaultOpeningMethod, openingMethod as defaultOpeningMethod,
serviceEndpoints, serviceEndpoints,
localStorageKeys, localStorageKeys,
iconSize as defaultSize,
} from '@/utils/defaults'; } from '@/utils/defaults';
export default { export default {
directives: { directives: {
longPress, longPress,
}, },
props: {
item: Object,
isAddNew: Boolean,
},
data() { data() {
return { return {
statusResponse: undefined, statusResponse: undefined,
@ -29,6 +34,25 @@ export default {
isEditMode() { isEditMode() {
return this.$store.state.editMode; return this.$store.state.editMode;
}, },
size() {
const validSizes = ['small', 'medium', 'large'];
if (this.itemSize && validSizes.includes(this.itemSize)) return this.itemSize;
return this.appConfig.iconSize || defaultSize;
},
/* Determines if user has enabled online status checks */
enableStatusCheck() {
const globalPref = this.appConfig.statusCheck || false;
const itemPref = this.item.statusCheck || false;
return itemPref || globalPref;
},
/* Determine how often to re-fire status checks */
statusCheckInterval() {
let interval = this.item.statusCheckInterval || this.appConfig.statusCheckInterval;
if (!interval) return 0;
if (interval > 60) interval = 60;
if (interval < 1) interval = 0;
return interval;
},
accumulatedTarget() { accumulatedTarget() {
return this.target || this.appConfig.defaultOpeningMethod || defaultOpeningMethod; return this.target || this.appConfig.defaultOpeningMethod || defaultOpeningMethod;
}, },
@ -53,7 +77,7 @@ export default {
return noAnchorNeeded.includes(this.accumulatedTarget) ? nothing : url; return noAnchorNeeded.includes(this.accumulatedTarget) ? nothing : url;
}, },
/* Pulls together all user options, returns URL + Get params for ping endpoint */ /* Pulls together all user options, returns URL + Get params for ping endpoint */
makeApiUrl() { statusCheckApiUrl() {
const { const {
url, url,
statusCheckUrl, statusCheckUrl,
@ -61,7 +85,7 @@ export default {
statusCheckAllowInsecure, statusCheckAllowInsecure,
statusCheckAcceptCodes, statusCheckAcceptCodes,
statusCheckMaxRedirects, statusCheckMaxRedirects,
} = this; } = this.item;
const encode = (str) => encodeURIComponent(str); const encode = (str) => encodeURIComponent(str);
this.statusResponse = undefined; this.statusResponse = undefined;
// Find base URL, where the API is hosted // Find base URL, where the API is hosted
@ -79,9 +103,16 @@ export default {
return `${baseUrl}${serviceEndpoints.statusCheck}/${urlToCheck}` return `${baseUrl}${serviceEndpoints.statusCheck}/${urlToCheck}`
+ `${headers}${enableInsecure}${acceptCodes}${maxRedirects}`; + `${headers}${enableInsecure}${acceptCodes}${maxRedirects}`;
}, },
customStyle() {
return `--open-icon:${this.unicodeOpeningIcon};`
+ `color:${this.item.color};`
+ `background:${this.item.backgroundColor}`;
},
},
methods: {
/* Checks if a given service is currently online */ /* Checks if a given service is currently online */
checkWebsiteStatus() { checkWebsiteStatus() {
const endpoint = this.makeApiUrl(); const endpoint = this.statusCheckApiUrl;
axios.get(endpoint) axios.get(endpoint)
.then((response) => { .then((response) => {
if (response.data) this.statusResponse = response.data; if (response.data) this.statusResponse = response.data;
@ -93,8 +124,6 @@ export default {
}; };
}); });
}, },
},
methods: {
/* 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) {
if (this.isEditMode) { if (this.isEditMode) {