🔀 Merge pull request #94 from Lissy93/FEATURE/service-keyboard-shortcuts

[FEATURE] Support custom keybindings
This commit is contained in:
Alicia Sykes 2021-07-19 22:23:53 +01:00 committed by GitHub
commit f22b1b0d42
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 95 additions and 17 deletions

View File

@ -17,17 +17,18 @@
## Features 🌈
- Instant search by name, domain and tags - just start typing
- Full keyboard shortcuts for navigation, searching and launching
- Multiple color themes, with easy method for adding more
- Easy to customize every part of your dashboard, layout, icon sizes and colors etc
- Many options for icons, including full Font-Awesome support and the ability to auto-fetch icon from URLs favicon
- Full customizable keyboard shortcuts for navigation, filtering and launching apps
- Multiple built-in color themes, with UI color configurator and support for custom CSS
- Easy to customize every part of your dashboard, layout, icon sizes, behavior and colors etc
- Many options for icons, including Font-Awesome support, auto-fetching service favicon, images and emojis
- Option to show service status for each of your apps / links, for basic availability and uptime monitoring
- Multiple ways of opening apps, either in your browser, a pop-up modal or workspace view
- Option for full-screen background image, custom nav-bar links, html footer, title, and more
- Encrypted cloud backup and restore feature available
- Optional authentication, requiring user to log in
- Easy single-file YAML-based configuration
- Optional authentication, requiring admins and non-privileged users to log in
- Easy single-file YAML-based configuration, which can also be configured directly through the UI
- Small bundle size, fully responsive UI and PWA makes the app easy to use on any device
- Easy to setup with Docker, or on bare metal, or with 1-Click cloud deployment
- Plus lots more...
## Demo ⚡
@ -268,6 +269,29 @@ Here's a quick demo of the workspace view:
---
## Searching and Shortcuts 🔎
Quickly finding and launching applications is the primary aim of Dashy. To that end instant search and customizable keyboard shortcuts are built-in.
To start filtering, just start typing. No need to select the search bar or use any special key. You can then use either the tab key or arrow keys to select and move between results, and hit enter to launch the currently selected application. You can also use `Alt + Enter` on a selected app to launch it in a popup modal, `Ctrl + Enter` to open in new tab, or right-click on it to see all opening methods.
For apps that you use regularly, you can set a custom keybinding. Use the `hotkey` parameter on a certain item to specify a numeric key, between `0 - 9`. You can then launch that app, by just pressing that key, which is very useful for services you use frequently.
Example:
```yaml
- title: Bookstack
icon: far fa-books
url: https://bookstack.local/
hotkey: 8
```
Hit `Esc` at anytime to close any open apps, clear the search field, or hide any modals.
**[⬆️ Back to Top](#dashy)**
---
## Config Editor ⚙️
From the Settings Menu in Dashy, you can download, backup, edit and rest your config. An interactive editor makes editing the config file easy, it will tell you if you've got any errors. After making your changes, you can either apply them locally, or export into your main config file. After saving to the config file to the disk, the app will need to be rebuilt. This will happen automatically, but may take a few minutes. You can also manually trigger a rebuild from the Settings Menu. A full list of available config options can be found [here](./docs/configuring.md). It's recommend to make a backup of your configuration, as you can then restore it into a new instance of Dashy, without having to set it up again. [json2yaml](https://www.json2yaml.com/) is very useful for converting between YAML to JSON and visa versa.

View File

@ -118,6 +118,7 @@ To disallow any changes from being written to disk via the UI config editor, set
**`url`** | `string` | Required | The URL / location of web address for when the item is clicked
**`icon`** | `string` | _Optional_ | The icon for a given item. Can be a font-awesome icon, favicon, remote URL or local URL. See [`item.icon`](#sectionicon-and-sectionitemicon)
**`target`** | `string` | _Optional_ | The opening method for when the item is clicked, either `newtab`, `sametab`, `modal` or `workspace`. Where `newtab` will open the link in a new tab, `sametab` will open it in the current tab, and `modal` will open a pop-up modal with the content displayed within that iframe. Note that for the iframe to load, you must have set the CORS headers to either allow `*` ot allow the domain that you are hosting Dashy on, for some websites and self-hosted services, this is already set.
**`hotkey`** | `number` | _Optional_ | Give frequently opened applications a numeric hotkey, between `0 - 9`. You can then just press that key to launch that application.
**`statusCheck`** | `boolean` | _Optional_ | When set to `true`, Dashy will ping the URL associated with the current service, and display its status as a dot next to the item. The value here will override `appConfig.statusCheck` so you can turn off or on checks for a given service. Defaults to `appConfig.statusCheck`, falls back to `false`
**`statusCheckUrl`** | `string` | _Optional_ | If you've enabled `statusCheck`, and want to use a different URL to what is defined under the item, then specify it here
**`statusCheckHeaders`** | `object` | _Optional_ | If you're endpoint requires any specific headers for the status checking, then define them here

View File

@ -1,6 +1,6 @@
{
"name": "Dashy",
"version": "1.4.0",
"version": "1.4.1",
"license": "MIT",
"main": "server",
"scripts": {

View File

@ -20,8 +20,9 @@
<Icon :icon="icon" :url="url" :size="itemSize" :color="color"
v-bind:style="customStyles" class="bounce" />
<!-- Small icon, showing opening method on hover -->
<ItemOpenMethodIcon class="opening-method-icon" :isSmall="!icon" :openingMethod="target"
:position="itemSize === 'medium'? 'bottom right' : 'top right'"/>
<ItemOpenMethodIcon class="opening-method-icon" :isSmall="!icon || itemSize === 'small'"
:openingMethod="target" :position="itemSize === 'medium'? 'bottom right' : 'top right'"
:hotkey="hotkey" />
<!-- Status indicator dot (if enabled) showing weather srevice is availible -->
<StatusIndicator
class="status-indicator"
@ -60,6 +61,7 @@ export default {
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
hotkey: Number, // Shortcut for quickly launching app
target: { // Where resource will open, either 'newtab', 'sametab' or 'modal'
type: String,
default: 'newtab',
@ -119,9 +121,10 @@ export default {
},
/* Returns configuration object for the tooltip */
getTooltipOptions() {
const hotkeyText = this.hotkey ? `\nPress '${this.hotkey}' to launch` : '';
return {
disabled: !this.description,
content: this.description,
content: this.description + hotkeyText,
trigger: 'hover focus',
hideOnTargetClick: true,
html: false,

View File

@ -30,6 +30,7 @@
:statusCheckUrl="item.statusCheckUrl"
:statusCheckHeaders="item.statusCheckHeaders"
:itemSize="newItemSize"
:hotkey="item.hotkey"
:enableStatusCheck="shouldEnableStatusCheck(item.statusCheck)"
:statusCheckInterval="getStatusCheckInterval()"
@itemClicked="$emit('itemClicked')"

View File

@ -1,9 +1,14 @@
<template>
<div :class="makeClass(position, isSmall, isTransparent)">
<NewTabOpenIcon v-if="openingMethod === 'newtab'" />
<SameTabOpenIcon v-else-if="openingMethod === 'sametab'" />
<IframeOpenIcon v-else-if="openingMethod === 'modal'" />
<WorkspaceOpenIcon v-else-if="openingMethod === 'workspace'" />
<div>
<div :class="makeClass(position, isSmall, isTransparent)">
<NewTabOpenIcon v-if="openingMethod === 'newtab'" />
<SameTabOpenIcon v-else-if="openingMethod === 'sametab'" />
<IframeOpenIcon v-else-if="openingMethod === 'modal'" />
<WorkspaceOpenIcon v-else-if="openingMethod === 'workspace'" />
</div>
<div v-if="hotkey" :class="`hotkey-denominator ${makeClass(position, isSmall, isTransparent)}`">
{{ hotkey }}
</div>
</div>
</template>
@ -23,6 +28,7 @@ export default {
isSmall: Boolean, // If true, will apply small class
position: String, // Position classes: top, bottom, left, right
isTransparent: Boolean, // If true, will apply opacity
hotkey: Number, // Optional hotkey to also display
},
methods: {
/* Returns custom class string, from optional props */
@ -49,7 +55,6 @@ export default {
width: 1rem;
margin: 2px;
path {
// fill: var(--primary);
fill: currentColor;
}
}
@ -68,4 +73,17 @@ export default {
}
}
div.hotkey-denominator {
position: absolute;
font-size: 0.8rem;
margin: 2px;
bottom: 2px;
color: currentColor;
border-radius: 18px;
border: 1px solid currentColor;
padding: 0.1rem 0.4rem 0.2rem 0.4rem;
&.top { right: 0; } // Position opposite of opening method icon
&.bottom { left: 0; }
}
</style>

View File

@ -18,6 +18,7 @@
<script>
import ArrowKeyNavigation from '@/utils/ArrowKeyNavigation';
import { getCustomKeyShortcuts } from '@/utils/ConfigHelpers';
export default {
name: 'FilterTile',
@ -28,6 +29,7 @@ export default {
return {
input: '', // Users current search term
akn: new ArrowKeyNavigation(), // Class that manages arrow key naviagtion
getCustomKeyShortcuts,
};
},
mounted() {
@ -38,8 +40,11 @@ export default {
if (!this.active) return;
if (/^[a-zA-Z]$/.test(key) && currentElem !== 'filter-tiles') {
/* Letter key pressed - start searching */
this.$refs.filter.focus();
if (this.$refs.filter) this.$refs.filter.focus();
this.userIsTypingSomething();
} else if (/^[0-9]$/.test(key)) {
/* Number key pressed, check if user has a custom binding */
this.handleHotKey(key);
} else if (keyCode >= 37 && keyCode <= 40) {
/* Arrow key pressed - start navigation */
this.akn.arrowNavigation(keyCode);
@ -61,6 +66,14 @@ export default {
document.activeElement.blur(); // Remove focus
this.akn.resetIndex(); // Reset current element index
},
handleHotKey(key) {
const usersHotKeys = this.getCustomKeyShortcuts();
usersHotKeys.forEach((hotkey) => {
if (hotkey.hotkey === parseInt(key, 10)) {
if (hotkey.url) window.open(hotkey.url, '_blank');
}
});
},
},
};
</script>

View File

@ -60,3 +60,17 @@ export const getCustomColors = () => {
const configColors = config.appConfig.customColors || {};
return Object.assign(configColors, localColors);
};
/**
* Returns a list of items which the user has assigned a hotkey to
* So that when the hotkey is pressed, the app/ service can be launched
*/
export const getCustomKeyShortcuts = () => {
const results = [];
const sections = config.sections || [];
sections.forEach((section) => {
const itemsWithHotKeys = section.items.filter(item => item.hotkey);
results.push(itemsWithHotKeys.map(item => ({ hotkey: item.hotkey, url: item.url })));
});
return results.flat();
};

View File

@ -364,6 +364,10 @@
"default": "newtab",
"description": "Opening method, when item is clicked"
},
"hotkey": {
"type": "number",
"description": "A numeric shortcut key, between 0 and 9. Useful for quickly launching frequently used applications"
},
"color": {
"type": "string",
"description": "A custom fill color of the item"