Initial commit
This commit is contained in:
commit
9212949716
27 changed files with 24369 additions and 0 deletions
133
src/App.vue
Normal file
133
src/App.vue
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
<template>
|
||||
<div class="maximize_size">
|
||||
<LoginCard v-if="!is_authed" />
|
||||
<MainView
|
||||
v-else
|
||||
:preview_mode="is_preview"
|
||||
:dev_mode="is_dev"
|
||||
/>
|
||||
<Themes
|
||||
v-if="show_theme_modal"
|
||||
@close="show_theme_modal = false"
|
||||
/>
|
||||
<SiteInfo
|
||||
v-if="show_site_info"
|
||||
@close="show_site_info = false"
|
||||
/>
|
||||
<div id="info-button">
|
||||
<button @click.stop="show_site_info = true">
|
||||
<Icon
|
||||
type="info"
|
||||
:size="35"
|
||||
:inner-size="35"
|
||||
primary="--icon-primary"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<div id="theme-button">
|
||||
<button @click.stop="show_theme_modal = true">
|
||||
<Icon
|
||||
type="palette"
|
||||
:size="35"
|
||||
:inner-size="35"
|
||||
primary="--icon-primary"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// Import Misc JS things
|
||||
import "./js/prototypes.js";
|
||||
|
||||
// Import components
|
||||
import ThemePicker from './components/modals/ThemeModal.vue';
|
||||
import SiteInfo from './components/modals/SiteInfo.vue';
|
||||
import MainView from './components/MainView.vue';
|
||||
import Icon from './components/Icon.vue';
|
||||
|
||||
export default {
|
||||
name: 'App',
|
||||
components: {
|
||||
"LoginCard": LoginCard,
|
||||
"MainView": MainView,
|
||||
"Themes": ThemePicker,
|
||||
"Icon": Icon,
|
||||
"SiteInfo": SiteInfo,
|
||||
},
|
||||
data() {return {
|
||||
show_theme_modal: false,
|
||||
show_site_info: false,
|
||||
}},
|
||||
computed: {
|
||||
is_authed() {
|
||||
let params = new URLSearchParams(window.location.hash.slice(1));
|
||||
|
||||
if (params.get(`access_token`)) {
|
||||
if (sessionStorage.getItem(this.storage_key.state) === params.get(`state`)) {
|
||||
console.info(`State compare success`)
|
||||
|
||||
// Modify sessionStorage
|
||||
sessionStorage.setItem(this.storage_key.token, params.get(`access_token`));
|
||||
sessionStorage.removeItem(this.storage_key.state);
|
||||
window.location.hash = ``;
|
||||
} else {
|
||||
console.error(`State compare failed`);
|
||||
};
|
||||
};
|
||||
|
||||
if (sessionStorage.getItem(this.storage_key.token)) {
|
||||
return true;
|
||||
};
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@import "./css/transitions.css";
|
||||
@import "./css/scrollbar.css";
|
||||
@import "./css/tooltips.css";
|
||||
|
||||
html, body, .maximize_size {
|
||||
user-select: none !important;
|
||||
font-family: var(--fonts);
|
||||
overflow-x: hidden;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--background);
|
||||
color: var(--background-text);
|
||||
}
|
||||
|
||||
/* Allows for better theming of the anchor text */
|
||||
a { color: var(--link); }
|
||||
a:visited { color: var(--visited-link); }
|
||||
|
||||
#theme-button {
|
||||
position: absolute;
|
||||
display: block;
|
||||
bottom: 5px;
|
||||
right: 5px;
|
||||
}
|
||||
#theme-button > button {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
#info-button {
|
||||
position: absolute;
|
||||
display: block;
|
||||
bottom: 5px;
|
||||
left: 5px;
|
||||
}
|
||||
#info-button > button {
|
||||
padding: 5px;
|
||||
}
|
||||
</style>
|
||||
136
src/components/Icon.vue
Normal file
136
src/components/Icon.vue
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
<template>
|
||||
<div id="icon" :style="div_styles">
|
||||
<span v-if="type === 'palette'" :style="span_styles">
|
||||
<svg
|
||||
:width="innerSize"
|
||||
:height="innerSize"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<rect
|
||||
:width="innerSize"
|
||||
:height="innerSize"
|
||||
fill="none"
|
||||
rx="0"
|
||||
ry="0"
|
||||
/>
|
||||
<path
|
||||
class="primary"
|
||||
:style="primary_styles"
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M12.2516 20.3664C12.173 20.3884 12.0894 20.4 12 20.4C11.9227 20.4 11.8455 20.3993 11.7686 20.3978C11.7126 20.3993 11.6564 20.4 11.6 20.4C11.2372 20.4 10.8834 20.3691 10.5425 20.3104C5.7105 19.7107 2 16.1737 2 11.9C2 7.2056 6.47715 3.40002 12 3.40002C15.7082 3.40002 18.945 5.11567 20.6717 7.66406C20.6905 7.69181 20.7029 7.72492 20.7093 7.76311C21.2826 8.51263 21.5525 9.44282 21.3862 10.386C21.1499 11.7265 20.0945 12.7409 18.7432 13.1075C18.6959 13.1025 18.6482 13.1 18.6001 13.1C18.3889 13.1 18.1853 13.1491 17.9942 13.2401L17.9745 13.2418L17.9718 13.251C17.175 13.6461 16.6001 14.7723 16.6001 16.1V16.1001C16.6001 16.1342 16.6001 16.1683 16.6009 16.2021L16.5988 16.3132L16.5992 16.3295L16.5986 16.3764L16.5999 16.3784L16.6 16.4C16.6 18.4325 14.7051 20.1109 12.2516 20.3664ZM12.0566 19.1638C11.9755 19.1925 11.8879 19.2078 11.7928 19.2078C7.0471 19.2078 3.19995 15.9378 3.19995 11.9039C3.19995 7.87006 7.0471 4.59998 11.7928 4.59998C14.882 4.59998 17.8404 5.876 19.3701 7.94922C20.0399 8.54236 20.3957 9.32338 20.2583 10.103C20.0591 11.2322 19.4734 11.9526 18.2539 12.0036C17.5102 11.955 16.5185 12.6539 15.9177 13.9423C15.752 14.2978 15.6278 14.6532 15.5447 14.9934C15.5431 15.0019 15.5412 15.0106 15.539 15.0195C15.439 15.4289 15.4192 15.7734 15.4218 15.8945L15.419 15.8969C15.4158 16.046 15.4228 16.1878 15.44 16.3202C15.3393 17.8505 13.8796 19.0768 12.0566 19.1638ZM13.5 7C13.5 7.82843 12.8284 8.5 12 8.5C11.1715 8.5 10.5 7.82843 10.5 7C10.5 6.17157 11.1715 5.5 12 5.5C12.8284 5.5 13.5 6.17157 13.5 7ZM8.99995 8.5C8.99995 9.32843 8.32838 10 7.49995 10C6.67152 10 5.99995 9.32843 5.99995 8.5C5.99995 7.67157 6.67152 7 7.49995 7C8.32838 7 8.99995 7.67157 8.99995 8.5ZM11.5 18C12.8807 18 14 17.1046 14 16C14 14.8954 12.8807 14 11.5 14C10.1192 14 8.99995 14.8954 8.99995 16C8.99995 17.1046 10.1192 18 11.5 18ZM11.5 16.8C12.2179 16.8 12.8 16.4418 12.8 16C12.8 15.5582 12.2179 15.2 11.5 15.2C10.782 15.2 10.2 15.5582 10.2 16C10.2 16.4418 10.782 16.8 11.5 16.8ZM16.5 10C17.3284 10 18 9.32843 18 8.5C18 7.67157 17.3284 7 16.5 7C15.6715 7 15 7.67157 15 8.5C15 9.32843 15.6715 10 16.5 10ZM5.69995 14C6.52838 14 7.19995 13.3284 7.19995 12.5C7.19995 11.6716 6.52838 11 5.69995 11C4.87152 11 4.19995 11.6716 4.19995 12.5C4.19995 13.3284 4.87152 14 5.69995 14Z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
<span v-else-if="type === 'info'" :style="span_styles">
|
||||
<svg
|
||||
:width="innerSize"
|
||||
:height="innerSize"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<rect
|
||||
:width="innerSize"
|
||||
:height="innerSize"
|
||||
fill="none"
|
||||
rx="0"
|
||||
ry="0"
|
||||
/>
|
||||
<path
|
||||
class="primary"
|
||||
:style="primary_styles"
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M2 12C2 6.49 6.49 2 12 2C17.51 2 22 6.49 22 12C22 17.51 17.51 22 12 22C6.49 22 2 17.51 2 12ZM4 12C4 16.41 7.59 20 12 20C16.41 20 20 16.41 20 12C20 7.59 16.41 4 12 4C7.59 4 4 7.59 4 12ZM12 9C12.5523 9 13 8.55228 13 8C13 7.44772 12.5523 7 12 7C11.4477 7 11 7.44772 11 8C11 8.55228 11.4477 9 12 9ZM12 10C11.45 10 11 10.45 11 11V16C11 16.55 11.45 17 12 17C12.55 17 13 16.55 13 16V11C13 10.45 12.55 10 12 10Z"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: `Icon`,
|
||||
props: {
|
||||
type: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
primary: {
|
||||
type: String,
|
||||
default: `--icon-primary`,
|
||||
required: false,
|
||||
},
|
||||
secondary: {
|
||||
type: String,
|
||||
default: `--icon-secondary`,
|
||||
required: false,
|
||||
},
|
||||
size: {
|
||||
type: Number,
|
||||
default: 25,
|
||||
required: false,
|
||||
},
|
||||
innerSize: {
|
||||
type: Number,
|
||||
default: null,
|
||||
required: false,
|
||||
},
|
||||
background: {
|
||||
type: String,
|
||||
default: null,
|
||||
required: false,
|
||||
},
|
||||
border: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
required: false,
|
||||
}
|
||||
},
|
||||
data() { return {
|
||||
div_styles: {
|
||||
"background-color": this.background ? `var(${this.background})` : null,
|
||||
"border-radius": `${this.border}px`,
|
||||
"width": `${this.size}px`,
|
||||
"height": `${this.size}px`,
|
||||
},
|
||||
}},
|
||||
computed: {
|
||||
span_styles() {
|
||||
if (this.innerSize) {
|
||||
return {
|
||||
"width": `${this.innerSize}px`,
|
||||
"height": `${this.innerSize}px`,
|
||||
}
|
||||
}
|
||||
let x = Math.floor(this.size * 0.6);
|
||||
return {
|
||||
width: `${x}px`,
|
||||
height: `${x}px`,
|
||||
}
|
||||
},
|
||||
primary_styles() {
|
||||
return {
|
||||
"fill": `var(${this.primary})`,
|
||||
};
|
||||
},
|
||||
secondary_styles() {
|
||||
return {
|
||||
"fill": `var(${this.secondary})`
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
#icon {
|
||||
justify-content: center;
|
||||
vertical-align: middle;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
107
src/components/LoginView.vue
Normal file
107
src/components/LoginView.vue
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
<template>
|
||||
<div id="login_screen" class="maximize_size">
|
||||
<div class="card">
|
||||
<div
|
||||
v-if="error"
|
||||
class="alert error"
|
||||
>
|
||||
{{ error }}
|
||||
</div>
|
||||
<a :href="auth_url">
|
||||
<button>Login</button>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'LoginView',
|
||||
data() { return {
|
||||
auth_base: ``,
|
||||
use_state: true,
|
||||
client_id: ``,
|
||||
scopes: [],
|
||||
show_dialog: process.env.NODE_ENV !== `production`,
|
||||
}},
|
||||
computed: {
|
||||
auth_url() {
|
||||
let params = [
|
||||
`client_id=${this.client_id}`,
|
||||
`response_type=token`,
|
||||
`redirect_uri=${encodeURIComponent(this.auth_redirect)}`,
|
||||
`scope=${encodeURIComponent(this.scopes.join(" "))}`,
|
||||
`show_dialog=${this.show_dialog}`
|
||||
];
|
||||
|
||||
// Create the state data if we are using it
|
||||
if (this.use_state) {
|
||||
let state = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
|
||||
params.push(`state=${state}`);
|
||||
sessionStorage.setItem(this.storage_key.state, state);
|
||||
};
|
||||
|
||||
return `${this.auth_base}?${params.join("&")}`;
|
||||
},
|
||||
error() {
|
||||
let error_message = (new URLSearchParams(window.location.search.slice(1))).get(`error`);
|
||||
return error_message ? error_message : ``;
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
#login_screen {
|
||||
font-family: var(--fonts);
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.card {
|
||||
border-radius: var(--corner-rounding);
|
||||
background-color: var(--card-colour);
|
||||
color: var(--card-text);
|
||||
text-align: center;
|
||||
padding: 15px;
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: var(--button-background);
|
||||
color: var(--button-text);
|
||||
font-family: var(--fonts);
|
||||
border-radius: 50px;
|
||||
padding: 10px 20px;
|
||||
border-style: none;
|
||||
font-size: larger;
|
||||
outline: none;
|
||||
}
|
||||
button:hover { cursor: pointer; }
|
||||
|
||||
|
||||
.alert {
|
||||
margin-bottom: 10px;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
|
||||
.error {
|
||||
background-color: var(--error-background);
|
||||
border-radius: var(--corner-rounding);
|
||||
border-color: var(--error);
|
||||
color: var(--error-text);
|
||||
border-style: solid;
|
||||
border-width: 2px;
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 768px) {
|
||||
.card {
|
||||
padding: 30px;
|
||||
width: 33%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
22
src/components/MainView.vue
Normal file
22
src/components/MainView.vue
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
<template>
|
||||
<div id="main_screen">
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as axios from "axios";
|
||||
|
||||
export default {
|
||||
name: `MainView`,
|
||||
props: {},
|
||||
components: {},
|
||||
data() { return {}},
|
||||
computed: {},
|
||||
methods: {},
|
||||
mounted() {},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
63
src/components/modals/SiteInfo.vue
Normal file
63
src/components/modals/SiteInfo.vue
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
<template id="info-modal">
|
||||
<transition name="fade" @after-enter="content = true">
|
||||
<div
|
||||
v-if="container"
|
||||
class="modal-container"
|
||||
@click.self.stop="content = false"
|
||||
>
|
||||
<transition name="burst" @after-leave="$emit('close')">
|
||||
<div v-if="content" class="modal">
|
||||
<p></p>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: `SiteInfoModal`,
|
||||
data() {return {
|
||||
container: false,
|
||||
content: false,
|
||||
}},
|
||||
mounted() {
|
||||
this.$nextTick(function() {
|
||||
this.container = true;
|
||||
});
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.modal-container {
|
||||
background-color: var(--modal-container-background);
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: fixed;
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
z-index: 10;
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.modal {
|
||||
background-color: var(--modal-background);
|
||||
border-radius: var(--corner-rounding);
|
||||
color: var(--modal-text);
|
||||
text-align: center;
|
||||
max-height: 85%;
|
||||
padding: 0 15px;
|
||||
z-index: 11;
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 768px) {
|
||||
.modal {
|
||||
width: 50%;
|
||||
max-height: 75%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
127
src/components/modals/ThemeModal.vue
Normal file
127
src/components/modals/ThemeModal.vue
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
<template id="theme-modal">
|
||||
<transition name="fade" @after-enter="content = true">
|
||||
<div
|
||||
v-if="container"
|
||||
class="modal-container"
|
||||
@click.self.stop="content = false"
|
||||
>
|
||||
<transition name="burst" @after-leave="$emit('close')">
|
||||
<div v-if="content" class="modal">
|
||||
<h2 class="center">Available Themes</h2>
|
||||
<div
|
||||
v-for="theme of available_themes"
|
||||
:key="theme.filename"
|
||||
class="theme-card"
|
||||
@click.stop="chosen_theme = theme.filename"
|
||||
>
|
||||
<h3>
|
||||
<input
|
||||
:id="'select_theme' + theme.filename"
|
||||
v-model="chosen_theme"
|
||||
type="radio"
|
||||
:value="theme.filename"
|
||||
>
|
||||
<label :for="'select_theme' + theme.filename">{{ theme.name }}</label>
|
||||
</h3>
|
||||
<p>
|
||||
{{ theme.description }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: `ThemesListModal`,
|
||||
data() {return {
|
||||
container: false,
|
||||
content: false,
|
||||
chosen_theme: `dark`,
|
||||
default_theme: `dark`,
|
||||
style_id: `colour-transition`,
|
||||
themes: [
|
||||
{
|
||||
name: `Dark`,
|
||||
filename: `dark`,
|
||||
description: `The default theme of the website, this uses darker background colours with lighter coloured accents.`,
|
||||
show() { return true },
|
||||
},
|
||||
],
|
||||
}},
|
||||
mounted() {
|
||||
this.chosen_theme = localStorage.getItem(`tl-theme`) || this.default_theme;
|
||||
|
||||
// Add the CSS to the document so that we can transition nicely
|
||||
let colour_transition = document.createElement(`style`);
|
||||
colour_transition.id = this.style_id;
|
||||
colour_transition.innerText=`/* Transition colours for theme changes */
|
||||
* {
|
||||
transition: color .5s;
|
||||
transition: background-color .5s;
|
||||
}`;
|
||||
document.body.appendChild(colour_transition);
|
||||
|
||||
this.$nextTick(function() {
|
||||
this.container = true;
|
||||
});
|
||||
},
|
||||
computed: {
|
||||
available_themes() {
|
||||
let themes = [];
|
||||
|
||||
for (var theme of this.themes) {
|
||||
if (theme.show()) {
|
||||
themes.push(theme);
|
||||
};
|
||||
};
|
||||
|
||||
return themes;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
chosen_theme(val) {
|
||||
localStorage.setItem(`tl-theme`, val);
|
||||
document.getElementById(`theme`).href = `static/css/theme/${val}.css`;
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
document.body.removeChild(document.getElementById(this.style_id));
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.modal-container {
|
||||
background-color: var(--modal-container-background);
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: fixed;
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
z-index: 10;
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.modal {
|
||||
background-color: var(--modal-background);
|
||||
border-radius: var(--corner-rounding);
|
||||
color: var(--modal-text);
|
||||
text-align: center;
|
||||
max-height: 85%;
|
||||
padding: 0 15px;
|
||||
z-index: 11;
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 768px) {
|
||||
.modal {
|
||||
width: 50%;
|
||||
max-height: 75%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
27
src/css/scrollbar.css
Normal file
27
src/css/scrollbar.css
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
/* width */
|
||||
::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
}
|
||||
|
||||
/* Track */
|
||||
::-webkit-scrollbar-track {
|
||||
background: var(--scrollbar-background);
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
/* Handle */
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: var(--scrollbar-handle);
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
/* Handle on hover */
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--scrollbar-handle-hover);
|
||||
}
|
||||
|
||||
|
||||
* {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--scrollbar-handle) var(--scrollbar-background);
|
||||
}
|
||||
105
src/css/tooltips.css
Normal file
105
src/css/tooltips.css
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
.tooltip {
|
||||
display: block !important;
|
||||
z-index: 10000;
|
||||
}
|
||||
|
||||
.tooltip .tooltip-inner {
|
||||
background: var(--tooltip-colour);
|
||||
color: var(--tooltip-text);
|
||||
border-radius: 16px;
|
||||
padding: 5px 10px 4px;
|
||||
}
|
||||
|
||||
.tooltip .tooltip-arrow {
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-style: solid;
|
||||
position: absolute;
|
||||
margin: 5px;
|
||||
border-color: var(--tooltip-colour);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.tooltip[x-placement^="top"] {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.tooltip[x-placement^="top"] .tooltip-arrow {
|
||||
border-width: 5px 5px 0 5px;
|
||||
border-left-color: transparent !important;
|
||||
border-right-color: transparent !important;
|
||||
border-bottom-color: transparent !important;
|
||||
bottom: -5px;
|
||||
left: calc(50% - 5px);
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.tooltip[x-placement^="bottom"] {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.tooltip[x-placement^="bottom"] .tooltip-arrow {
|
||||
border-width: 0 5px 5px 5px;
|
||||
border-left-color: transparent !important;
|
||||
border-right-color: transparent !important;
|
||||
border-top-color: transparent !important;
|
||||
top: -5px;
|
||||
left: calc(50% - 5px);
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.tooltip[x-placement^="right"] {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.tooltip[x-placement^="right"] .tooltip-arrow {
|
||||
border-width: 5px 5px 5px 0;
|
||||
border-left-color: transparent !important;
|
||||
border-top-color: transparent !important;
|
||||
border-bottom-color: transparent !important;
|
||||
left: -5px;
|
||||
top: calc(50% - 5px);
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.tooltip[x-placement^="left"] {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.tooltip[x-placement^="left"] .tooltip-arrow {
|
||||
border-width: 5px 0 5px 5px;
|
||||
border-top-color: transparent !important;
|
||||
border-right-color: transparent !important;
|
||||
border-bottom-color: transparent !important;
|
||||
right: -5px;
|
||||
top: calc(50% - 5px);
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.tooltip.popover .popover-inner {
|
||||
background: var(--tooltip-colour);
|
||||
color: var(--tooltip-text);
|
||||
padding: 24px;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 5px 30px rgba(black, .1);
|
||||
}
|
||||
|
||||
.tooltip.popover .popover-arrow {
|
||||
border-color: var(--tooltip-colour);
|
||||
}
|
||||
|
||||
.tooltip[aria-hidden='true'] {
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
transition: opacity .15s, visibility .15s;
|
||||
}
|
||||
|
||||
.tooltip[aria-hidden='false'] {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
transition: opacity .15s;
|
||||
}
|
||||
62
src/css/transitions.css
Normal file
62
src/css/transitions.css
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
/* Transition for modal background appearing/disappearing */
|
||||
.fade-enter-active, .fade-leave-active {
|
||||
transition: opacity .5s;
|
||||
}
|
||||
|
||||
.fade-enter, .fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Transition for modal card appearing disappearing */
|
||||
.burst-enter-active {
|
||||
animation: burst-in .5s;
|
||||
}
|
||||
.burst-leave-active { animation: burst-out .5s; }
|
||||
|
||||
@keyframes burst-in {
|
||||
0% {
|
||||
transform: scale(0);
|
||||
}
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes burst-out {
|
||||
0% {
|
||||
transform: scale(1);
|
||||
}
|
||||
100% {
|
||||
transform: scale(0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@media only screen and (min-width: 768px) {
|
||||
@keyframes burst-in {
|
||||
0% {
|
||||
transform: scale(0);
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.25);
|
||||
}
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes burst-out {
|
||||
0% {
|
||||
transform: scale(1);
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.25);
|
||||
}
|
||||
100% {
|
||||
transform: scale(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
13
src/js/constants.js
Normal file
13
src/js/constants.js
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
const DEV_URL = `http://localhost:8080`;
|
||||
const PROD_URL = ``;
|
||||
|
||||
export const HOME_PAGE = process.env.NODE_ENV === `production`
|
||||
? PROD_URL
|
||||
: DEV_URL
|
||||
|
||||
export const AUTH_REDIRECT = `${HOME_PAGE}`;
|
||||
|
||||
export const STORAGE_KEYS = {
|
||||
token: `auth-token`,
|
||||
state: `auth-state`
|
||||
};
|
||||
10
src/js/prototypes.js
vendored
Normal file
10
src/js/prototypes.js
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
String.prototype.toTitleCase = function () {
|
||||
let words = this.split(` `);
|
||||
let new_words = [];
|
||||
for (var word of words) {
|
||||
new_words.push(
|
||||
`${word[0].toUpperCase()}${word.slice(1).toLowerCase()}`
|
||||
);
|
||||
};
|
||||
return new_words.join(` `);
|
||||
}
|
||||
56
src/main.js
Normal file
56
src/main.js
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
import Vue from 'vue';
|
||||
import VTooltip from 'v-tooltip';
|
||||
import * as clipboard from 'clipboard-polyfill/text';
|
||||
import TextareaAutosize from 'vue-textarea-autosize';
|
||||
import VueEllipseProgress from 'vue-ellipse-progress';
|
||||
import App from './App.vue';
|
||||
import {
|
||||
AUTH_REDIRECT,
|
||||
SPOTIFY_API_BASE,
|
||||
STORAGE_KEYS,
|
||||
HOME_PAGE
|
||||
} from './js/constants';
|
||||
|
||||
Vue.config.productionTip = false;
|
||||
VTooltip.enabled = window.innerWidth > 768
|
||||
|
||||
// Third-party plugins
|
||||
Vue.use(VTooltip);
|
||||
Vue.use(TextareaAutosize);
|
||||
Vue.use(VueEllipseProgress, `percent`);
|
||||
|
||||
// global mixings
|
||||
Vue.mixin({
|
||||
data() {return {
|
||||
api_url: SPOTIFY_API_BASE,
|
||||
auth_redirect: AUTH_REDIRECT,
|
||||
storage_key: STORAGE_KEYS,
|
||||
home_page: HOME_PAGE,
|
||||
}},
|
||||
computed: {
|
||||
api_token() {
|
||||
return sessionStorage.getItem(this.storage_key.token);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
css_var(var_name) {
|
||||
return getComputedStyle(document.documentElement).getPropertyValue(var_name);
|
||||
},
|
||||
auth_expired(error = null) {
|
||||
sessionStorage.removeItem(this.storage_key.token);
|
||||
window.location.hash = ``;
|
||||
if (error) {
|
||||
window.location.href = `${this.home_page}?error=${error}`;
|
||||
} else {
|
||||
window.location.href = this.home_page;
|
||||
};
|
||||
},
|
||||
copy_text: clipboard.writeText,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
// eslint-disable-next-line
|
||||
const app = new Vue({
|
||||
render: h => h(App),
|
||||
}).$mount('#app')
|
||||
Loading…
Add table
Add a link
Reference in a new issue