Merge branch 'master' into FEATURE/login-remember-me-always

This commit is contained in:
Alicia Sykes
2021-12-01 21:34:26 +00:00
committed by GitHub
177 changed files with 9683 additions and 3812 deletions

90
src/views/404.vue Normal file
View File

@@ -0,0 +1,90 @@
<template>
<main class="not-found-page">
<h1 class="not-found-title">404</h1>
<h2 class="not-found-sad-face">:(</h2>
<p class="not-found-subtitle">Page Not Found</p>
<p class="not-found-message">
Facing Issues?
<a href="https://git.io/JzpL5">Get Support</a>.
</p>
<router-link to="/" class="go-home">Back Home</router-link>
</main>
</template>
<script>
export default {
name: 'not-found',
methods: {
setTheme() {
document.getElementsByTagName('html')[0].setAttribute('data-theme', 'dashy-docs');
},
},
mounted() {
this.setTheme();
},
};
</script>
<style scoped lang="scss">
@import '@/styles/media-queries.scss';
@import '@/styles/style-helpers.scss';
main.not-found-page {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: center;
cursor: default;
background: #202020;
min-height: calc(99vh - var(--footer-height));
background-color: #202020;
h1.not-found-title, h2.not-found-sad-face {
font-size: 20vh;
font-family: Tahoma, monospace;
cursor: default;
color: #0c0c0c;
text-shadow: 0px 4px 4px #090909, 0 0 0 #000, 0px 2px 2px #000000;
margin: 1rem 0 0;
}
h2.not-found-sad-face {
font-size: 4rem;
margin: 0 0 1.5rem 0;
}
p {
font-family: monospace;
cursor: default;
color: #0c0c0c;
margin: 0.2rem 0;
text-shadow: 0 1px 1px #090909, 0 0 0 #000, 0 1px 1px #000000;
}
p.not-found-subtitle {
font-size: 2.8rem;
}
p.not-found-message {
font-size: 1.4rem;
font-weight: normal;
a {
color: #0c0c0c;
font-family: monospace;
}
}
a.go-home {
padding: 0.3rem 1rem;
border-radius: 3px;
font-size: 1.7rem;
cursor: pointer;
font-family: Tahoma, monospace;
color: #0c0c0c;
margin: 2rem 0 0;
text-decoration: none;
background: #db78fc;
box-shadow: 0 4px #b83ddd;
&:hover { box-shadow: 0 2px #b83ddd; }
}
::selection { background-color: #db78fc; color: #121212; }
}
</style>

View File

@@ -7,18 +7,13 @@ import JsonToYaml from '@/utils/JsonToYaml';
export default {
name: 'DownloadConfig',
props: {
sections: Array,
appConfig: Object,
pageInfo: Object,
computed: {
config() {
return this.$store.state.config;
},
},
data() {
return {
config: {
appConfig: this.appConfig,
pageInfo: this.pageInfo,
sections: this.sections,
},
jsonParser: JsonToYaml,
};
},

View File

@@ -4,24 +4,33 @@
<!-- Search bar, layout options and settings -->
<SettingsContainer ref="filterComp"
@user-is-searchin="searching"
@change-display-layout="setLayoutOrientation"
@change-icon-size="setItemSize"
@change-modal-visibility="updateModalVisibility"
:displayLayout="layout"
:iconSize="itemSizeBound"
:externalThemes="getExternalCSSLinks()"
:sections="allSections"
:appConfig="appConfig"
:pageInfo="pageInfo"
:modalOpen="modalOpen"
class="settings-outer"
/>
<!-- Show back button, when on single-section view -->
<div v-if="singleSectionView">
<router-link to="/home" class="back-to-all-link">
<BackIcon />
<span>Back to All</span>
</router-link>
</div>
<!-- Main content, section for each group of items -->
<div v-if="checkTheresData(sections)"
:class="`item-group-container orientation-${layout} item-size-${itemSizeBound}`">
:class="`item-group-container `
+ `orientation-${layout} `
+ `item-size-${itemSizeBound} `
+ (isEditMode ? 'edit-mode ' : '')
+ (singleSectionView ? 'single-section-view ' : '')
+ (this.colCount ? `col-count-${this.colCount} ` : '')"
>
<Section
v-for="(section, index) in filteredTiles"
:key="index"
:index="index"
:title="section.name"
:icon="section.icon || undefined"
:displayData="getDisplayData(section)"
@@ -34,11 +43,17 @@
:class="
(searchValue && filterTiles(section.items, searchValue).length === 0) ? 'no-results' : ''"
/>
<!-- Show add new section button, in edit mode -->
<AddNewSection v-if="isEditMode" />
</div>
<!-- Show message when there's no data to show -->
<div v-if="checkIfResults()" class="no-data">
{{searchValue ? $t('home.no-results') : $t('home.no-data')}}
</div>
<!-- Show banner at bottom of screen, for Saving config changes -->
<EditModeSaveMenu v-if="isEditMode" />
<!-- Modal for viewing and exporting configuration file -->
<ExportConfigMenu />
</div>
</template>
@@ -46,57 +61,80 @@
import SettingsContainer from '@/components/Settings/SettingsContainer.vue';
import Section from '@/components/LinkItems/Section.vue';
import EditModeSaveMenu from '@/components/InteractiveEditor/EditModeSaveMenu.vue';
import ExportConfigMenu from '@/components/InteractiveEditor/ExportConfigMenu.vue';
import AddNewSection from '@/components/InteractiveEditor/AddNewSectionLauncher.vue';
import { searchTiles } from '@/utils/Search';
import Defaults, { localStorageKeys, iconCdns } from '@/utils/defaults';
import StoreKeys from '@/utils/StoreMutations';
import Defaults, { localStorageKeys, iconCdns, modalNames } from '@/utils/defaults';
import ErrorHandler from '@/utils/ErrorHandler';
import BackIcon from '@/assets/interface-icons/back-arrow.svg';
export default {
name: 'home',
props: {
sections: Array, // Main site content
appConfig: Object, // Main site configuation (optional)
pageInfo: Object, // Page metadata (optional)
},
components: {
SettingsContainer,
EditModeSaveMenu,
ExportConfigMenu,
AddNewSection,
Section,
BackIcon,
},
data: () => ({
searchValue: '',
layout: '',
itemSizeBound: '',
modalOpen: false, // When true, keybindings are disabled
addNewSectionOpen: false,
}),
computed: {
/* Combines sections from config file, with those in local storage */
allSections() {
// If the user has stored sections in local storage, return those
const localSections = localStorage[localStorageKeys.CONF_SECTIONS];
if (localSections) {
const json = JSON.parse(localSections);
if (json.length >= 1) return json;
}
// Otherwise, return the usuall data from conf.yml
return this.sections;
sections() {
return this.$store.getters.sections;
},
appConfig() {
return this.$store.getters.appConfig;
},
pageInfo() {
return this.$store.getters.pageInfo;
},
modalOpen() {
return this.$store.state.modalOpen;
},
singleSectionView() {
return this.findSingleSection(this.$store.getters.sections, this.$route.params.section);
},
isEditMode() {
return this.$store.state.editMode;
},
/* Get class for num columns, if specified by user */
colCount() {
let { colCount } = this.appConfig;
if (!colCount) return null;
if (colCount < 1) colCount = 1;
if (colCount > 8) colCount = 8;
return colCount;
},
/* Return all sections, that match users search term */
filteredTiles() {
const sections = this.allSections;
const sections = this.singleSectionView || this.sections;
return sections.filter((section) => this.filterTiles(section.items, this.searchValue));
},
/* Updates layout (when button clicked), and saves in local storage */
layoutOrientation: {
get() { return this.appConfig.layout || Defaults.layout; },
set: function setLayout(layout) {
localStorage.setItem(localStorageKeys.LAYOUT_ORIENTATION, layout);
this.layout = layout;
},
layoutOrientation() {
return this.$store.getters.layout;
},
/* Updates icon size (when button clicked), and saves in local storage */
iconSize: {
get() { return this.appConfig.iconSize || Defaults.iconSize; },
set: function setIconSize(iconSize) {
localStorage.setItem(localStorageKeys.ICON_SIZE, iconSize);
this.itemSizeBound = iconSize;
},
iconSize() {
return this.$store.getters.iconSize;
},
},
watch: {
layoutOrientation(layout) {
localStorage.setItem(localStorageKeys.LAYOUT_ORIENTATION, layout);
this.layout = layout;
},
iconSize(size) {
localStorage.setItem(localStorageKeys.ICON_SIZE, size);
this.itemSizeBound = size;
},
},
methods: {
@@ -121,17 +159,32 @@ export default {
getDisplayData(section) {
return !section.displayData ? {} : section.displayData;
},
/* Sets layout attribute, which is used by Section */
setLayoutOrientation(layout) {
this.layoutOrientation = layout;
},
/* Sets item size attribute, which is used by Section */
setItemSize(itemSize) {
this.iconSize = itemSize;
},
/* Update data when modal is open (so that key bindings can be disabled) */
updateModalVisibility(modalState) {
this.modalOpen = modalState;
this.$store.commit('SET_MODAL_OPEN', modalState);
},
openAddNewSectionMenu() {
this.addNewSectionOpen = true;
this.$modal.show(modalNames.EDIT_SECTION);
this.$store.commit(StoreKeys.SET_MODAL_OPEN, true);
},
closeEditSection() {
this.addNewSectionOpen = false;
this.$modal.hide(modalNames.EDIT_SECTION);
this.$store.commit(StoreKeys.SET_MODAL_OPEN, false);
},
/* If on sub-route, and section exists, then return only that section */
findSingleSection: (allSections, sectionTitle) => {
if (!sectionTitle) return undefined;
let sectionToReturn;
const parse = (section) => section.replaceAll(' ', '-').toLowerCase().trim();
allSections.forEach((section) => {
if (parse(sectionTitle) === parse(section.name)) {
sectionToReturn = [section];
}
});
if (!sectionToReturn) ErrorHandler(`No section named '${sectionTitle}' was found`);
return sectionToReturn;
},
/* Returns an array of links to external CSS from the Config */
getExternalCSSLinks() {
@@ -154,8 +207,8 @@ export default {
/* Checks if any sections or items use icons from a given CDN */
checkIfIconLibraryNeeded(prefix) {
let isNeeded = false;
if (!this.allSections) return false;
this.allSections.forEach((section) => {
if (!this.sections) return false;
this.sections.forEach((section) => {
if (section.icon && section.icon.includes(prefix)) isNeeded = true;
section.items.forEach((item) => {
if (item.icon && item.icon.includes(prefix)) isNeeded = true;
@@ -194,10 +247,10 @@ export default {
},
/* Returns true if there is more than 1 sub-result visible during searching */
checkIfResults() {
if (!this.allSections) return false;
if (!this.sections) return false;
else {
let itemsFound = true;
this.allSections.forEach((section) => {
this.sections.forEach((section) => {
if (this.filterTiles(section.items, this.searchValue).length > 0) itemsFound = false;
});
return itemsFound;
@@ -227,10 +280,19 @@ export default {
.home {
padding-bottom: 1px;
background: var(--background);
// min-height: calc(100vh - 126px);
min-height: calc(99.9vh - var(--footer-height));
}
.back-to-all-link {
display: flex;
align-items: center;
padding: 0.25rem;
margin: 0.25rem;
@extend .svg-button;
svg { margin-right: 0.5rem; }
text-decoration: none;
}
/* Outside container wrapping the item groups*/
.item-group-container {
display: grid;
@@ -255,29 +317,61 @@ export default {
flex-direction: row;
}
}
&.orientation-horizontal, &.orientation-vertical, &.single-section-view {
@include phone { --content-max-width: 100%; }
@include tablet { --content-max-width: 98%; }
@include laptop { --content-max-width: 90%; }
@include monitor { --content-max-width: 85%; }
@include big-screen { --content-max-width: 80%; }
@include big-screen-up { --content-max-width: 60%; }
max-width: var(--content-max-width, 90%);
}
/* Specify number of columns, based on screen size */
@include phone {
grid-template-columns: repeat(1, 1fr);
}
@include tablet {
grid-template-columns: repeat(2, 1fr);
}
@include laptop {
grid-template-columns: repeat(2, 1fr);
}
@include monitor {
grid-template-columns: repeat(3, 1fr);
}
@include big-screen {
grid-template-columns: repeat(4, 1fr);
}
@include big-screen-up {
grid-template-columns: repeat(5, 1fr);
/* Specify number of columns, based on screen size or user preference */
@include phone { --col-count: 1; }
@include tablet { --col-count: 2; }
@include laptop { --col-count: 2; }
@include monitor { --col-count: 3; }
@include big-screen { --col-count: 4; }
@include big-screen-up { --col-count: 5; }
@include tablet-up {
&.col-count-1 { --col-count: 1; }
&.col-count-2 { --col-count: 2; }
&.col-count-3 { --col-count: 3; }
&.col-count-4 { --col-count: 4; }
&.col-count-5 { --col-count: 5; }
&.col-count-6 { --col-count: 6; }
&.col-count-7 { --col-count: 7; }
&.col-count-8 { --col-count: 8; }
}
grid-template-columns: repeat(var(--col-count, 2), minmax(0, 1fr));
/* Hide when search term returns nothing */
.no-results { display: none; }
/* Additional spacing when in edit mode */
&.edit-mode {
margin-bottom: 12rem;
}
/* When in single-section view mode */
&.single-section-view {
display: block;
}
.add-new-section {
border: 2px dashed var(--primary);
border-radius: var(--curve-factor);
padding: var(--item-group-padding);
background: var(--item-group-background);
color: var(--primary);
font-size: 1.2rem;
cursor: pointer;
text-align: center;
height: fit-content;
margin: 10px;
}
}
/* Custom styles only applied when there is no sections in config */

View File

@@ -76,7 +76,7 @@ import router from '@/router';
import Button from '@/components/FormElements/Button';
import Input from '@/components/FormElements/Input';
import Defaults, { localStorageKeys } from '@/utils/defaults';
import { InfoHandler } from '@/utils/ErrorHandler';
import { InfoHandler, WarningInfoHandler, InfoKeys } from '@/utils/ErrorHandler';
import {
checkCredentials,
login,
@@ -91,9 +91,6 @@ export default {
Button,
Input,
},
props: {
appConfig: Object,
},
data() {
return {
username: '',
@@ -104,6 +101,9 @@ export default {
};
},
computed: {
appConfig() {
return this.$store.getters.appConfig;
},
/* Data for timeout dropdown menu, translated label + value in ms */
dropDownMenu() {
return [
@@ -159,9 +159,9 @@ export default {
if (response.correct) { // Yay, credentials were correct :)
login(this.username, this.password, timeout); // Login, to set the cookie
this.goHome();
InfoHandler(`Succesfully signed in as ${this.username}`, 'Authentication');
InfoHandler(`Succesfully signed in as ${this.username}`, InfoKeys.AUTH);
} else {
InfoHandler(`Unable to Sign In - ${this.message}`, 'Authentication');
WarningInfoHandler('Unable to Sign In', InfoKeys.AUTH, this.message);
}
},
/* Calls function to double-check guest access enabled, then log in as guest */
@@ -169,9 +169,11 @@ export default {
const isAllowed = this.isGuestAccessEnabled;
if (isAllowed) {
this.$toasted.show('Logged in as Guest, Redirecting...', { className: 'toast-success' });
InfoHandler('Logged in as Guest', InfoKeys.AUTH);
this.goHome();
} else {
this.$toasted.show('Guest access not allowed', { className: 'toast-error' });
this.$toasted.show('Guest Access Not Allowed', { className: 'toast-error' });
WarningInfoHandler('Guest Access Not Allowed', InfoKeys.AUTH);
}
},
/* Calls logout, shows status message, and refreshed page */

View File

@@ -2,15 +2,16 @@
<div class="minimal-home" :style="getBackgroundImage() + setColumnCount()">
<!-- Buttons for config and home page -->
<div class="minimal-buttons">
<ConfigLauncher :sections="sections" :pageInfo="pageInfo" :appConfig="appConfig"
@modalChanged="modalChanged" class="config-launcher" />
<ConfigLauncher @modalChanged="modalChanged" class="config-launcher" />
</div>
<!-- Page title and search bar -->
<div class="title-and-search">
<router-link to="/">
<h1>{{ pageInfo.title }}</h1>
</router-link>
<MinimalSearch @user-is-searchin="(s) => { this.searchValue = s; }" :active="!modalOpen" />
<MinimalSearch
@user-is-searchin="(s) => { this.searchValue = s; }"
:active="!modalOpen" ref="filterComp" />
</div>
<div v-if="checkTheresData(sections)"
:class="`item-group-container ${!tabbedView ? 'showing-all' : ''}`">
@@ -60,11 +61,6 @@ import ConfigLauncher from '@/components/Settings/ConfigLauncher';
export default {
name: 'home',
props: {
sections: Array, // Main site content
appConfig: Object, // Main site configuation (optional)
pageInfo: Object,
},
components: {
MinimalSection,
MinimalHeading,
@@ -79,10 +75,21 @@ export default {
tabbedView: true, // By default use tabs, when searching then show all instead
theme: GetTheme(),
}),
computed: {
sections() {
return this.$store.getters.sections;
},
appConfig() {
return this.$store.getters.appConfig;
},
pageInfo() {
return this.$store.getters.pageInfo;
},
},
watch: {
/* When the theme changes, then call the update method */
searchValue() {
this.tabbedView = !(this.searchValue.length > 0);
this.tabbedView = !this.searchValue || this.searchValue.length === 0;
},
},
methods: {
@@ -111,7 +118,7 @@ export default {
},
/* Clears input field, once a searched item is opened */
finishedSearching() {
this.$refs.filterComp.clearFilterInput();
this.$refs.filterComp.clearMinFilterInput();
},
/* Extracts the site name from domain, used for the searching functionality */
getDomainFromUrl(url) {

View File

@@ -1,6 +1,6 @@
<template>
<div class="work-space">
<SideBar :sections="sections" @launch-app="launchApp" />
<SideBar :sections="sections" @launch-app="launchApp" :initUrl="getInitialUrl()" />
<WebContent :url="url" v-if="!isMultiTaskingEnabled" />
<MultiTaskingWebComtent :url="url" v-else />
</div>
@@ -16,17 +16,19 @@ import { GetTheme, ApplyLocalTheme, ApplyCustomVariables } from '@/utils/ThemeHe
export default {
name: 'Workspace',
props: {
sections: Array,
appConfig: Object,
},
data: () => ({
url: '', // this.$route.query.url || '',
url: '',
GetTheme,
ApplyLocalTheme,
ApplyCustomVariables,
}),
computed: {
sections() {
return this.$store.getters.sections;
},
appConfig() {
return this.$store.getters.appConfig;
},
isMultiTaskingEnabled() {
return this.appConfig.enableMultiTasking || false;
},
@@ -37,8 +39,12 @@ export default {
MultiTaskingWebComtent,
},
methods: {
launchApp(url) {
this.url = url;
launchApp(options) {
if (options.target === 'newtab') {
window.open(options.url, '_blank');
} else {
this.url = options.url;
}
},
setTheme() {
const theme = this.GetTheme();
@@ -51,16 +57,21 @@ export default {
fontAwesomeScript.setAttribute('src', `https://kit.fontawesome.com/${faKey}.js`);
document.head.appendChild(fontAwesomeScript);
},
repositionFooter() {
document.getElementsByTagName('footer')[0].style.position = 'fixed';
/* Returns a service URL, if set as a URL param, or if user has specified landing URL */
getInitialUrl() {
const route = this.$route;
if (route.query && route.query.url) {
return decodeURI(route.query.url);
} else if (this.appConfig.workspaceLandingUrl) {
return this.appConfig.workspaceLandingUrl;
}
return undefined;
},
},
mounted() {
const route = this.$route;
if (route.query && route.query.url) this.url = decodeURI(route.query.url);
this.setTheme();
this.initiateFontAwesome();
// this.repositionFooter();
this.url = this.getInitialUrl();
},
};