Show opening method on hover. Allow items to be opened in an iframe

This commit is contained in:
Alicia Sykes 2021-04-06 15:47:34 +01:00
parent 8ddc2506ac
commit 7fd36d9ec6
14 changed files with 9255 additions and 37157 deletions

27851
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -15,6 +15,7 @@
"v-tooltip": "^2.1.3", "v-tooltip": "^2.1.3",
"vue": "^2.6.10", "vue": "^2.6.10",
"vue-cli-plugin-yaml": "^1.0.2", "vue-cli-plugin-yaml": "^1.0.2",
"vue-js-modal": "^2.0.0-rc.6",
"vue-router": "^3.0.3" "vue-router": "^3.0.3"
}, },
"devDependencies": { "devDependencies": {

View File

@ -0,0 +1 @@
<svg aria-hidden="true" focusable="false" data-prefix="far" data-icon="window-maximize" class="svg-inline--fa fa-window-maximize fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M464 32H48C21.5 32 0 53.5 0 80v352c0 26.5 21.5 48 48 48h416c26.5 0 48-21.5 48-48V80c0-26.5-21.5-48-48-48zm0 394c0 3.3-2.7 6-6 6H54c-3.3 0-6-2.7-6-6V192h416v234z"></path></svg>

After

Width:  |  Height:  |  Size: 410 B

View File

@ -0,0 +1 @@
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="clone" class="svg-inline--fa fa-clone fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M464 0c26.51 0 48 21.49 48 48v288c0 26.51-21.49 48-48 48H176c-26.51 0-48-21.49-48-48V48c0-26.51 21.49-48 48-48h288M176 416c-44.112 0-80-35.888-80-80V128H48c-26.51 0-48 21.49-48 48v288c0 26.51 21.49 48 48 48h288c26.51 0 48-21.49 48-48v-48H176z"></path></svg>

After

Width:  |  Height:  |  Size: 472 B

View File

@ -0,0 +1 @@
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="external-link-alt" class="svg-inline--fa fa-external-link-alt fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z"></path></svg>

After

Width:  |  Height:  |  Size: 597 B

View File

@ -143,16 +143,16 @@ export default {
span.options-label { span.options-label {
font-size: 0.8rem; font-size: 0.8rem;
color: #5cabca; color: $ascent-with-opacity;
width: 5.5rem; width: 5.5rem;
text-align: left; text-align: left;
} }
.display-options { .display-options {
color: $ascent; color: $ascent-with-opacity;
svg { svg {
path { path {
fill: $ascent; fill: $ascent-with-opacity;
} }
width: 1rem; width: 1rem;
height: 1rem; height: 1rem;
@ -165,7 +165,7 @@ export default {
opacity: 0.8; opacity: 0.8;
cursor: pointer; cursor: pointer;
&:hover, &.selected { &:hover, &.selected {
background: $ascent; background: $ascent-with-opacity;
path { fill: $background; } path { fill: $background; }
} }
} }

View File

@ -1,33 +0,0 @@
<template>
<div class="hello">
<h1>{{ msg }}</h1>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
props: {
msg: String,
},
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="scss">
h3 {
margin: 40px 0 0;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
</style>

View File

@ -0,0 +1,30 @@
<template>
<modal name="iframe-modal" :resizable="true"
:adaptive="true" width="80%" height="80%">
<iframe :src="url" class="frame" />
</modal>
</template>
<script>
export default {
name: 'IframeModal',
props: {
url: String,
},
methods: {
show: function show() {
this.$modal.show('iframe-modal');
},
},
};
</script>
<style scoped lang="scss">
.frame {
width: 100%;
height: 100%;
border: none;
}
</style>

View File

@ -1,6 +1,13 @@
<template> <template>
<a :href="url" :class="`item ${!icon? 'short': ''}`" v-on:click="$emit('itemClicked')" <a
tabindex="0" target="_blank" rel="noopener noreferrer" v-tooltip="getTooltipOptions()"> :href="target !== 'iframe' ? url : '#'"
v-on:click="itemOpened()"
:class="`item ${!icon? 'short': ''}`"
v-tooltip="getTooltipOptions()"
:target="target === 'newtab' ? '_blank' : ''"
rel="noopener noreferrer"
tabindex="0"
>
<!-- Item Text --> <!-- Item Text -->
<div class="tile-title" :id="`tile-${id}`"> <div class="tile-title" :id="`tile-${id}`">
<span class="text">{{ title }}</span> <span class="text">{{ title }}</span>
@ -8,11 +15,22 @@
</div> </div>
<!-- Item Icon --> <!-- Item Icon -->
<Icon :icon="icon" :url="url" /> <Icon :icon="icon" :url="url" />
<div :class="`opening-method-icon ${!icon? 'short': ''}`">
<NewTabOpenIcon v-if="target === 'newtab'" />
<SameTabOpenIcon v-else-if="target === 'sametab'" />
<IframeOpenIcon v-else-if="target === 'iframe'" />
</div>
<IframeModal v-if="target === 'iframe'" :url="url" ref="iframeModal"/>
</a> </a>
</template> </template>
<script> <script>
import Icon from '@/components/ItemIcon.vue'; import Icon from '@/components/ItemIcon.vue';
import IframeModal from '@/components/IframeModal.vue';
import NewTabOpenIcon from '@/assets/icons/open-new-tab.svg';
import SameTabOpenIcon from '@/assets/icons/open-current-tab.svg';
import IframeOpenIcon from '@/assets/icons/open-iframe.svg';
export default { export default {
name: 'Item', name: 'Item',
@ -25,7 +43,7 @@ export default {
svg: String, // Optional vector graphic, that is then dynamically filled svg: String, // Optional vector graphic, that is then dynamically filled
color: String, // Optional background color, specified in hex code color: String, // Optional background color, specified in hex code
url: String, // URL to the resource, optional but recommended url: String, // URL to the resource, optional but recommended
openingMethod: { // Where resource will open, either 'newtab', 'sametab' or 'iframe' target: { // Where resource will open, either 'newtab', 'sametab' or 'iframe'
type: String, type: String,
default: 'newtab', default: 'newtab',
validator: (value) => ['newtab', 'sametab', 'iframe'].indexOf(value) !== -1, validator: (value) => ['newtab', 'sametab', 'iframe'].indexOf(value) !== -1,
@ -38,11 +56,18 @@ export default {
}, },
components: { components: {
Icon, Icon,
NewTabOpenIcon,
SameTabOpenIcon,
IframeOpenIcon,
IframeModal,
}, },
methods: { methods: {
/* Called when an item is opened, so that search field can be reset */ /* Called when an item is opened, so that search field can be reset */
itemOpened() { itemOpened() {
this.$emit('itemClicked'); this.$emit('itemClicked');
if (this.target === 'iframe') {
this.$refs.iframeModal.show();
}
}, },
/** /**
* Detects overflowing text, shows ellipse, and allows is to marguee on hover * Detects overflowing text, shows ellipse, and allows is to marguee on hover
@ -78,107 +103,128 @@ export default {
@import '../../src/styles/color-pallet.scss'; @import '../../src/styles/color-pallet.scss';
@import '../../src/styles/constants.scss'; @import '../../src/styles/constants.scss';
/* Item wrapper */
.item { .item {
flex-grow: 1; flex-grow: 1;
height: 100px; height: 100px;
color: $ascent; position: relative;
vertical-align: middle; color: $ascent;
margin: 0.5rem; vertical-align: middle;
background: #607d8b33; margin: 0.5rem;
text-align: center; background: #607d8b33;
padding: 2px; text-align: center;
border: 2px solid transparent; padding: 2px;
border-radius: $curve-factor; border: 2px solid transparent;
box-shadow: 1px 1px 2px #373737; border-radius: $curve-factor;
cursor: pointer; box-shadow: 1px 1px 2px #373737;
&:hover { cursor: pointer;
box-shadow: 1px 2px 4px #373737; &:hover {
background: #607d8b4d; box-shadow: 1px 2px 4px #373737;
} background: #607d8b4d;
&:focus { }
border: 2px solid $ascent; &:focus {
outline: none; border: 2px solid $ascent;
} outline: none;
&.short { }
height: 18px; &.short {
} height: 18px;
}
} }
/* Text in tile */
.tile-title { .tile-title {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
min-width: 120px;
height: 30px;
overflow: hidden;
position: relative;
padding: 0;
span.text {
position: absolute;
white-space: nowrap; white-space: nowrap;
overflow: hidden; transition: 1s;
text-overflow: ellipsis; float: left;
min-width: 120px; left: 0;
height: 30px; }
overflow: hidden; &:not(.is-overflowing) span.text{
position: relative; width: 100%;
padding: 0; }
.overflow-dots {
opacity: 0;
}
&.is-overflowing {
span.text { span.text {
position: absolute; overflow: hidden;
white-space: nowrap;
transition: 1s;
float: left;
left: 0;
}
&:not(.is-overflowing) span.text{
width: 100%;
} }
.overflow-dots { .overflow-dots {
opacity: 0; display: block;
opacity: 1;
background: $overflow-ellipse;
position: absolute;
z-index: 5;
right: 0;
transition: opacity 0.1s ease-in;
} }
}
} }
.tile-title.is-overflowing { /* Manage hover and focus actions */
span.text { .item:hover, .item:focus {
overflow: hidden; /* Show opening-method icon */
} .opening-method-icon svg {
.overflow-dots { display: block;
display: block; }
opacity: 1;
background: $overflow-ellipse;
position: absolute;
z-index: 5;
right: 0;
transition: opacity 0.1s ease-in;
}
}
.item:hover .tile-title.is-overflowing{ /* Trigger text-marquee for text that doesn't fit */
.tile-title.is-overflowing{
.overflow-dots { .overflow-dots {
opacity: 0; opacity: 0;
} }
span.text { span.text {
transform: translateX(calc(120px - 100%)); transform: translateX(calc(120px - 100%));
} }
}
/* Colourize icons on hover */
.tile-svg {
filter: drop-shadow(4px 8px 3px $transparent-black);
}
.tile-icon {
filter:
drop-shadow(4px 8px 3px $transparent-black)
saturate(2);
}
} }
.tile-icon { .tile-icon {
width: 60px; width: 60px;
filter: drop-shadow(2px 4px 6px $transparent-black) saturate(0.65); filter: drop-shadow(2px 4px 6px $transparent-black) saturate(0.65);
}
.item:hover {
.tile-svg {
filter: drop-shadow(4px 8px 3px $transparent-black);
}
.tile-icon {
filter:
drop-shadow(4px 8px 3px $transparent-black)
saturate(2);
}
} }
.tile-svg { .tile-svg {
width: 56px; width: 56px;
filter:
invert(69%)
sepia(40%)
saturate(4686%)
hue-rotate(142deg)
brightness(96%)
contrast(102%);
} }
/* Opening-method icon */
.opening-method-icon {
svg {
position: absolute;
display: none;
width: 1rem;
margin: 2px;
right: 0;
top: 0;
path {
fill: $ascent-with-opacity;
}
}
&.short svg {
width: 0.5rem;
margin: 0;
}
}
</style> </style>

View File

@ -20,7 +20,7 @@
:title="item.title" :title="item.title"
:description="item.description" :description="item.description"
:icon="item.icon" :icon="item.icon"
:iconType="item.iconType" :target="item.target"
:svg="item.svg" :svg="item.svg"
@itemClicked="$emit('itemClicked')" @itemClicked="$emit('itemClicked')"
/> />

View File

@ -9,6 +9,7 @@ sections:
description: Firewall Central Management description: Firewall Central Management
icon: networking/opnsense.png icon: networking/opnsense.png
iconType: img iconType: img
target: iframe
url: https://192.168.1.1 url: https://192.168.1.1
- title: NetData - title: NetData
description: System resource usage on firewall description: System resource usage on firewall

View File

@ -1,11 +1,13 @@
import Vue from 'vue'; import Vue from 'vue';
import VTooltip from 'v-tooltip'; // A Vue directive for Popper.js import VTooltip from 'v-tooltip'; // A Vue directive for Popper.js
import VModal from 'vue-js-modal'; // Modal component
import App from './App.vue'; import App from './App.vue';
import router from './router'; import router from './router';
import './registerServiceWorker'; import './registerServiceWorker';
Vue.use(VTooltip); Vue.use(VTooltip);
Vue.use(VModal);
Vue.config.productionTip = false; Vue.config.productionTip = false;
new Vue({ new Vue({

View File

@ -12,4 +12,4 @@ $overflow-ellipse: #283e51;
$bg-with-opacity: rgba($background, 0.8); $bg-with-opacity: rgba($background, 0.8);
$header-color: darken($background, 5%); $header-color: darken($background, 5%);
$dark-ascent: darken($ascent, 50%); $dark-ascent: darken($ascent, 50%);
$ascent-with-opacity: rgba($ascent, 0.8); $ascent-with-opacity: rgba($ascent, 0.7);

15925
yarn.lock

File diff suppressed because it is too large Load Diff