backup
This commit is contained in:
@@ -1,9 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import ContentManager from './components/managers/ContentManager.vue';
|
||||
import ToastManager from './components/managers/ToastManager.vue';
|
||||
import WindowManager from './components/managers/WindowManager.vue';
|
||||
import Content from './components/viewer/content/Content.vue';
|
||||
import StatusBar from './components/viewer/statusbar/StatusBar.vue';
|
||||
import TopBar from './components/viewer/TopBar.vue';
|
||||
|
||||
|
||||
import { CreateWindow } from '@/services/Windows'
|
||||
|
||||
@@ -14,19 +12,17 @@ async function start(){
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
setupTheme();
|
||||
setTheme('dark');
|
||||
start();
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="viewer">
|
||||
<ToastManager></ToastManager>
|
||||
<WindowManager></WindowManager>
|
||||
|
||||
|
||||
<TopBar></TopBar>
|
||||
<Content></Content>
|
||||
<StatusBar></StatusBar>
|
||||
|
||||
<ContentManager></ContentManager>
|
||||
<!-- Managers -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -3,27 +3,53 @@
|
||||
$themes: (
|
||||
dark: (
|
||||
background: #141414,
|
||||
background-light: #202020,
|
||||
background-line: #202324,
|
||||
background-fore: #10141f,
|
||||
|
||||
window-handle-background: #191919,
|
||||
window-background: #141414,
|
||||
window-border: #202324,
|
||||
window-shadow: #00000077,
|
||||
|
||||
button-background: #20202077,
|
||||
button-hover: #202020aa,
|
||||
button-active: #202020cc,
|
||||
|
||||
hover: #21262d,
|
||||
selected: #4a4a4b,
|
||||
border-color: #819796,
|
||||
border: #202324,
|
||||
text: #ebede9,
|
||||
container-shadow: #151d28,
|
||||
sticky-header-bg: #20202077
|
||||
sticky-header-bg: #20202077,
|
||||
|
||||
icon-invert: 100%
|
||||
),
|
||||
light: (
|
||||
background: #ffffff,
|
||||
background-light: #f9f9f9,
|
||||
background-line: #f0f0f0,
|
||||
background-fore: #ffffff,
|
||||
|
||||
window-handle-background: #f0f0f0,
|
||||
window-background: #ffffff,
|
||||
window-border: #e0e0e0,
|
||||
window-shadow: #d4d4d4,
|
||||
|
||||
button-background: #f0f0f0,
|
||||
button-hover: #e9e9e9,
|
||||
button-active: #d4d4d4,
|
||||
|
||||
border-color: #e0e0e0,
|
||||
border: #f0f0f0,
|
||||
hover: #e9e9e9,
|
||||
selected: #d4d4d4,
|
||||
text: #1e1e1e,
|
||||
container-shadow: #5f6774,
|
||||
sticky-header-bg: #fff
|
||||
sticky-header-bg: #fff,
|
||||
|
||||
icon-invert: 0%
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
@@ -1,25 +1,31 @@
|
||||
body {
|
||||
color: var(--text-color);
|
||||
color: var(--color-text);
|
||||
font-family: "BookInsanityRemake", Arial, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--background-color);
|
||||
background-color: var(--color-background);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
* {
|
||||
color: var(--text-color);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--link-color);
|
||||
color: var(--color-link);
|
||||
}
|
||||
|
||||
.icon {
|
||||
height: 12px;
|
||||
filter: invert(var(--color-icon-invert));
|
||||
}
|
||||
|
||||
* {
|
||||
font-family: BookInsanityRemake;
|
||||
}
|
||||
|
||||
|
||||
*::-webkit-scrollbar
|
||||
{
|
||||
width: 6px;
|
||||
@@ -39,6 +45,141 @@ a {
|
||||
color: var(--error-link);
|
||||
}
|
||||
|
||||
.buttons-row {
|
||||
width: 100%;
|
||||
padding-right: 10px;
|
||||
padding-left: 10px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.button-row {
|
||||
margin-left: 5px;
|
||||
margin-right: 5px;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.form-field {
|
||||
padding-bottom: 10px;
|
||||
display: flex;
|
||||
align-items: left;
|
||||
flex-direction: column;
|
||||
justify-content: left;
|
||||
}
|
||||
|
||||
hr {
|
||||
border: 0;
|
||||
height: 1px;
|
||||
width: 30%;
|
||||
overflow: visible;
|
||||
position: relative;
|
||||
margin: 16px auto 16px auto;
|
||||
background-color: var(--separator);
|
||||
}
|
||||
|
||||
hr:before {
|
||||
content: "";
|
||||
display: inline-block;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background-color: var(--separator);
|
||||
position: absolute;
|
||||
transform: rotate(45deg);
|
||||
top: -2.5px;
|
||||
left: 50%;
|
||||
margin: -1px 0 0 -1px;
|
||||
}
|
||||
|
||||
input[type=text], input[type=password], input[type=email] {
|
||||
background-color: var(--color-background-softer);
|
||||
border: none;
|
||||
padding: 8px;
|
||||
border-radius: 6px;
|
||||
color: var(--color-text);
|
||||
transition: 300ms background-color;
|
||||
border: solid 1px var(--color-border);
|
||||
}
|
||||
|
||||
|
||||
textarea {
|
||||
background-color: var(--color-background-softer);
|
||||
padding: 12px;
|
||||
color: var(--color-text);
|
||||
border: none;
|
||||
}
|
||||
|
||||
input[type=text]:focus, input[type=password]:focus, input[type=email]:focus {
|
||||
outline: none;
|
||||
background-color: var(--color-background-softest);
|
||||
}
|
||||
|
||||
textarea:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
button {
|
||||
margin-top: 5px;
|
||||
margin-bottom: 5px;
|
||||
|
||||
padding: 14px;
|
||||
font-size: 15px;
|
||||
border-radius: 6px;
|
||||
outline: none;
|
||||
|
||||
border: solid 1px var(--color-border);
|
||||
-webkit-box-shadow: 0px 0px 10px -2px rgba(0,0,0,0.25);
|
||||
-moz-box-shadow: 0px 0px 10px -2px rgba(0,0,0,0.25);
|
||||
box-shadow: 0px 0px 10px -2px rgba(0,0,0,0.25);
|
||||
|
||||
transition: 300ms background-color;
|
||||
background-color: var(--color-button-background);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: var(--color-button-hover);
|
||||
}
|
||||
|
||||
button:active {
|
||||
background-color: var(--color-button-active);
|
||||
}
|
||||
|
||||
.render-image {
|
||||
max-width: 600px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.confirm-form-button {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.parameters {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.param-element {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.param-text {
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.param-value {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.centered {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.window-wrapper {
|
||||
display: flex;
|
||||
@@ -51,4 +192,166 @@ a {
|
||||
-webkit-box-shadow: 0px 0px 10px -2px var(--shadow-color);
|
||||
-moz-box-shadow: 0px 0px 10px -2px var(--shadow-color);
|
||||
box-shadow: 0px 0px 10px -2px var(--shadow-color);
|
||||
}
|
||||
}
|
||||
|
||||
.document {
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.document.centered {
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.document.item {
|
||||
text-align: center;
|
||||
width: 220px;
|
||||
}
|
||||
|
||||
.document.item img {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
}
|
||||
|
||||
|
||||
.document h1 {
|
||||
font-weight: normal;
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
.document b {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.text-icon {
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
margin-bottom: -4px;
|
||||
}
|
||||
|
||||
.invert {
|
||||
filter: invert(0.9);
|
||||
}
|
||||
|
||||
.main-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.row {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
overflow-x: auto;
|
||||
scrollbar-width: thin;
|
||||
}
|
||||
|
||||
|
||||
span.important {
|
||||
font-family: NodestoCapsCondensed;
|
||||
font-size: 24px;
|
||||
line-height: 32px;
|
||||
}
|
||||
|
||||
span.common {
|
||||
color: var(--color-common);
|
||||
}
|
||||
span.uncommon {
|
||||
color: var(--color-uncommon);
|
||||
}
|
||||
span.rare {
|
||||
color: var(--color-rare);
|
||||
}
|
||||
span.very-rare {
|
||||
color: var(--color-very-rare);
|
||||
}
|
||||
span.legendary {
|
||||
color: var(--color-legendary);
|
||||
}
|
||||
span.artifact {
|
||||
color: var(--color-artifact);
|
||||
}
|
||||
|
||||
.form-container {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.form-element {
|
||||
padding: 10px 0 10px 0;
|
||||
margin: 0 10px 0 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-bottom: 1px dashed var(--color-border);
|
||||
}
|
||||
|
||||
.form-element label {
|
||||
flex-grow: 0;
|
||||
margin-right: 6px;
|
||||
margin-left: 6px;
|
||||
}
|
||||
|
||||
.form-element.centered {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.grow {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
|
||||
.subsection.border:first-child {
|
||||
border-left: none;
|
||||
}
|
||||
|
||||
.subsection.border {
|
||||
border-left: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.subsection {
|
||||
margin-left: 5px;
|
||||
margin-right: 5px;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: left;
|
||||
justify-content: left;
|
||||
}
|
||||
|
||||
.subsection.left {
|
||||
align-items: left;
|
||||
justify-content: left;
|
||||
|
||||
}
|
||||
|
||||
.subsection.right {
|
||||
align-items: right;
|
||||
justify-content: right;
|
||||
|
||||
}
|
||||
|
||||
.subsection.center {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.window-enter-active,
|
||||
.window-leave-active {
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
.window-enter-from,
|
||||
.window-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateY(15px);
|
||||
}
|
||||
|
||||
.window-wrapper {
|
||||
background-color: var(--window-background);
|
||||
|
||||
/* backdrop-filter: blur(10px); */
|
||||
position: fixed;
|
||||
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
18
frontend/app/components/managers/ContentManager.vue
Normal file
18
frontend/app/components/managers/ContentManager.vue
Normal file
@@ -0,0 +1,18 @@
|
||||
<script setup>
|
||||
import Content from '../viewer/content/Content.vue';
|
||||
import StatusBar from '../viewer/statusbar/StatusBar.vue';
|
||||
import TopBar from '../viewer/TopBar.vue';
|
||||
|
||||
import { ShowContent } from '../../services/Content.js';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-show="ShowContent">
|
||||
<TopBar></TopBar>
|
||||
<Content></Content>
|
||||
<StatusBar></StatusBar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
120
frontend/app/components/managers/ToastManager.vue
Normal file
120
frontend/app/components/managers/ToastManager.vue
Normal file
@@ -0,0 +1,120 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
|
||||
import { emitter } from '@/services/Emitter';
|
||||
|
||||
const text = ref("");
|
||||
const toast = ref(null);
|
||||
|
||||
let toastQueue = [];
|
||||
let displayingToast = false;
|
||||
|
||||
function DisplayToast(){
|
||||
if(displayingToast) return;
|
||||
if(toastQueue.length == 0) return;
|
||||
|
||||
displayingToast = true;
|
||||
let data = toastQueue.pop();
|
||||
|
||||
text.value = data.text;
|
||||
|
||||
toast.value.classList.add(data.color);
|
||||
toast.value.classList.add("show");
|
||||
setTimeout(() => {
|
||||
toast.value.classList.add("sliding");
|
||||
setTimeout(() => {
|
||||
toast.value.style = {};
|
||||
toast.value.classList.remove("show");
|
||||
toast.value.classList.remove("sliding");
|
||||
toast.value.classList.remove(data.color);
|
||||
displayingToast = false;
|
||||
DisplayToast();
|
||||
}, 400);
|
||||
}, data.duration);
|
||||
}
|
||||
|
||||
emitter.on('toast', data => {
|
||||
toastQueue.push(data);
|
||||
DisplayToast();
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<template>
|
||||
<div class="toast" ref="toast">
|
||||
<div class="toast-container">{{ text }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<style scoped lang="scss">
|
||||
.toast-container {
|
||||
height: 100%;
|
||||
background-color: var(--color-background-soft);
|
||||
padding: 10px;
|
||||
margin-left: 5px;
|
||||
border-top-right-radius: 6px;
|
||||
border-bottom-right-radius: 6px;
|
||||
transform: translate(2px,0px)
|
||||
}
|
||||
|
||||
.toast {
|
||||
position: absolute;
|
||||
display: none;
|
||||
|
||||
top: 10px;
|
||||
left: 50%;
|
||||
transform: translate(-50%, 0);
|
||||
|
||||
min-width: 400px;
|
||||
min-height: 40px;
|
||||
border-radius: 6px;
|
||||
text-align: center;
|
||||
|
||||
z-index: 9999999;
|
||||
|
||||
|
||||
animation: slide-in 0.4s ease-in-out;
|
||||
@keyframes slide-in {
|
||||
0% {
|
||||
transform: translate(-50%,-50px);
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&.sliding {
|
||||
@keyframes slide-out {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
transform: translate(-50%,-50px);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
animation: slide-out .4s ease-in-out forwards;
|
||||
}
|
||||
|
||||
&.show {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Colors!!!! */
|
||||
|
||||
&.red {
|
||||
background-color: rgb(243, 68, 68);
|
||||
}
|
||||
|
||||
&.green {
|
||||
background-color: rgb(92, 199, 92);
|
||||
}
|
||||
|
||||
&.aqua {
|
||||
background-color: rgb(113, 250, 250);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -29,7 +29,7 @@ const windows = Windows();
|
||||
}
|
||||
|
||||
.window-wrapper {
|
||||
background-color: var(--window-background);
|
||||
background-color: var(--color-window-background);
|
||||
|
||||
/* backdrop-filter: blur(10px); */
|
||||
position: fixed;
|
||||
@@ -37,6 +37,15 @@ const windows = Windows();
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
align-items: center;
|
||||
|
||||
border: solid 1px var(--color-window-border);
|
||||
|
||||
/* opacity: 0; */
|
||||
user-select: none;
|
||||
-webkit-box-shadow: 0px 0px 10px -2px var(--color-window-shadow);
|
||||
-moz-box-shadow: 0px 0px 10px -2px var(--color-window-shadow);
|
||||
box-shadow: 0px 0px 10px -2px var(--color-window-shadow);
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -21,7 +21,7 @@ import TopSearchBar from './topbar/TopSearchBar.vue';
|
||||
flex-shrink: 0;
|
||||
min-height: 40px;
|
||||
width: 100%;
|
||||
background-color: var(--top-bar-background-color);
|
||||
background-color: var(--color-background-light);
|
||||
display: flex;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue';
|
||||
import Note from './Note.vue';
|
||||
|
||||
const emitter = useEmitter();
|
||||
import { emitter } from '~/services/Emitter';
|
||||
|
||||
let noteData = ref([]);
|
||||
|
||||
@@ -61,6 +60,7 @@ emitter.on("delete-note", (key) => {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
height: 100%;
|
||||
background-color: var(--color-background);
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script setup>
|
||||
import { ref,onMounted } from 'vue';
|
||||
const emitter = useEmitter();
|
||||
import { emitter } from '~/services/Emitter';
|
||||
|
||||
|
||||
const statusIcon = ref(null);
|
||||
const statusMessage = ref(null);
|
||||
|
||||
@@ -22,7 +22,7 @@ import FetchStatus from './FetchStatus.vue';
|
||||
min-height: 24px;
|
||||
max-height: 24px;
|
||||
width: 100%;
|
||||
background-color: var(--top-bar-background-color);
|
||||
background-color: var(--color-background-light);
|
||||
display: flex;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
42
frontend/app/components/windows/ExampleWindow.vue
Normal file
42
frontend/app/components/windows/ExampleWindow.vue
Normal file
@@ -0,0 +1,42 @@
|
||||
<script setup>
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { SetupHandle, SetSize, ResetPosition } from '@/services/Windows';
|
||||
|
||||
import WindowHandle from './partials/WindowHandle.vue';
|
||||
|
||||
const handle = ref(null);
|
||||
|
||||
const props = defineProps(['data']);
|
||||
const data = props.data;
|
||||
|
||||
let id = data.id;
|
||||
|
||||
const test = ref(null)
|
||||
|
||||
onMounted(() => {
|
||||
SetupHandle(id, handle);
|
||||
SetSize(id, {width: 500, height: 380});
|
||||
ResetPosition(id, "center");
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
<template>
|
||||
<div class="window-wrapper" :id="'window-wrapper-' + id">
|
||||
<WindowHandle :window="id" ref="handle"></WindowHandle>
|
||||
|
||||
<!-- Body -->
|
||||
<div ref="test"></div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<style scoped>
|
||||
.window-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -1,8 +1,19 @@
|
||||
<script setup>
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { SetupHandle, SetSize, ResetPosition } from '@/services/Windows';
|
||||
import {
|
||||
SetupHandle,
|
||||
SetSize,
|
||||
ResetPosition,
|
||||
SetResizable,
|
||||
SetMovable,
|
||||
ClearWindow,
|
||||
CreateWindow,
|
||||
} from '@/services/Windows';
|
||||
|
||||
import WindowHandle from './partials/WindowHandle.vue';
|
||||
import { DisplayToast } from '~/services/Toaster';
|
||||
import Server from '~/services/Server';
|
||||
import { SetUser } from '~/services/User';
|
||||
|
||||
const handle = ref(null);
|
||||
|
||||
@@ -11,13 +22,44 @@ const data = props.data;
|
||||
|
||||
let id = data.id;
|
||||
|
||||
const test = ref(null)
|
||||
const username = ref("");
|
||||
const password = ref("");
|
||||
|
||||
onMounted(() => {
|
||||
SetupHandle(id, handle);
|
||||
SetSize(id, {width: 500, height: 380});
|
||||
SetSize(id, {width: 450, height: 480});
|
||||
SetMovable(id, false);
|
||||
SetResizable(id, false);
|
||||
ResetPosition(id, "center");
|
||||
});
|
||||
|
||||
function login() {
|
||||
Server().post('/user/login', { username: username.value, password: password.value }).then((response) => {
|
||||
const data = response.data;
|
||||
console.log(data);
|
||||
|
||||
if(data.status == "error"){
|
||||
DisplayToast('red', "Wrong username or password", 3000)
|
||||
} else {
|
||||
SetUser(data.token);
|
||||
|
||||
ShowMainMenu();
|
||||
}
|
||||
}).catch((error) => {
|
||||
console.log(error);
|
||||
if(error.response.status == 429){
|
||||
// errorMessage.value = error.response.data;
|
||||
} else {
|
||||
// errorMessage.value = "Hi ha hagut un error intern, torna'ho a provar més tard";
|
||||
console.log(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function toRegister(){
|
||||
CreateWindow('register');
|
||||
ClearWindow('login');
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -26,8 +68,30 @@ onMounted(() => {
|
||||
<WindowHandle :window="id" ref="handle"></WindowHandle>
|
||||
|
||||
<!-- Body -->
|
||||
<div ref="test">
|
||||
<p>Hola</p>
|
||||
<div class="vert-expand">
|
||||
<picture align="center">
|
||||
<source media="(prefers-color-scheme: dark)" srcset="/img/logo-splash.png">
|
||||
<source media="(prefers-color-scheme: light)" srcset="/img/logo-splash-light.png">
|
||||
<img alt="Dragonroll logo" src="/img/logo-splash.png" class="splash-image" draggable="false">
|
||||
</picture>
|
||||
|
||||
<form v-on:submit.prevent="login">
|
||||
<div class="form-field">
|
||||
<label for="username">Username</label>
|
||||
<input id="username-field" type="text" placeholder="Enter your username here..." name="username" v-model="username" autocomplete="off" >
|
||||
</div>
|
||||
<div class="form-field">
|
||||
<label for="password">Password</label>
|
||||
<input id="password-field" type="password" placeholder="Enter your password..." name="password" v-model="password" autocomplete="off" >
|
||||
</div>
|
||||
<div class="form-field">
|
||||
<button class="btn-primary sound-click">Log in</button>
|
||||
</div>
|
||||
<div class="form-field center">
|
||||
<p>You don't have an account? <a href="#" @click.prevent="toRegister">Register</a></p>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -35,10 +99,41 @@ onMounted(() => {
|
||||
|
||||
|
||||
<style scoped>
|
||||
p {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.vert-expand {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.window-wrapper {
|
||||
user-select: none;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.splash-image {
|
||||
width: 450px;
|
||||
}
|
||||
|
||||
form {
|
||||
margin-left: 30px;
|
||||
margin-right: 30px;
|
||||
}
|
||||
|
||||
label {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
|
||||
|
||||
42
frontend/app/components/windows/RegisterWindow.vue
Normal file
42
frontend/app/components/windows/RegisterWindow.vue
Normal file
@@ -0,0 +1,42 @@
|
||||
<script setup>
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { SetupHandle, SetSize, ResetPosition } from '@/services/Windows';
|
||||
|
||||
import WindowHandle from './partials/WindowHandle.vue';
|
||||
|
||||
const handle = ref(null);
|
||||
|
||||
const props = defineProps(['data']);
|
||||
const data = props.data;
|
||||
|
||||
let id = data.id;
|
||||
|
||||
const test = ref(null)
|
||||
|
||||
onMounted(() => {
|
||||
SetupHandle(id, handle);
|
||||
SetSize(id, {width: 500, height: 380});
|
||||
ResetPosition(id, "center");
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
<template>
|
||||
<div class="window-wrapper" :id="'window-wrapper-' + id">
|
||||
<WindowHandle :window="id" ref="handle"></WindowHandle>
|
||||
|
||||
<!-- Body -->
|
||||
<div ref="test"></div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<style scoped>
|
||||
.window-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -133,7 +133,7 @@ defineExpose({
|
||||
|
||||
display: flex;
|
||||
|
||||
background-color: var(--color-handler);
|
||||
background-color: var(--color-window-handle-background);
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
47
frontend/app/composables/useTheme.ts
Normal file
47
frontend/app/composables/useTheme.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { ref, onMounted, watch } from 'vue'
|
||||
|
||||
type Theme = 'light' | 'dark'
|
||||
type Accent = 'katlum'
|
||||
|
||||
const theme = ref<Theme>('light')
|
||||
const accent = ref<Accent>('katlum')
|
||||
|
||||
const applyTheme = () => {
|
||||
document.documentElement.setAttribute('data-theme', theme.value)
|
||||
document.documentElement.setAttribute('data-accent', accent.value)
|
||||
}
|
||||
|
||||
const setTheme = (value: Theme) => {
|
||||
theme.value = value
|
||||
localStorage.setItem('theme', value)
|
||||
applyTheme();
|
||||
}
|
||||
|
||||
const setAccent = (value: Accent) => {
|
||||
accent.value = value
|
||||
localStorage.setItem('accent', value)
|
||||
applyTheme();
|
||||
}
|
||||
|
||||
const setupTheme = () => {
|
||||
const savedTheme = localStorage.getItem('theme') as Theme | null
|
||||
const savedAccent = localStorage.getItem('accent') as Accent | null
|
||||
|
||||
const media = window.matchMedia('(prefers-color-scheme: dark)')
|
||||
|
||||
theme.value = savedTheme || (media.matches ? 'dark' : 'light')
|
||||
accent.value = savedAccent || 'katlum'
|
||||
|
||||
applyTheme()
|
||||
|
||||
media.addEventListener('change', (e) => {
|
||||
if (!localStorage.getItem('theme')) {
|
||||
theme.value = e.matches ? 'dark' : 'light'
|
||||
applyTheme()
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
watch([theme, accent], applyTheme)
|
||||
|
||||
export { theme, accent, setTheme, setAccent, setupTheme}
|
||||
@@ -1,11 +0,0 @@
|
||||
import mitt from 'mitt'
|
||||
|
||||
export default defineNuxtPlugin(() => {
|
||||
const emitter = mitt()
|
||||
|
||||
return {
|
||||
provide: {
|
||||
emitter
|
||||
}
|
||||
}
|
||||
})
|
||||
11
frontend/app/services/BackendURL.js
Normal file
11
frontend/app/services/BackendURL.js
Normal file
@@ -0,0 +1,11 @@
|
||||
var backendUrl = ''
|
||||
if (import.meta.env.PROD) {
|
||||
backendUrl = 'https://api.aranroig.com/';
|
||||
} else {
|
||||
backendUrl = 'http://localhost:5000/'
|
||||
}
|
||||
|
||||
|
||||
export {
|
||||
backendUrl
|
||||
};
|
||||
12
frontend/app/services/Content.js
Normal file
12
frontend/app/services/Content.js
Normal file
@@ -0,0 +1,12 @@
|
||||
import { ref } from 'vue';
|
||||
|
||||
const ShowContent = ref(false);
|
||||
|
||||
function SetShowContent(value) {
|
||||
ShowContent.value = value;
|
||||
}
|
||||
|
||||
export {
|
||||
ShowContent,
|
||||
SetShowContent
|
||||
}
|
||||
3
frontend/app/services/Emitter.js
Normal file
3
frontend/app/services/Emitter.js
Normal file
@@ -0,0 +1,3 @@
|
||||
import mitt from 'mitt'
|
||||
|
||||
export const emitter = mitt();
|
||||
21
frontend/app/services/Server.js
Normal file
21
frontend/app/services/Server.js
Normal file
@@ -0,0 +1,21 @@
|
||||
import axios from 'axios';
|
||||
|
||||
import { backendUrl } from './BackendURL';
|
||||
|
||||
const server = axios.create({
|
||||
baseURL: backendUrl,
|
||||
headers: {
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
}
|
||||
});
|
||||
|
||||
// Attach token dynamically on each request via interceptor
|
||||
server.interceptors.request.use((config) => {
|
||||
const token = localStorage.getItem('token');
|
||||
if (token) {
|
||||
config.headers.Authorization = `Bearer ${token}`;
|
||||
}
|
||||
return config;
|
||||
});
|
||||
|
||||
export default () => server;
|
||||
9
frontend/app/services/Toaster.js
Normal file
9
frontend/app/services/Toaster.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import { emitter } from './Emitter';
|
||||
|
||||
function DisplayToast(color, text, duration = 1000){
|
||||
emitter.emit("toast", {color, text, duration});
|
||||
}
|
||||
|
||||
export {
|
||||
DisplayToast,
|
||||
}
|
||||
80
frontend/app/services/User.js
Normal file
80
frontend/app/services/User.js
Normal file
@@ -0,0 +1,80 @@
|
||||
import { ref } from 'vue';
|
||||
import Server from './Server';
|
||||
|
||||
const UserStatus = ref(0);
|
||||
|
||||
function parseJwt(token) {
|
||||
return JSON.parse(atob(token.split('.')[1]));
|
||||
}
|
||||
|
||||
function SetUser(token){
|
||||
localStorage.setItem('token', token);
|
||||
UserStatus.value = 1;
|
||||
}
|
||||
|
||||
async function HasAdmin(){
|
||||
let response = await Server().get('/user/has-admin');
|
||||
return response.data.status != "init";
|
||||
}
|
||||
|
||||
async function SetUserSetting(key, value){
|
||||
let user = GetUser();
|
||||
if(!user.settings) user.settings = {};
|
||||
user.settings[key] = value;
|
||||
const response = await Server().post('/user/update-settings', { settings: user.settings });
|
||||
return response.data.settings;
|
||||
}
|
||||
|
||||
async function GetUserSetting(key){
|
||||
const response = await Server().get('/user/get-settings');
|
||||
if (response.data.settings)
|
||||
return response.data.settings[key];
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function GetUser(){
|
||||
const token = localStorage.getItem('token');
|
||||
|
||||
if(token){
|
||||
const data = parseJwt(token);
|
||||
|
||||
// Check if token is expired
|
||||
const now = Date.now() / 1000;
|
||||
if(now > data.exp){
|
||||
LogoutUser();
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function IsAdmin(){
|
||||
const user = GetUser();
|
||||
if(user){
|
||||
return user.admin;
|
||||
}
|
||||
}
|
||||
|
||||
function LoadUser(){
|
||||
const token = localStorage.getItem('token');
|
||||
if(token) UserStatus.value = 1;
|
||||
}
|
||||
|
||||
function LogoutUser(){
|
||||
localStorage.removeItem("token");
|
||||
UserStatus.value = 0;
|
||||
}
|
||||
|
||||
export {
|
||||
UserStatus,
|
||||
GetUser,
|
||||
SetUser,
|
||||
LoadUser,
|
||||
IsAdmin,
|
||||
LogoutUser,
|
||||
HasAdmin,
|
||||
GetUserSetting,
|
||||
SetUserSetting
|
||||
}
|
||||
@@ -3,22 +3,29 @@ import { ref } from 'vue'
|
||||
const windows = ref([]);
|
||||
|
||||
import LoginWindow from '~/components/windows/LoginWindow.vue';
|
||||
import RegisterWindow from '~/components/windows/RegisterWindow.vue';
|
||||
import ExampleWindow from '~/components/windows/ExampleWindow.vue';
|
||||
|
||||
let windowMap = {
|
||||
login: LoginWindow
|
||||
login: LoginWindow,
|
||||
register: RegisterWindow,
|
||||
example: ExampleWindow
|
||||
};
|
||||
|
||||
async function InjectWindow(window_type, plugin, window_component) {
|
||||
let systemWidows = {};
|
||||
systemWidows[window_type] = (await import(`../../plugins/${plugin}/views/${window_component}.vue`)).default;
|
||||
windowMap = { ...windowMap, ...systemWidows };
|
||||
}
|
||||
|
||||
// Presets
|
||||
const defValues = {
|
||||
'example': {
|
||||
id: "example",
|
||||
title: "Example",
|
||||
close: () => ClearWindow('example')
|
||||
},
|
||||
'login': {
|
||||
id: 'login',
|
||||
title: 'Login',
|
||||
},
|
||||
'register': {
|
||||
id: 'register',
|
||||
title: 'Register'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,7 +66,9 @@ function SetupHandle(id, handle) {
|
||||
SetOnTop(id);
|
||||
});
|
||||
|
||||
// Move window listeners
|
||||
handler.addEventListener("mousedown", (event) => {
|
||||
if(win.noMove) return;
|
||||
draggingWindow = true;
|
||||
|
||||
let windowRect = currentWindow.getBoundingClientRect();
|
||||
@@ -67,8 +76,8 @@ function SetupHandle(id, handle) {
|
||||
offsetY = windowRect.top - event.clientY;
|
||||
})
|
||||
|
||||
// Move window listeners
|
||||
document.addEventListener("mousemove", (event) => {
|
||||
if(win.noMove) return;
|
||||
if (!draggingWindow) return;
|
||||
|
||||
if (event.clientX + offsetX < -currentWindow.getBoundingClientRect().width + 20) currentWindow.style.left = (-currentWindow.getBoundingClientRect().width + 20) + "px";
|
||||
@@ -81,6 +90,7 @@ function SetupHandle(id, handle) {
|
||||
})
|
||||
|
||||
document.addEventListener("mouseup", (event) => {
|
||||
if(win.noMove) return;
|
||||
draggingWindow = false;
|
||||
// ummm suposo que no pots tancar mentres mous?
|
||||
SaveWindowPos({ id, x: parseInt(currentWindow.style.left, 10), y: parseInt(currentWindow.style.top, 10) });
|
||||
@@ -126,6 +136,11 @@ function SetResizable(id, resizable) {
|
||||
win.resizable = resizable;
|
||||
}
|
||||
|
||||
function SetMovable(id, movable) {
|
||||
let win = GetWindowWithId(id);
|
||||
win.noMove = !movable;
|
||||
}
|
||||
|
||||
function SetSize(id, size) {
|
||||
let currentWindowId = "window-wrapper-" + id;
|
||||
let currentWindow = document.getElementById(currentWindowId);
|
||||
@@ -284,10 +299,10 @@ export {
|
||||
SetMaxSize,
|
||||
SetMinSize,
|
||||
SetPosition,
|
||||
SetMovable,
|
||||
ResetPosition,
|
||||
Windows,
|
||||
WindowMap,
|
||||
InjectWindow,
|
||||
ReloadRef,
|
||||
ClearWindows,
|
||||
CreateWindow,
|
||||
|
||||
Reference in New Issue
Block a user