♻️ 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">
<div :class="`item-wrapper wrap-size-${itemSize}`" >
<div :class="`item-wrapper wrap-size-${size}`" >
<a @click="itemClicked"
@long-press="openContextMenu"
v-longPress="500"
@mouseup.right="openContextMenu"
@contextmenu.prevent
:href="url"
@mouseup.right="openContextMenu"
v-longPress="true"
:href="item.url"
:target="anchorTarget"
:class="`item ${makeClassList}`"
v-tooltip="getTooltipOptions()"
rel="noopener noreferrer" tabindex="0"
:id="`link-${id}`"
:style="`--open-icon:${unicodeOpeningIcon};color:${color};background:${backgroundColor}`"
:id="`link-${item.id}`"
:style=
"`--open-icon:${unicodeOpeningIcon};color:${item.color};background:${item.backgroundColor}`"
>
<!-- Item Text -->
<div :class="`tile-title ${!icon? 'bounce no-icon': ''}`" :id="`tile-${id}`" >
<span class="text">{{ title }}</span>
<p class="description">{{ description }}</p>
<div :class="`tile-title ${!item.icon? 'bounce no-icon': ''}`" :id="`tile-${item.id}`" >
<span class="text">{{ item.title }}</span>
<p class="description">{{ item.description }}</p>
</div>
<!-- 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" />
<!-- 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"
:hotkey="hotkey" />
:hotkey="item.hotkey" />
<!-- Status indicator dot (if enabled) showing weather service is available -->
<StatusIndicator
class="status-indicator"
@ -41,15 +43,15 @@
v-click-outside="closeContextMenu"
:posX="contextPos.posX"
:posY="contextPos.posY"
:id="`context-menu-${id}`"
:id="`context-menu-${item.id}`"
@launchItem="launchItem"
@openItemSettings="openItemSettings"
@openMoveItemMenu="openMoveItemMenu"
@openDeleteItem="openDeleteItem"
/>
<!-- Edit and move item menu modals -->
<MoveItemTo v-if="isEditMode" :itemId="id" />
<EditItem v-if="editMenuOpen" :itemId="id"
<MoveItemTo v-if="isEditMode" :itemId="item.id" />
<EditItem v-if="editMenuOpen" :itemId="item.id"
@closeEditMenu="closeEditMenu"
:isNew="isAddNew" :parentSectionTitle="parentSectionTitle" />
</div>
@ -64,7 +66,7 @@ import MoveItemTo from '@/components/InteractiveEditor/MoveItemTo';
import ContextMenu from '@/components/LinkItems/ItemContextMenu';
import StoreKeys from '@/utils/StoreMutations';
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 { modalNames } from '@/utils/defaults';
@ -72,28 +74,7 @@ export default {
name: 'Item',
mixins: [ItemMixin],
props: {
id: String, // The unique ID of a tile (e.g. 001)
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
itemSize: String,
parentSectionTitle: String, // Title of parent section (for add new)
isAddNew: Boolean, // Only set if 'fake' item used as Add New button
},
@ -109,11 +90,10 @@ export default {
computed: {
/* Based on item props, adjust class names */
makeClassList() {
const {
icon, itemSize, isAddNew, isEditMode,
} = this;
return `size-${itemSize} ${!icon ? 'short' : ''} `
+ `${isAddNew ? 'add-new' : ''} ${isEditMode ? 'is-edit-mode' : ''}`;
const { isAddNew, isEditMode, size } = this;
const { icon } = this.item;
return `size-${size} ${!icon ? 'short' : ''} `
+ `${isAddNew ? 'add-new' : ''} ${isEditMode ? 'is-edit-mode' : ''}`;
},
/* Used by certain themes (material), to show animated CSS icon */
unicodeOpeningIcon() {
@ -133,19 +113,19 @@ export default {
return {
editMenuOpen: false,
customStyles: {
color: this.color,
background: this.backgroundColor,
color: this.item.color,
background: this.item.backgroundColor,
},
};
},
methods: {
/* Returns configuration object for the tooltip */
getTooltipOptions() {
if (!this.description && !this.provider) return {}; // If no description, then skip
const description = this.description ? this.description : '';
const providerText = this.provider ? `<b>Provider</b>: ${this.provider}` : '';
if (!this.item.description && !this.item.provider) return {}; // If no description, then skip
const description = this.item.description || '';
const providerText = this.item.provider ? `<b>Provider</b>: ${this.item.provider}` : '';
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 editText = this.$t('interactive-editor.edit-section.edit-tooltip');
return {
@ -155,7 +135,7 @@ export default {
html: true,
placement: this.statusResponse ? 'left' : 'auto',
delay: { show: 600, hide: 200 },
classes: `item-description-tooltip tooltip-is-${this.itemSize}`,
classes: `item-description-tooltip tooltip-is-${this.size}`,
};
},
openItemSettings() {
@ -172,14 +152,14 @@ export default {
},
/* Open the modal for moving/ copying item to other section */
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.closeContextMenu();
},
/* Deletes the current item from the state */
openDeleteItem() {
const parentSection = this.$store.getters.getParentSectionOfItem(this.id);
const payload = { itemId: this.id, sectionName: parentSection.name };
const parentSection = this.$store.getters.getParentSectionOfItem(this.item.id);
const payload = { itemId: this.item.id, sectionName: parentSection.name };
this.$store.commit(StoreKeys.REMOVE_ITEM, payload);
this.closeContextMenu();
},

View File

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

View File

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

View File

@ -6,12 +6,17 @@ import {
openingMethod as defaultOpeningMethod,
serviceEndpoints,
localStorageKeys,
iconSize as defaultSize,
} from '@/utils/defaults';
export default {
directives: {
longPress,
},
props: {
item: Object,
isAddNew: Boolean,
},
data() {
return {
statusResponse: undefined,
@ -29,6 +34,25 @@ export default {
isEditMode() {
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() {
return this.target || this.appConfig.defaultOpeningMethod || defaultOpeningMethod;
},
@ -53,7 +77,7 @@ export default {
return noAnchorNeeded.includes(this.accumulatedTarget) ? nothing : url;
},
/* Pulls together all user options, returns URL + Get params for ping endpoint */
makeApiUrl() {
statusCheckApiUrl() {
const {
url,
statusCheckUrl,
@ -61,7 +85,7 @@ export default {
statusCheckAllowInsecure,
statusCheckAcceptCodes,
statusCheckMaxRedirects,
} = this;
} = this.item;
const encode = (str) => encodeURIComponent(str);
this.statusResponse = undefined;
// Find base URL, where the API is hosted
@ -79,9 +103,16 @@ export default {
return `${baseUrl}${serviceEndpoints.statusCheck}/${urlToCheck}`
+ `${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 */
checkWebsiteStatus() {
const endpoint = this.makeApiUrl();
const endpoint = this.statusCheckApiUrl;
axios.get(endpoint)
.then((response) => {
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 */
itemClicked(e) {
if (this.isEditMode) {