Moves item reusable methods into mixin

This commit is contained in:
Alicia Sykes 2022-02-15 11:11:40 +00:00
parent 5bc9a5f660
commit 11c59504dc
2 changed files with 145 additions and 144 deletions

View File

@ -1,6 +1,6 @@
<template ref="container"> <template ref="container">
<div :class="`item-wrapper wrap-size-${itemSize}`"> <div :class="`item-wrapper wrap-size-${itemSize}`">
<a @click="itemOpened" <a @click="beforeLaunchItem"
@mouseup.right="openContextMenu" @mouseup.right="openContextMenu"
@contextmenu.prevent @contextmenu.prevent
:href="hyperLinkHref" :href="hyperLinkHref"
@ -9,7 +9,7 @@
v-tooltip="getTooltipOptions()" v-tooltip="getTooltipOptions()"
rel="noopener noreferrer" tabindex="0" rel="noopener noreferrer" tabindex="0"
:id="`link-${id}`" :id="`link-${id}`"
:style="`--open-icon: ${getUnicodeOpeningIcon()}; color: ${color}; ${customStyles}`" :style="`--open-icon:${unicodeOpeningIcon};color:${color};background:${backgroundColor}`"
> >
<!-- Item Text --> <!-- Item Text -->
<div :class="`tile-title ${!icon? 'bounce no-icon': ''}`" :id="`tile-${id}`" > <div :class="`tile-title ${!icon? 'bounce no-icon': ''}`" :id="`tile-${id}`" >
@ -23,7 +23,7 @@
<ItemOpenMethodIcon class="opening-method-icon" :isSmall="!icon || itemSize === 'small'" <ItemOpenMethodIcon class="opening-method-icon" :isSmall="!icon || itemSize === 'small'"
:openingMethod="accumulatedTarget" position="bottom right" :openingMethod="accumulatedTarget" position="bottom right"
:hotkey="hotkey" /> :hotkey="hotkey" />
<!-- Status indicator dot (if enabled) showing weather srevice is availible --> <!-- Status indicator dot (if enabled) showing weather service is available -->
<StatusIndicator <StatusIndicator
class="status-indicator" class="status-indicator"
v-if="enableStatusCheck" v-if="enableStatusCheck"
@ -54,8 +54,6 @@
</template> </template>
<script> <script>
import axios from 'axios';
import router from '@/router';
import Icon from '@/components/LinkItems/ItemIcon.vue'; import Icon from '@/components/LinkItems/ItemIcon.vue';
import ItemOpenMethodIcon from '@/components/LinkItems/ItemOpenMethodIcon'; import ItemOpenMethodIcon from '@/components/LinkItems/ItemOpenMethodIcon';
import StatusIndicator from '@/components/LinkItems/StatusIndicator'; import StatusIndicator from '@/components/LinkItems/StatusIndicator';
@ -66,11 +64,7 @@ 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 { import { modalNames } from '@/utils/defaults';
localStorageKeys,
serviceEndpoints,
modalNames,
} from '@/utils/defaults';
export default { export default {
name: 'Item', name: 'Item',
@ -118,63 +112,54 @@ export default {
return `size-${itemSize} ${!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 */
unicodeOpeningIcon() {
switch (this.accumulatedTarget) {
case 'newtab': return '"\\f360"';
case 'sametab': return '"\\f24d"';
case 'parent': return '"\\f3bf"';
case 'top': return '"\\f102"';
case 'modal': return '"\\f2d0"';
case 'workspace': return '"\\f0b1"';
case 'clipboard': return '"\\f0ea"';
default: return '"\\f054"';
}
},
}, },
data() { data() {
return { return {
contextMenuOpen: false, editMenuOpen: false,
getId: this.id,
customStyles: { customStyles: {
color: this.color, color: this.color,
background: this.backgroundColor, background: this.backgroundColor,
}, },
statusResponse: undefined,
contextPos: {
posX: undefined,
posY: undefined,
},
editMenuOpen: false,
}; };
}, },
methods: { 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 */
itemOpened(e) { beforeLaunchItem(e) {
if (this.isEditMode) { if (this.isEditMode) { // If in edit mode, open settings, don't launch app
// If in edit mode, open settings, and don't launch app
this.openItemSettings(); this.openItemSettings();
return; return;
} }
if (e.altKey || this.accumulatedTarget === 'modal') { if (e.altKey) {
e.preventDefault(); e.preventDefault();
this.$emit('triggerModal', this.url); this.launchItem('modal');
} else if (this.accumulatedTarget === 'modal') {
this.launchItem('modal');
} else if (this.accumulatedTarget === 'workspace') { } else if (this.accumulatedTarget === 'workspace') {
router.push({ name: 'workspace', query: { url: this.url } }); this.launchItem('workspace');
} else if (this.accumulatedTarget === 'clipboard') { } else if (this.accumulatedTarget === 'clipboard') {
navigator.clipboard.writeText(this.url); this.launchItem('clipboard');
this.$toasted.show(this.$t('context-menus.item.copied-toast'));
} else {
this.$emit('itemClicked');
} }
// Clear search bar
this.$emit('itemClicked');
// Update the most/ last used ledger, for smart-sorting // Update the most/ last used ledger, for smart-sorting
if (!this.appConfig.disableSmartSort) { if (!this.appConfig.disableSmartSort) {
this.incrementMostUsedCount(this.id); this.incrementMostUsedCount(this.id);
this.incrementLastUsedCount(this.id); this.incrementLastUsedCount(this.id);
} }
}, },
/* Open custom context menu, and set position */
openContextMenu(e) {
this.contextMenuOpen = !this.contextMenuOpen;
if (e && window) {
// Calculate placement based on cursor and scroll position
this.contextPos = {
posX: e.clientX + window.pageXOffset,
posY: e.clientY + window.pageYOffset,
};
}
},
/* Closes the context menu, called when user clicks literally anywhere */
closeContextMenu() {
this.contextMenuOpen = false;
},
/* 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.description && !this.provider) return {}; // If no description, then skip
@ -194,79 +179,7 @@ export default {
classes: `item-description-tooltip tooltip-is-${this.itemSize}`, classes: `item-description-tooltip tooltip-is-${this.itemSize}`,
}; };
}, },
/* Used by certain themes (material), to show animated CSS icon */ /* Open the Edit Item modal form */
getUnicodeOpeningIcon() {
switch (this.accumulatedTarget) {
case 'newtab': return '"\\f360"';
case 'sametab': return '"\\f24d"';
case 'parent': return '"\\f3bf"';
case 'top': return '"\\f102"';
case 'modal': return '"\\f2d0"';
case 'workspace': return '"\\f0b1"';
case 'clipboard': return '"\\f0ea"';
default: return '"\\f054"';
}
},
/* Pulls together all user options, returns URL + Get params for ping endpoint */
makeApiUrl() {
const {
url, statusCheckUrl, statusCheckHeaders, statusCheckAllowInsecure, statusCheckAcceptCodes,
} = this;
const encode = (str) => encodeURIComponent(str);
this.statusResponse = undefined;
// Find base URL, where the API is hosted
const baseUrl = process.env.VUE_APP_DOMAIN || window.location.origin;
// Find correct URL to check, and encode
const urlToCheck = `?&url=${encode(statusCheckUrl || url)}`;
// Get, stringify and encode any headers
const headers = statusCheckHeaders
? `&headers=${encode(JSON.stringify(statusCheckHeaders))}` : '';
// Deterimine if user disabled security
const enableInsecure = statusCheckAllowInsecure ? '&enableInsecure=true' : '';
const acceptCodes = statusCheckAcceptCodes ? `&acceptCodes=${statusCheckAcceptCodes}` : '';
// Construct the full API endpoint's URL with GET params
return `${baseUrl}${serviceEndpoints.statusCheck}/${urlToCheck}`
+ `${headers}${enableInsecure}${acceptCodes}`;
},
/* Checks if a given service is currently online */
checkWebsiteStatus() {
const endpoint = this.makeApiUrl();
axios.get(endpoint)
.then((response) => {
if (response.data) this.statusResponse = response.data;
})
.catch(() => { // Something went very wrong.
this.statusResponse = {
statusText: 'Failed to make request',
statusSuccess: false,
};
});
},
/* Handle navigation options from the context menu */
launchItem(method) {
const { url } = this;
this.contextMenuOpen = false;
switch (method) {
case 'newtab':
window.open(url, '_blank');
break;
case 'sametab':
window.open(url, '_self');
break;
case 'modal':
this.$emit('triggerModal', url);
break;
case 'workspace':
router.push({ name: 'workspace', query: { url } });
break;
case 'clipboard':
navigator.clipboard.writeText(url);
this.$toasted.show(this.$t('context-menus.item.copied-toast'));
break;
default: window.open(url, '_blank');
}
},
/* Open the Edit Item moal form */
openItemSettings() { openItemSettings() {
this.editMenuOpen = true; this.editMenuOpen = true;
this.contextMenuOpen = false; this.contextMenuOpen = false;
@ -279,20 +192,6 @@ export default {
this.$modal.hide(modalNames.EDIT_ITEM); this.$modal.hide(modalNames.EDIT_ITEM);
this.$store.commit(StoreKeys.SET_MODAL_OPEN, false); this.$store.commit(StoreKeys.SET_MODAL_OPEN, false);
}, },
/* Used for smart-sort when sorting items by most used apps */
incrementMostUsedCount(itemId) {
const mostUsed = JSON.parse(localStorage.getItem(localStorageKeys.MOST_USED) || '{}');
let counter = mostUsed[itemId] || 0;
counter += 1;
mostUsed[itemId] = counter;
localStorage.setItem(localStorageKeys.MOST_USED, JSON.stringify(mostUsed));
},
/* Used for smart-sort when sorting by last used apps */
incrementLastUsedCount(itemId) {
const lastUsed = JSON.parse(localStorage.getItem(localStorageKeys.LAST_USED) || '{}');
lastUsed[itemId] = new Date().getTime();
localStorage.setItem(localStorageKeys.LAST_USED, JSON.stringify(lastUsed));
},
/* 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.id}`);
@ -348,20 +247,16 @@ export default {
box-shadow: var(--item-hover-shadow); box-shadow: var(--item-hover-shadow);
background: var(--item-background-hover); background: var(--item-background-hover);
color: var(--item-text-color-hover); color: var(--item-text-color-hover);
// position: relative;
// .tile-title span.text {
// white-space: pre-wrap;
// }
} }
&:focus { &:focus {
outline: 2px solid var(--primary); outline: 2px solid var(--primary);
} }
&.short:not(.size-large) {
height: 2rem;
}
&.add-new { &.add-new {
border: 2px dashed var(--primary) !important; border: 2px dashed var(--primary) !important;
} }
&.short:not(.size-large) {
height: 2rem;
}
} }
/* Text in tile */ /* Text in tile */
@ -410,13 +305,13 @@ export default {
} }
} }
/* Apply transofmation of icons on hover */ /* Apply transformation of icons on hover */
.tile-icon, .tile-svg { .tile-icon, .tile-svg {
filter: var(--item-icon-transform-hover); filter: var(--item-icon-transform-hover);
} }
} }
/* Edit Mode Icon */ /* Edit icon, visible in edit mode */
.item .edit-mode-item { .item .edit-mode-item {
width: 1rem; width: 1rem;
height: 1rem; height: 1rem;
@ -425,6 +320,10 @@ export default {
right: 0.2rem; right: 0.2rem;
} }
p.description {
display: none; // By default, we don't show the description
}
/* Specify layout for alternate sized icons */ /* Specify layout for alternate sized icons */
.item { .item {
/* Small Tile Specific Themes */ /* Small Tile Specific Themes */
@ -506,9 +405,6 @@ export default {
} }
} }
} }
p.description {
display: none; // By default, we don't show the description
}
&:before { // Certain themes (e.g. material) show css animated fas icon on hover &:before { // Certain themes (e.g. material) show css animated fas icon on hover
display: none; display: none;
font-family: FontAwesome; font-family: FontAwesome;

View File

@ -1,7 +1,23 @@
/** Reusable mixin for items */ /** Reusable mixin for items */
import { openingMethod as defaultOpeningMethod } from '@/utils/defaults'; import axios from 'axios';
import router from '@/router';
import {
openingMethod as defaultOpeningMethod,
serviceEndpoints,
localStorageKeys,
} from '@/utils/defaults';
export default { export default {
data() {
return {
statusResponse: undefined,
contextMenuOpen: false,
contextPos: {
posX: undefined,
posY: undefined,
},
};
},
computed: { computed: {
appConfig() { appConfig() {
return this.$store.getters.appConfig; return this.$store.getters.appConfig;
@ -32,6 +48,95 @@ export default {
const noAnchorNeeded = ['modal', 'workspace', 'clipboard']; const noAnchorNeeded = ['modal', 'workspace', 'clipboard'];
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 */
makeApiUrl() {
const {
url, statusCheckUrl, statusCheckHeaders, statusCheckAllowInsecure, statusCheckAcceptCodes,
} = this;
const encode = (str) => encodeURIComponent(str);
this.statusResponse = undefined;
// Find base URL, where the API is hosted
const baseUrl = process.env.VUE_APP_DOMAIN || window.location.origin;
// Find correct URL to check, and encode
const urlToCheck = `?&url=${encode(statusCheckUrl || url)}`;
// Get, stringify and encode any headers
const headers = statusCheckHeaders
? `&headers=${encode(JSON.stringify(statusCheckHeaders))}` : '';
// Deterimine if user disabled security
const enableInsecure = statusCheckAllowInsecure ? '&enableInsecure=true' : '';
const acceptCodes = statusCheckAcceptCodes ? `&acceptCodes=${statusCheckAcceptCodes}` : '';
// Construct the full API endpoint's URL with GET params
return `${baseUrl}${serviceEndpoints.statusCheck}/${urlToCheck}`
+ `${headers}${enableInsecure}${acceptCodes}`;
},
/* Checks if a given service is currently online */
checkWebsiteStatus() {
const endpoint = this.makeApiUrl();
axios.get(endpoint)
.then((response) => {
if (response.data) this.statusResponse = response.data;
})
.catch(() => { // Something went very wrong.
this.statusResponse = {
statusText: 'Failed to make request',
statusSuccess: false,
};
});
},
},
methods: {
/* Open item, using specified method */
launchItem(method, link) {
const url = link || this.url;
this.contextMenuOpen = false;
switch (method) {
case 'newtab':
window.open(url, '_blank');
break;
case 'sametab':
window.open(url, '_self');
break;
case 'modal':
this.$emit('triggerModal', url);
break;
case 'workspace':
router.push({ name: 'workspace', query: { url } });
break;
case 'clipboard':
navigator.clipboard.writeText(url);
this.$toasted.show(this.$t('context-menus.item.copied-toast'));
break;
default: window.open(url, '_blank');
}
},
/* Open custom context menu, and set position */
openContextMenu(e) {
this.contextMenuOpen = !this.contextMenuOpen;
if (e && window) {
// Calculate placement based on cursor and scroll position
this.contextPos = {
posX: e.clientX + window.pageXOffset,
posY: e.clientY + window.pageYOffset,
};
}
},
/* Closes the context menu, called when user clicks literally anywhere */
closeContextMenu() {
this.contextMenuOpen = false;
},
/* Used for smart-sort when sorting items by most used apps */
incrementMostUsedCount(itemId) {
const mostUsed = JSON.parse(localStorage.getItem(localStorageKeys.MOST_USED) || '{}');
let counter = mostUsed[itemId] || 0;
counter += 1;
mostUsed[itemId] = counter;
localStorage.setItem(localStorageKeys.MOST_USED, JSON.stringify(mostUsed));
},
/* Used for smart-sort when sorting by last used apps */
incrementLastUsedCount(itemId) {
const lastUsed = JSON.parse(localStorage.getItem(localStorageKeys.LAST_USED) || '{}');
lastUsed[itemId] = new Date().getTime();
localStorage.setItem(localStorageKeys.LAST_USED, JSON.stringify(lastUsed));
},
}, },
methods: {},
}; };