Migration to vue3 continued

Replace vuex with pinia
Fix some routing issues
Fix most vue3 breaking changes
This commit is contained in:
Kloon ImKloon 2023-08-28 17:48:55 +02:00
parent b9574d9e62
commit 7c91ba03b7
No known key found for this signature in database
GPG Key ID: CCF1C86A995C5B6A
66 changed files with 1996 additions and 1715 deletions

View File

@ -4,7 +4,7 @@
"node": true
},
"extends": [
"plugin:vue/essential",
"plugin:vue/vue3-essential",
"eslint:recommended",
"@vue/eslint-config-prettier"
],

View File

@ -8,25 +8,19 @@
content="width=device-width, initial-scale=1, user-scalable=no"
/>
[{[ if .ReCaptcha -]}]
<script src="[{[ .ReCaptchaHost ]}]/recaptcha/api.js?render=explicit"></script>
[{[ end ]}]
<title>
[{[ if .Name -]}][{[ .Name ]}][{[ else ]}]File Browser[{[ end ]}]
</title>
<title>File Browser</title>
<link
rel="icon"
type="image/png"
sizes="32x32"
href="[{[ .StaticURL ]}]/img/icons/favicon-32x32.png"
href="/img/icons/favicon-32x32.png"
/>
<link
rel="icon"
type="image/png"
sizes="16x16"
href="[{[ .StaticURL ]}]/img/icons/favicon-16x16.png"
href="/img/icons/favicon-16x16.png"
/>
<!-- Add to home screen for Android and modern mobile browsers -->
@ -35,49 +29,63 @@
id="manifestPlaceholder"
crossorigin="use-credentials"
/>
<meta
name="theme-color"
content="[{[ if .Color -]}][{[ .Color ]}][{[ else ]}]#2979ff[{[ end ]}]"
/>
<meta name="theme-color" content="#2979ff" />
<!-- Add to home screen for Safari on iOS/iPadOS -->
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
<meta name="apple-mobile-web-app-title" content="assets" />
<link
rel="apple-touch-icon"
href="[{[ .StaticURL ]}]/img/icons/apple-touch-icon.png"
/>
<link rel="apple-touch-icon" href="/img/icons/apple-touch-icon.png" />
<!-- Add to home screen for Windows -->
<meta
name="msapplication-TileImage"
content="[{[ .StaticURL ]}]/img/icons/mstile-144x144.png"
/>
<meta
name="msapplication-TileColor"
content="[{[ if .Color -]}][{[ .Color ]}][{[ else ]}]#2979ff[{[ end ]}]"
content="/img/icons/mstile-144x144.png"
/>
<meta name="msapplication-TileColor" content="#2979ff" />
<!-- Inject Some Variables and generate the manifest json -->
<script>
<!-- Json is actually a JS object, assign it directly -->
window.FileBrowser = [{[ .Json ]}];
<!-- Global function to prepend static url -->
// We can assign JSON directly
window.FileBrowser = {
AuthMethod: "json",
BaseURL: "",
CSS: false,
Color: "",
DisableExternal: false,
DisableUsedPercentage: false,
EnableExec: true,
EnableThumbs: true,
LoginPage: true,
Name: "",
NoAuth: false,
ReCaptcha: false,
ResizePreview: true,
Signup: false,
StaticURL: "",
Theme: "",
TusSettings: { chunkSize: 10485760, retryCount: 5 },
Version: "(untracked)",
};
// Global function to prepend static url
window.__prependStaticUrl = (url) => {
return `${window.FileBrowser.StaticURL}/${url.replace(/^\/+/, '')}`;
return `${window.FileBrowser.StaticURL}/${url.replace(/^\/+/, "")}`;
};
var dynamicManifest = {
name: window.FileBrowser.Name || "File Browser",
short_name: window.FileBrowser.Name || "File Browser",
icons: [
{
src: window.__prependStaticUrl("/img/icons/android-chrome-192x192.png"),
src: window.__prependStaticUrl(
"/img/icons/android-chrome-192x192.png"
),
sizes: "192x192",
type: "image/png",
},
{
src: window.__prependStaticUrl("/img/icons/android-chrome-512x512.png"),
src: window.__prependStaticUrl(
"/img/icons/android-chrome-512x512.png"
),
sizes: "512x512",
type: "image/png",
},
@ -180,14 +188,5 @@
</div>
<script type="module" src="/src/main.js"></script>
[{[ if .Theme -]}]
<link
rel="stylesheet"
href="[{[ .StaticURL ]}]/themes/[{[ .Theme ]}].css"
/>
[{[ end ]}] [{[ if .CSS -]}]
<link rel="stylesheet" href="[{[ .StaticURL ]}]/custom.css" />
[{[ end ]}]
</body>
</html>

10
frontend/jsconfig.json Normal file
View File

@ -0,0 +1,10 @@
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}

File diff suppressed because it is too large Load Diff

View File

@ -14,46 +14,48 @@
},
"dependencies": {
"@vue/compat": "^3.3.4",
"ace-builds": "^1.23.4",
"@vueuse/core": "^10.4.1",
"ace-builds": "^1.24.1",
"clipboard": "^2.0.11",
"core-js": "^3.32.0",
"core-js": "^3.32.1",
"css-vars-ponyfill": "^2.4.8",
"filesize": "^10.0.8",
"filesize": "^10.0.12",
"js-base64": "^3.7.5",
"jwt-decode": "^3.1.2",
"lodash.clonedeep": "^4.5.0",
"lodash.throttle": "^4.1.1",
"material-icons": "^1.13.9",
"material-icons": "^1.13.10",
"moment": "^2.29.4",
"normalize.css": "^8.0.1",
"noty": "^3.2.0-beta",
"pinia": "^2.1.6",
"pretty-bytes": "^6.1.1",
"qrcode.vue": "^3.4.1",
"tus-js-client": "^3.1.1",
"utif": "^3.1.0",
"vue": "^3.3.4",
"vue-async-computed": "^3.9.0",
"vue-i18n": "^9.2.2",
"vue-lazyload": "^1.3.5",
"vue-lazyload": "^3.0.0",
"vue-router": "^4.2.4",
"vue-simple-progress": "^1.1.1",
"vuex": "^4.1.0",
"whatwg-fetch": "^3.6.17"
},
"devDependencies": {
"@intlify/unplugin-vue-i18n": "^0.12.2",
"@intlify/unplugin-vue-i18n": "^0.12.3",
"@vitejs/plugin-legacy": "^4.1.1",
"@vitejs/plugin-vue": "^4.2.3",
"@vitejs/plugin-vue": "^4.3.3",
"@vue/eslint-config-prettier": "^8.0.0",
"autoprefixer": "^10.4.14",
"eslint": "^8.46.0",
"autoprefixer": "^10.4.15",
"eslint": "^8.48.0",
"eslint-plugin-prettier": "^5.0.0",
"eslint-plugin-vue": "^9.16.1",
"eslint-plugin-vue": "^9.17.0",
"jsdom": "^22.1.0",
"postcss": "^8.4.27",
"prettier": "^3.0.1",
"postcss": "^8.4.28",
"prettier": "^3.0.2",
"terser": "^5.19.2",
"vite": "^4.4.9",
"vite-plugin-compression2": "^0.10.3"
"vite-plugin-compression2": "^0.10.4",
"vite-plugin-rewrite-all": "^1.0.1"
},
"browserslist": [
"> 1%",

View File

@ -1,144 +1,193 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, user-scalable=no"
/>
[{[ if .ReCaptcha -]}]
[{[ if .ReCaptcha -]}]
<script src="[{[ .ReCaptchaHost ]}]/recaptcha/api.js?render=explicit"></script>
[{[ end ]}]
[{[ end ]}]
<title>[{[ if .Name -]}][{[ .Name ]}][{[ else ]}]File Browser[{[ end ]}]</title>
<title>
[{[ if .Name -]}][{[ .Name ]}][{[ else ]}]File Browser[{[ end ]}]
</title>
<link rel="icon" type="image/png" sizes="32x32" href="[{[ .StaticURL ]}]/img/icons/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="[{[ .StaticURL ]}]/img/icons/favicon-16x16.png">
<link
rel="icon"
type="image/png"
sizes="32x32"
href="[{[ .StaticURL ]}]/img/icons/favicon-32x32.png"
/>
<link
rel="icon"
type="image/png"
sizes="16x16"
href="[{[ .StaticURL ]}]/img/icons/favicon-16x16.png"
/>
<!-- Add to home screen for Android and modern mobile browsers -->
<link rel="manifest" id="manifestPlaceholder" crossorigin="use-credentials">
<meta name="theme-color" content="[{[ if .Color -]}][{[ .Color ]}][{[ else ]}]#2979ff[{[ end ]}]">
<!-- Add to home screen for Android and modern mobile browsers -->
<link
rel="manifest"
id="manifestPlaceholder"
crossorigin="use-credentials"
/>
<meta
name="theme-color"
content="[{[ if .Color -]}][{[ .Color ]}][{[ else ]}]#2979ff[{[ end ]}]"
/>
<!-- Add to home screen for Safari on iOS/iPadOS -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="assets">
<link rel="apple-touch-icon" href="[{[ .StaticURL ]}]/img/icons/apple-touch-icon.png">
<!-- Add to home screen for Safari on iOS/iPadOS -->
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
<meta name="apple-mobile-web-app-title" content="assets" />
<link
rel="apple-touch-icon"
href="[{[ .StaticURL ]}]/img/icons/apple-touch-icon.png"
/>
<!-- Add to home screen for Windows -->
<meta name="msapplication-TileImage" content="[{[ .StaticURL ]}]/img/icons/mstile-144x144.png">
<meta name="msapplication-TileColor" content="[{[ if .Color -]}][{[ .Color ]}][{[ else ]}]#2979ff[{[ end ]}]">
<!-- Add to home screen for Windows -->
<meta
name="msapplication-TileImage"
content="[{[ .StaticURL ]}]/img/icons/mstile-144x144.png"
/>
<meta
name="msapplication-TileColor"
content="[{[ if .Color -]}][{[ .Color ]}][{[ else ]}]#2979ff[{[ end ]}]"
/>
<!-- Inject Some Variables and generate the manifest json -->
<script>
window.FileBrowser = JSON.parse('[{[ .Json ]}]');
<!-- Inject Some Variables and generate the manifest json -->
<script>
// We can assign JSON directly
window.FileBrowser = [{[ .Json ]}];
// Global function to prepend static url
window.__prependStaticUrl = (url) => {
return `${window.FileBrowser.StaticURL}/${url.replace(/^\/+/, "")}`;
};
var dynamicManifest = {
name: window.FileBrowser.Name || "File Browser",
short_name: window.FileBrowser.Name || "File Browser",
icons: [
{
src: window.__prependStaticUrl("/img/icons/android-chrome-192x192.png"),
sizes: "192x192",
type: "image/png",
},
{
src: window.__prependStaticUrl("/img/icons/android-chrome-512x512.png"),
sizes: "512x512",
type: "image/png",
},
],
start_url: window.location.origin + window.FileBrowser.BaseURL,
display: "standalone",
background_color: "#ffffff",
theme_color: window.FileBrowser.Color || "#455a64",
};
var fullStaticURL = window.location.origin + window.FileBrowser.StaticURL;
var dynamicManifest = {
"name": window.FileBrowser.Name || 'File Browser',
"short_name": window.FileBrowser.Name || 'File Browser',
"icons": [
{
"src": fullStaticURL + "/img/icons/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": fullStaticURL + "/img/icons/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
const stringManifest = JSON.stringify(dynamicManifest);
const blob = new Blob([stringManifest], { type: "application/json" });
const manifestURL = URL.createObjectURL(blob);
document
.querySelector("#manifestPlaceholder")
.setAttribute("href", manifestURL);
</script>
<style>
#loading {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: #fff;
z-index: 9999;
transition: 0.1s ease opacity;
-webkit-transition: 0.1s ease opacity;
}
#loading.done {
opacity: 0;
}
#loading .spinner {
width: 70px;
text-align: center;
position: fixed;
top: 50%;
left: 50%;
-webkit-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
}
#loading .spinner > div {
width: 18px;
height: 18px;
background-color: #333;
border-radius: 100%;
display: inline-block;
-webkit-animation: sk-bouncedelay 1.4s infinite ease-in-out both;
animation: sk-bouncedelay 1.4s infinite ease-in-out both;
}
#loading .spinner .bounce1 {
-webkit-animation-delay: -0.32s;
animation-delay: -0.32s;
}
#loading .spinner .bounce2 {
-webkit-animation-delay: -0.16s;
animation-delay: -0.16s;
}
@-webkit-keyframes sk-bouncedelay {
0%,
80%,
100% {
-webkit-transform: scale(0);
}
],
"start_url": window.location.origin + window.FileBrowser.BaseURL,
"display": "standalone",
"background_color": "#ffffff",
"theme_color": window.FileBrowser.Color || "#455a64"
}
40% {
-webkit-transform: scale(1);
}
}
const stringManifest = JSON.stringify(dynamicManifest);
const blob = new Blob([stringManifest], {type: 'application/json'});
const manifestURL = URL.createObjectURL(blob);
document.querySelector('#manifestPlaceholder').setAttribute('href', manifestURL);
</script>
@keyframes sk-bouncedelay {
0%,
80%,
100% {
-webkit-transform: scale(0);
transform: scale(0);
}
40% {
-webkit-transform: scale(1);
transform: scale(1);
}
}
</style>
</head>
<body>
<div id="app"></div>
<style>
#loading {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: #fff;
z-index: 9999;
transition: .1s ease opacity;
-webkit-transition: .1s ease opacity;
}
#loading.done {
opacity: 0;
}
#loading .spinner {
width: 70px;
text-align: center;
position: fixed;
top: 50%;
left: 50%;
-webkit-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
}
#loading .spinner > div {
width: 18px;
height: 18px;
background-color: #333;
border-radius: 100%;
display: inline-block;
-webkit-animation: sk-bouncedelay 1.4s infinite ease-in-out both;
animation: sk-bouncedelay 1.4s infinite ease-in-out both;
}
#loading .spinner .bounce1 {
-webkit-animation-delay: -0.32s;
animation-delay: -0.32s;
}
#loading .spinner .bounce2 {
-webkit-animation-delay: -0.16s;
animation-delay: -0.16s;
}
@-webkit-keyframes sk-bouncedelay {
0%, 80%, 100% { -webkit-transform: scale(0) }
40% { -webkit-transform: scale(1.0) }
}
@keyframes sk-bouncedelay {
0%, 80%, 100% {
-webkit-transform: scale(0);
transform: scale(0);
} 40% {
-webkit-transform: scale(1.0);
transform: scale(1.0);
}
}
</style>
</head>
<body>
<div id="app"></div>
<div id="loading">
<div class="spinner">
<div class="bounce1"></div>
<div class="bounce2"></div>
<div class="bounce3"></div>
<div id="loading">
<div class="spinner">
<div class="bounce1"></div>
<div class="bounce2"></div>
<div class="bounce3"></div>
</div>
</div>
</div>
[{[ if .Theme -]}]
<link rel="stylesheet" href="[{[ .StaticURL ]}]/themes/[{[ .Theme ]}].css" />
[{[ end ]}]
[{[ if .CSS -]}]
<script type="module" src="/src/main.js"></script>
[{[ if .Theme -]}]
<link
rel="stylesheet"
href="[{[ .StaticURL ]}]/themes/[{[ .Theme ]}].css"
/>
[{[ end ]}] [{[ if .CSS -]}]
<link rel="stylesheet" href="[{[ .StaticURL ]}]/custom.css" />
[{[ end ]}]
</body>
[{[ end ]}]
</body>
</html>

View File

@ -5,9 +5,6 @@
</template>
<script>
// eslint-disable-next-line no-undef
// __webpack_public_path__ = window.FileBrowser.StaticURL + "/";
export default {
name: "app",
mounted() {

View File

@ -1,13 +1,15 @@
import { removePrefix } from "./utils";
import { baseURL } from "@/utils/constants";
import store from "@/store";
import { useAuthStore } from "@/stores/auth";
const ssl = window.location.protocol === "https:";
const protocol = ssl ? "wss:" : "ws:";
export default function command(url, command, onmessage, onclose) {
const authStore = useAuthStore();
url = removePrefix(url);
url = `${protocol}//${window.location.host}${baseURL}/api/command${url}?auth=${store.state.jwt}`;
url = `${protocol}//${window.location.host}${baseURL}/api/command${url}?auth=${authStore.jwt}`;
let conn = new window.WebSocket(url);
conn.onopen = () => conn.send(command);

View File

@ -1,6 +1,6 @@
import { createURL, fetchURL, removePrefix } from "./utils";
import { baseURL } from "@/utils/constants";
import store from "@/store";
import { useAuthStore } from "@/stores/auth";
import { upload as postTus, useTus } from "./tus";
export async function fetch(url) {
@ -71,8 +71,9 @@ export function download(format, ...files) {
url += `algo=${format}&`;
}
if (store.state.jwt) {
url += `auth=${store.state.jwt}&`;
const authStore = useAuthStore();
if (authStore.jwt) {
url += `auth=${authStore.jwt}&`;
}
window.open(url);
@ -104,6 +105,7 @@ async function postResources(url, content = "", overwrite = false, onupload) {
bufferContent = await new Response(content).arrayBuffer();
}
const authStore = useAuthStore();
return new Promise((resolve, reject) => {
let request = new XMLHttpRequest();
request.open(
@ -111,7 +113,7 @@ async function postResources(url, content = "", overwrite = false, onupload) {
`${baseURL}/api/resources${url}?override=${overwrite}`,
true
);
request.setRequestHeader("X-Auth", store.state.jwt);
request.setRequestHeader("X-Auth", authStore.jwt);
if (typeof onupload === "function") {
request.upload.onprogress = onupload;

View File

@ -1,6 +1,6 @@
import * as tus from "tus-js-client";
import { baseURL, tusEndpoint, tusSettings } from "@/utils/constants";
import store from "@/store";
import { useAuthStore } from "@/stores/auth";
import { removePrefix } from "@/api/utils";
import { fetchURL } from "./utils";
@ -23,6 +23,7 @@ export async function upload(
await createUpload(resourcePath);
const authStore = useAuthStore();
return new Promise((resolve, reject) => {
let upload = new tus.Upload(content, {
uploadUrl: `${baseURL}${resourcePath}`,
@ -31,7 +32,7 @@ export async function upload(
parallelUploads: 1,
storeFingerprintForResuming: false,
headers: {
"X-Auth": store.state.jwt,
"X-Auth": authStore.jwt,
},
onError: function (error) {
reject("Upload failed: " + error);

View File

@ -1,9 +1,11 @@
import store from "@/store";
import { useAuthStore } from "@/stores/auth";
import { renew, logout } from "@/utils/auth";
import { baseURL } from "@/utils/constants";
import { encodePath } from "@/utils/url";
export async function fetchURL(url, opts, auth = true) {
const authStore = useAuthStore();
opts = opts || {};
opts.headers = opts.headers || {};
@ -12,7 +14,7 @@ export async function fetchURL(url, opts, auth = true) {
try {
res = await fetch(`${baseURL}${url}`, {
headers: {
"X-Auth": store.state.jwt,
"X-Auth": authStore.jwt,
...headers,
},
...rest,
@ -25,7 +27,7 @@ export async function fetchURL(url, opts, auth = true) {
}
if (auth && res.headers.get("X-Renew-Token") === "true") {
await renew(store.state.jwt);
await renew(authStore.jwt);
}
if (res.status < 200 || res.status > 299) {
@ -61,6 +63,8 @@ export function removePrefix(url) {
}
export function createURL(endpoint, params = {}, auth = true) {
const authStore = useAuthStore();
let prefix = baseURL;
if (!prefix.endsWith("/")) {
prefix = prefix + "/";
@ -68,7 +72,7 @@ export function createURL(endpoint, params = {}, auth = true) {
const url = new URL(prefix + encodePath(endpoint), origin);
const searchParams = {
...(auth && { auth: store.state.jwt }),
...(auth && { auth: authStore.jwt }),
...params,
};

View File

@ -49,7 +49,7 @@
</template>
<ul v-show="results.length > 0">
<li v-for="(s, k) in filteredResults" :key="k">
<router-link @click.native="close" :to="s.url">
<router-link v-on:click="close" :to="s.url">
<i v-if="s.dir" class="material-icons">folder</i>
<i v-else class="material-icons">insert_drive_file</i>
<span>./{{ s.path }}</span>
@ -65,7 +65,10 @@
</template>
<script>
import { mapState, mapGetters, mapMutations } from "vuex";
import { mapActions, mapState, mapWritableState } from "pinia";
import { useFileStore } from "@/stores/file";
import { useLayoutStore } from "@/stores/layout";
import url from "@/utils/url";
import { search } from "@/api";
@ -95,7 +98,7 @@ export default {
if (old === "search" && !this.active) {
if (this.reload) {
this.setReload(true);
this.sReload = true;
}
document.body.style.overflow = "auto";
@ -116,8 +119,9 @@ export default {
},
},
computed: {
...mapState(["user", "show"]),
...mapGetters(["isListing"]),
...mapState(useFileStore, ["isListing"]),
...mapState(useLayoutStore, ["show"]),
...mapWritableState(useFileStore, { sReload: "reload" }),
boxes() {
return boxes;
},
@ -148,7 +152,7 @@ export default {
});
},
methods: {
...mapMutations(["showHover", "closeHovers", "setReload"]),
...mapActions(useLayoutStore, ["showHover", "closeHovers"]),
open() {
this.showHover("search");
},
@ -199,3 +203,4 @@ export default {
},
};
</script>
@/stores/file@/stores/layout

View File

@ -20,9 +20,9 @@
tabindex="0"
ref="input"
class="shell__text"
contenteditable="true"
@keydown.prevent.38="historyUp"
@keydown.prevent.40="historyDown"
:contenteditable="true"
@keydown.prevent.arrow-up="historyUp"
@keydown.prevent.arrow-down="historyDown"
@keypress.prevent.enter="submit"
/>
</div>
@ -30,14 +30,17 @@
</template>
<script>
import { mapMutations, mapState, mapGetters } from "vuex";
import { mapState, mapActions } from "pinia";
import { useFileStore } from "@/stores/file";
import { useLayoutStore } from "@/stores/layout";
import { commands } from "@/api";
export default {
name: "shell",
computed: {
...mapState(["user", "showShell"]),
...mapGetters(["isFiles", "isLogged"]),
...mapState(useLayoutStore, ["showShell"]),
...mapState(useFileStore, ["isFiles"]),
path: function () {
if (this.isFiles) {
return this.$route.path;
@ -53,7 +56,7 @@ export default {
canInput: true,
}),
methods: {
...mapMutations(["toggleShell"]),
...mapActions(useLayoutStore, ["toggleShell"]),
scroll: function () {
this.$refs.scrollable.scrollTop = this.$refs.scrollable.scrollHeight;
},
@ -126,3 +129,4 @@ export default {
},
};
</script>
@/stores/file@/stores/layout

View File

@ -1,6 +1,6 @@
<template>
<nav :class="{ active }">
<template v-if="isLogged">
<template v-if="isLoggedIn">
<button
class="action"
@click="toRoot"
@ -13,7 +13,7 @@
<div v-if="user.perm.create">
<button
@click="$store.commit('showHover', 'newDir')"
@click="showHover('newDir')"
class="action"
:aria-label="$t('sidebar.newFolder')"
:title="$t('sidebar.newFolder')"
@ -23,7 +23,7 @@
</button>
<button
@click="$store.commit('showHover', 'newFile')"
@click="showHover('newFile')"
class="action"
:aria-label="$t('sidebar.newFile')"
:title="$t('sidebar.newFile')"
@ -83,7 +83,7 @@
<div
class="credits"
v-if="
$router.currentRoute.path.includes('/files/') && !disableUsedPercentage
$router.currentRoute.path?.includes('/files/') && !disableUsedPercentage
"
style="width: 90%; margin: 2em 2.5em 3em 2.5em"
>
@ -112,7 +112,10 @@
</template>
<script>
import { mapState, mapGetters } from "vuex";
import { mapActions, mapState } from "pinia";
import { useAuthStore } from "@/stores/auth";
import { useLayoutStore } from "@/stores/layout";
import * as auth from "@/utils/auth";
import {
version,
@ -132,10 +135,10 @@ export default {
ProgressBar,
},
computed: {
...mapState(["user"]),
...mapGetters(["isLogged"]),
...mapState(useAuthStore, ["user", "isLoggedIn"]),
...mapState(useLayoutStore, ["show"]),
active() {
return this.$store.state.show === "sidebar";
return this.show === "sidebar";
},
signup: () => signup,
version: () => version,
@ -172,18 +175,20 @@ export default {
},
},
methods: {
...mapActions(useLayoutStore, ["closeHovers", "showHover"]),
toRoot() {
this.$router.push({ path: "/files/" }, () => {});
this.$store.commit("closeHovers");
this.$router.push({ path: "/files/" });
this.closeHovers();
},
toSettings() {
this.$router.push({ path: "/settings" }, () => {});
this.$store.commit("closeHovers");
this.$router.push({ path: "/settings" });
this.closeHovers();
},
help() {
this.$store.commit("showHover", "help");
this.showHover("help");
},
logout: auth.logout,
},
};
</script>
@/stores/auth@/stores/layout

View File

@ -10,12 +10,7 @@
@mouseup="mouseUp"
@wheel="wheelMove"
>
<img
src=""
class="image-ex-img image-ex-img-center"
ref="imgex"
@load="onLoad"
/>
<img class="image-ex-img image-ex-img-center" ref="imgex" @load="onLoad" />
</div>
</template>
<script>
@ -73,7 +68,7 @@ export default {
window.addEventListener("resize", this.onResize);
},
beforeDestroy() {
beforeUnmount() {
window.removeEventListener("resize", this.onResize);
document.removeEventListener("mouseup", this.onMouseUp);
},

View File

@ -35,8 +35,12 @@
</template>
<script>
import { mapState, mapActions, mapWritableState } from "pinia";
import { useAuthStore } from "@/stores/auth";
import { useFileStore } from "@/stores/file";
import { useLayoutStore } from "@/stores/layout";
import { enableThumbs } from "@/utils/constants";
import { mapMutations, mapGetters, mapState } from "vuex";
import { filesize } from "filesize";
import moment from "moment";
import { files as api } from "@/api";
@ -44,6 +48,9 @@ import * as upload from "@/utils/upload";
export default {
name: "item",
compatConfig: {
ATTR_FALSE_VALUE: "suppress-warning",
},
data: function () {
return {
touches: 0,
@ -61,8 +68,9 @@ export default {
"path",
],
computed: {
...mapState(["user", "selected", "req", "jwt"]),
...mapGetters(["selectedCount"]),
...mapState(useAuthStore, ["user", "jwt"]),
...mapState(useFileStore, ["req", "selectedCount", "multiple"]),
...mapWritableState(useFileStore, ["reload", "selected"]),
singleClick() {
return this.readOnly == undefined && this.user.singleClick;
},
@ -96,7 +104,8 @@ export default {
},
},
methods: {
...mapMutations(["addSelected", "removeSelected", "resetSelected"]),
...mapActions(useFileStore, ["removeSelected"]),
...mapActions(useLayoutStore, ["showHover", "closeHovers"]),
humanSize: function () {
return this.type == "invalid_link" ? "invalid link" : filesize(this.size);
},
@ -108,13 +117,13 @@ export default {
},
dragStart: function () {
if (this.selectedCount === 0) {
this.addSelected(this.index);
this.selected.push(this.index);
return;
}
if (!this.isSelected) {
this.resetSelected();
this.addSelected(this.index);
this.selected = [];
this.selected.push(this.index);
}
},
dragOver: function (event) {
@ -162,7 +171,7 @@ export default {
api
.move(items, overwrite, rename)
.then(() => {
this.$store.commit("setReload", true);
this.reload = true;
})
.catch(this.$showError);
};
@ -173,14 +182,14 @@ export default {
let rename = false;
if (conflict) {
this.$store.commit("showHover", {
this.showHover({
prompt: "replace-rename",
confirm: (event, option) => {
overwrite = option == "overwrite";
rename = option == "rename";
event.preventDefault();
this.$store.commit("closeHovers");
this.closeHovers();
action(overwrite, rename);
},
});
@ -191,7 +200,7 @@ export default {
action(overwrite, rename);
},
itemClick: function (event) {
if (this.singleClick && !this.$store.state.multiple) this.open();
if (this.singleClick && !this.multiple) this.open();
else this.click(event);
},
click: function (event) {
@ -206,7 +215,7 @@ export default {
this.open();
}
if (this.$store.state.selected.indexOf(this.index) !== -1) {
if (this.selected.indexOf(this.index) !== -1) {
this.removeSelected(this.index);
return;
}
@ -224,8 +233,8 @@ export default {
}
for (; fi <= la; fi++) {
if (this.$store.state.selected.indexOf(fi) == -1) {
this.addSelected(fi);
if (this.selected.indexOf(fi) == -1) {
this.selected.push(fi);
}
}
@ -236,10 +245,11 @@ export default {
!this.singleClick &&
!event.ctrlKey &&
!event.metaKey &&
!this.$store.state.multiple
)
this.resetSelected();
this.addSelected(this.index);
!this.multiple
) {
this.selected = [];
}
this.selected.push(this.index);
},
open: function () {
this.$router.push({ path: this.url });
@ -247,3 +257,4 @@ export default {
},
};
</script>
@/stores/auth@/stores/file@/stores/layout

View File

@ -7,13 +7,17 @@
</template>
<script>
import { mapActions } from "pinia";
import { useLayoutStore } from "@/stores/layout";
export default {
name: "action",
props: ["icon", "label", "counter", "show"],
methods: {
...mapActions(useLayoutStore, ["showHover"]),
action: function () {
if (this.show) {
this.$store.commit("showHover", this.show);
this.showHover(this.show);
}
this.$emit("action");
@ -23,3 +27,4 @@ export default {
</script>
<style></style>
@/stores/layout

View File

@ -6,12 +6,12 @@
class="menu-button"
icon="menu"
:label="$t('buttons.toggleSidebar')"
@action="openSidebar()"
@action="showHover('sidebar')"
/>
<slot />
<div id="dropdown" :class="{ active: this.$store.state.show === 'more' }">
<div id="dropdown" :class="{ active: this.show === 'more' }">
<slot name="actions" />
</div>
@ -20,18 +20,17 @@
id="more"
icon="more_vert"
:label="$t('buttons.more')"
@action="$store.commit('showHover', 'more')"
@action="showHover('more')"
/>
<div
class="overlay"
v-show="this.$store.state.show == 'more'"
@click="$store.commit('closeHovers')"
/>
<div class="overlay" v-show="this.show == 'more'" @click="closeHovers" />
</header>
</template>
<script>
import { mapActions, mapState } from "pinia";
import { useLayoutStore } from "@/stores/layout";
import { logoURL } from "@/utils/constants";
import Action from "@/components/header/Action.vue";
@ -47,12 +46,14 @@ export default {
logoURL,
};
},
computed: {
...mapState(useLayoutStore, ["show"]),
},
methods: {
openSidebar() {
this.$store.commit("showHover", "sidebar");
},
...mapActions(useLayoutStore, ["showHover", "closeHovers"]),
},
};
</script>
<style></style>
@/stores/layout

View File

@ -12,7 +12,7 @@
<div class="card-action">
<button
class="button button--flat button--grey"
@click="$store.commit('closeHovers')"
@click="closeHovers"
:aria-label="$t('buttons.cancel')"
:title="$t('buttons.cancel')"
>
@ -31,11 +31,13 @@
</template>
<script>
import { mapState } from "vuex";
import { mapActions, mapState, mapWritableState } from "pinia";
import { useFileStore } from "@/stores/file";
import FileList from "./FileList.vue";
import { files as api } from "@/api";
import buttons from "@/utils/buttons";
import * as upload from "@/utils/upload";
import { useLayoutStore } from "@/stores/layout";
export default {
name: "copy",
@ -46,8 +48,12 @@ export default {
dest: null,
};
},
computed: mapState(["req", "selected"]),
computed: {
...mapState(useFileStore, ["req", "selected"]),
...mapWritableState(useFileStore, ["reload"]),
},
methods: {
...mapActions(useLayoutStore, ["showHover", "closeHovers"]),
copy: async function (event) {
event.preventDefault();
let items = [];
@ -70,7 +76,7 @@ export default {
buttons.success("copy");
if (this.$route.path === this.dest) {
this.$store.commit("setReload", true);
this.reload = true;
return;
}
@ -84,7 +90,7 @@ export default {
};
if (this.$route.path === this.dest) {
this.$store.commit("closeHovers");
this.closeHovers();
action(false, true);
return;
@ -97,14 +103,14 @@ export default {
let rename = false;
if (conflict) {
this.$store.commit("showHover", {
this.showHover({
prompt: "replace-rename",
confirm: (event, option) => {
overwrite = option == "overwrite";
rename = option == "rename";
event.preventDefault();
this.$store.commit("closeHovers");
this.closeHovers();
action(overwrite, rename);
},
});
@ -117,3 +123,4 @@ export default {
},
};
</script>
@/stores/file@/stores/layout

View File

@ -10,7 +10,7 @@
</div>
<div class="card-action">
<button
@click="$store.commit('closeHovers')"
@click="closeHovers"
class="button button--flat button--grey"
:aria-label="$t('buttons.cancel')"
:title="$t('buttons.cancel')"
@ -30,18 +30,26 @@
</template>
<script>
import { mapGetters, mapMutations, mapState } from "vuex";
import { mapActions, mapState, mapWritableState } from "pinia";
import { files as api } from "@/api";
import buttons from "@/utils/buttons";
import { useFileStore } from "@/stores/file";
import { useLayoutStore } from "@/stores/layout";
export default {
name: "delete",
computed: {
...mapGetters(["isListing", "selectedCount"]),
...mapState(["req", "selected", "showConfirm"]),
...mapState(useFileStore, [
"isListing",
"selectedCount",
"req",
"selected",
]),
...mapWritableState(useFileStore, ["reload"]),
...mapState(useLayoutStore, ["showConfirm"]),
},
methods: {
...mapMutations(["closeHovers"]),
...mapActions(useLayoutStore, ["closeHovers"]),
submit: async function () {
buttons.loading("delete");
@ -68,13 +76,14 @@ export default {
await Promise.all(promises);
buttons.success("delete");
this.$store.commit("setReload", true);
this.reload = true;
} catch (e) {
buttons.done("delete");
this.$showError(e);
if (this.isListing) this.$store.commit("setReload", true);
if (this.isListing) this.reload = true;
}
},
},
};
</script>
@/stores/file@/stores/layout

View File

@ -21,7 +21,8 @@
</template>
<script>
import { mapState } from "vuex";
import { mapState } from "pinia";
import { useLayoutStore } from "@/stores/layout";
export default {
name: "download",
@ -38,6 +39,7 @@ export default {
},
};
},
computed: mapState(["showConfirm"]),
computed: mapState(useLayoutStore, ["showConfirm"]),
};
</script>
@/stores/layout

View File

@ -25,7 +25,10 @@
</template>
<script>
import { mapState } from "vuex";
import { mapState } from "pinia";
import { useAuthStore } from "@/stores/auth";
import { useFileStore } from "@/stores/file";
import url from "@/utils/url";
import { files } from "@/api";
@ -43,7 +46,8 @@ export default {
};
},
computed: {
...mapState(["req", "user"]),
...mapState(useAuthStore, ["user"]),
...mapState(useFileStore, ["req"]),
nav() {
return decodeURIComponent(this.current);
},
@ -136,3 +140,4 @@ export default {
},
};
</script>
@/stores/auth@/stores/file

View File

@ -21,7 +21,7 @@
<div class="card-action">
<button
type="submit"
@click="$store.commit('closeHovers')"
@click="closeHovers"
class="button button--flat"
:aria-label="$t('buttons.ok')"
:title="$t('buttons.ok')"
@ -33,5 +33,14 @@
</template>
<script>
export default { name: "help" };
import { mapActions } from "pinia";
import { useLayoutStore } from "@/stores/layout";
export default {
name: "help",
methods: {
...mapActions(useLayoutStore, ["closeHovers"]),
},
};
</script>
@/stores/layout

View File

@ -68,7 +68,7 @@
<div class="card-action">
<button
type="submit"
@click="$store.commit('closeHovers')"
@click="closeHovers"
class="button button--flat"
:aria-label="$t('buttons.ok')"
:title="$t('buttons.ok')"
@ -80,7 +80,9 @@
</template>
<script>
import { mapState, mapGetters } from "vuex";
import { mapActions, mapState } from "pinia";
import { useFileStore } from "@/stores/file";
import { useLayoutStore } from "@/stores/layout";
import { filesize } from "filesize";
import moment from "moment";
import { files as api } from "@/api";
@ -88,8 +90,12 @@ import { files as api } from "@/api";
export default {
name: "info",
computed: {
...mapState(["req", "selected"]),
...mapGetters(["selectedCount", "isListing"]),
...mapState(useFileStore, [
"req",
"selected",
"selectedCount",
"isListing",
]),
humanSize: function () {
if (this.selectedCount === 0 || !this.isListing) {
return filesize(this.req.size);
@ -128,6 +134,7 @@ export default {
},
},
methods: {
...mapActions(useLayoutStore, ["closeHovers"]),
checksum: async function (event, algo) {
event.preventDefault();
@ -142,7 +149,7 @@ export default {
try {
const hash = await api.checksum(link, algo);
// eslint-disable-next-line
event.target.innerHTML = hash
event.target.innerHTML = hash;
} catch (e) {
this.$showError(e);
}
@ -150,3 +157,4 @@ export default {
},
};
</script>
@/stores/file@/stores/layout

View File

@ -11,7 +11,7 @@
<div class="card-action">
<button
class="button button--flat button--grey"
@click="$store.commit('closeHovers')"
@click="closeHovers"
:aria-label="$t('buttons.cancel')"
:title="$t('buttons.cancel')"
>
@ -31,11 +31,13 @@
</template>
<script>
import { mapState } from "vuex";
import { mapActions, mapState } from "pinia";
import { useFileStore } from "@/stores/file";
import FileList from "./FileList.vue";
import { files as api } from "@/api";
import buttons from "@/utils/buttons";
import * as upload from "@/utils/upload";
import { useLayoutStore } from "@/stores/layout";
export default {
name: "move",
@ -46,8 +48,9 @@ export default {
dest: null,
};
},
computed: mapState(["req", "selected"]),
computed: mapState(useFileStore, ["req", "selected"]),
methods: {
...mapActions(useLayoutStore, ["showHover", "closeHovers"]),
move: async function (event) {
event.preventDefault();
let items = [];
@ -82,14 +85,14 @@ export default {
let rename = false;
if (conflict) {
this.$store.commit("showHover", {
this.showHover({
prompt: "replace-rename",
confirm: (event, option) => {
overwrite = option == "overwrite";
rename = option == "rename";
event.preventDefault();
this.$store.commit("closeHovers");
this.closeHovers();
action(overwrite, rename);
},
});
@ -102,3 +105,4 @@ export default {
},
};
</script>
@/stores/file@/stores/layout

View File

@ -18,7 +18,7 @@
<div class="card-action">
<button
class="button button--flat button--grey"
@click="$store.commit('closeHovers')"
@click="closeHovers"
:aria-label="$t('buttons.cancel')"
:title="$t('buttons.cancel')"
>
@ -37,7 +37,10 @@
</template>
<script>
import { mapGetters } from "vuex";
import { mapActions, mapState } from "pinia";
import { useFileStore } from "@/stores/file";
import { useLayoutStore } from "@/stores/layout";
import { files as api } from "@/api";
import url from "@/utils/url";
@ -49,9 +52,10 @@ export default {
};
},
computed: {
...mapGetters(["isFiles", "isListing"]),
...mapState(useFileStore, ["isFiles", "isListing"]),
},
methods: {
...mapActions(useLayoutStore, ["closeHovers"]),
submit: async function (event) {
event.preventDefault();
if (this.new === "") return;
@ -73,8 +77,9 @@ export default {
this.$showError(e);
}
this.$store.commit("closeHovers");
this.closeHovers();
},
},
};
</script>
@/stores/file@/stores/layout

View File

@ -18,7 +18,7 @@
<div class="card-action">
<button
class="button button--flat button--grey"
@click="$store.commit('closeHovers')"
@click="closeHovers"
:aria-label="$t('buttons.cancel')"
:title="$t('buttons.cancel')"
>
@ -37,7 +37,10 @@
</template>
<script>
import { mapGetters } from "vuex";
import { mapActions, mapState } from "pinia";
import { useFileStore } from "@/stores/file";
import { useLayoutStore } from "@/stores/layout";
import { files as api } from "@/api";
import url from "@/utils/url";
@ -49,9 +52,10 @@ export default {
};
},
computed: {
...mapGetters(["isFiles", "isListing"]),
...mapState(useFileStore, ["isFiles", "isListing"]),
},
methods: {
...mapActions(useLayoutStore, ["closeHovers"]),
submit: async function (event) {
event.preventDefault();
if (this.new === "") return;
@ -73,8 +77,9 @@ export default {
this.$showError(e);
}
this.$store.commit("closeHovers");
this.closeHovers();
},
},
};
</script>
@/stores/file@/stores/layout

View File

@ -1,11 +1,14 @@
<template>
<div>
<component ref="currentComponent" :is="currentComponent"></component>
<div v-show="showOverlay" @click="resetPrompts" class="overlay"></div>
<div v-show="showOverlay" @click="closeHovers" class="overlay"></div>
</div>
</template>
<script>
import { mapActions, mapState } from "pinia";
import { useLayoutStore } from "@/stores/layout";
import Help from "./Help.vue";
import Info from "./Info.vue";
import Delete from "./Delete.vue";
@ -20,7 +23,6 @@ import ReplaceRename from "./ReplaceRename.vue";
import Share from "./Share.vue";
import Upload from "./Upload.vue";
import ShareDelete from "./ShareDelete.vue";
import { mapState } from "vuex";
import buttons from "@/utils/buttons";
export default {
@ -45,8 +47,6 @@ export default {
return {
pluginData: {
buttons,
store: this.$store,
router: this.$router,
},
};
},
@ -59,7 +59,7 @@ export default {
// Esc!
if (event.keyCode === 27) {
event.stopImmediatePropagation();
this.$store.commit("closeHovers");
this.closeHovers();
}
// Enter
@ -82,7 +82,7 @@ export default {
});
},
computed: {
...mapState(["show", "plugins"]),
...mapState(useLayoutStore, ["show", "showConfirm"]),
currentComponent: function () {
const matched =
[
@ -111,9 +111,8 @@ export default {
},
},
methods: {
resetPrompts() {
this.$store.commit("closeHovers");
},
...mapActions(useLayoutStore, ["closeHovers"]),
},
};
</script>
@/stores/layout

View File

@ -21,7 +21,7 @@
<div class="card-action">
<button
class="button button--flat button--grey"
@click="$store.commit('closeHovers')"
@click="closeHovers"
:aria-label="$t('buttons.cancel')"
:title="$t('buttons.cancel')"
>
@ -41,7 +41,9 @@
</template>
<script>
import { mapState, mapGetters } from "vuex";
import { mapActions, mapState, mapWritableState } from "pinia";
import { useFileStore } from "@/stores/file";
import { useLayoutStore } from "@/stores/layout";
import url from "@/utils/url";
import { files as api } from "@/api";
@ -56,12 +58,18 @@ export default {
this.name = this.oldName();
},
computed: {
...mapState(["req", "selected", "selectedCount"]),
...mapGetters(["isListing"]),
...mapState(useFileStore, [
"req",
"selected",
"selectedCount",
"isListing",
]),
...mapWritableState(useFileStore, ["reload"]),
},
methods: {
...mapActions(useLayoutStore, ["closeHovers"]),
cancel: function () {
this.$store.commit("closeHovers");
this.closeHovers();
},
oldName: function () {
if (!this.isListing) {
@ -95,13 +103,14 @@ export default {
return;
}
this.$store.commit("setReload", true);
this.reload = true;
} catch (e) {
this.$showError(e);
}
this.$store.commit("closeHovers");
this.closeHovers();
},
},
};
</script>
@/stores/file@/stores/layout

View File

@ -11,7 +11,7 @@
<div class="card-action">
<button
class="button button--flat button--grey"
@click="$store.commit('closeHovers')"
@click="closeHovers"
:aria-label="$t('buttons.cancel')"
:title="$t('buttons.cancel')"
>
@ -38,10 +38,17 @@
</template>
<script>
import { mapState } from "vuex";
import { mapActions, mapState } from "pinia";
import { useLayoutStore } from "@/stores/layout";
export default {
name: "replace",
computed: mapState(["showConfirm", "showAction"]),
computed: {
...mapState(useLayoutStore, ["showConfirm", "showAction"]),
},
methods: {
...mapActions(useLayoutStore, ["closeHovers"]),
},
};
</script>
@/stores/layout

View File

@ -11,7 +11,7 @@
<div class="card-action">
<button
class="button button--flat button--grey"
@click="$store.commit('closeHovers')"
@click="closeHovers"
:aria-label="$t('buttons.cancel')"
:title="$t('buttons.cancel')"
>
@ -38,10 +38,17 @@
</template>
<script>
import { mapState } from "vuex";
import { mapActions, mapState } from "pinia";
import { useLayoutStore } from "@/stores/layout";
export default {
name: "replace-rename",
computed: mapState(["showConfirm"]),
computed: {
...mapState(useLayoutStore, ["showConfirm"]),
},
methods: {
...mapActions(useLayoutStore, ["closeHovers"]),
},
};
</script>
@/stores/layout

View File

@ -59,7 +59,7 @@
<div class="card-action">
<button
class="button button--flat button--grey"
@click="$store.commit('closeHovers')"
@click="closeHovers"
:aria-label="$t('buttons.close')"
:title="$t('buttons.close')"
>
@ -126,10 +126,12 @@
</template>
<script>
import { mapState, mapGetters } from "vuex";
import { mapActions, mapState } from "pinia";
import { useFileStore } from "@/stores/file";
import { share as api, pub as pub_api } from "@/api";
import moment from "moment";
import Clipboard from "clipboard";
import { useLayoutStore } from "@/stores/layout";
export default {
name: "share",
@ -144,8 +146,12 @@ export default {
};
},
computed: {
...mapState(["req", "selected", "selectedCount"]),
...mapGetters(["isListing"]),
...mapState(useFileStore, [
"req",
"selected",
"selectedCount",
"isListing",
]),
url() {
if (!this.isListing) {
return this.$route.path;
@ -178,10 +184,11 @@ export default {
this.$showSuccess(this.$t("success.linkCopied"));
});
},
beforeDestroy() {
beforeUnmount() {
this.clip.destroy();
},
methods: {
...mapActions(useLayoutStore, ["closeHovers"]),
submit: async function () {
let isPermanent = !this.time || this.time == 0;
@ -242,7 +249,7 @@ export default {
},
switchListing() {
if (this.links.length == 0 && !this.listing) {
this.$store.commit("closeHovers");
this.closeHovers();
}
this.listing = !this.listing;
@ -250,3 +257,4 @@ export default {
},
};
</script>
@/stores/file@/stores/layout

View File

@ -5,7 +5,7 @@
</div>
<div class="card-action">
<button
@click="$store.commit('closeHovers')"
@click="closeHovers"
class="button button--flat button--grey"
:aria-label="$t('buttons.cancel')"
:title="$t('buttons.cancel')"
@ -25,17 +25,20 @@
</template>
<script>
import { mapState } from "vuex";
import { mapActions, mapState } from "pinia";
import { useLayoutStore } from "@/stores/layout";
export default {
name: "share-delete",
computed: {
...mapState(["showConfirm"]),
...mapState(useLayoutStore, ["showConfirm"]),
},
methods: {
...mapActions(useLayoutStore, ["closeHovers"]),
submit: function () {
this.showConfirm();
},
},
};
</script>
@/stores/layout

View File

@ -42,7 +42,8 @@
</template>
<script>
import { mapGetters } from "vuex";
import { mapState } from "pinia";
import { useUploadStore } from "@/stores/upload";
export default {
name: "uploadFiles",
@ -52,7 +53,7 @@ export default {
};
},
computed: {
...mapGetters(["filesInUpload", "filesInUploadCount"]),
...mapState(useUploadStore, ["filesInUpload", "filesInUploadCount"]),
},
methods: {
toggle: function () {
@ -61,3 +62,4 @@ export default {
},
};
</script>
@/stores/upload

View File

@ -42,7 +42,7 @@
<languages
class="input input--block"
id="locale"
:locale.sync="user.locale"
v-model:locale="user.locale"
></languages>
</p>
@ -55,13 +55,13 @@
{{ $t("settings.lockPassword") }}
</p>
<permissions :perm.sync="user.perm" />
<commands v-if="isExecEnabled" :commands.sync="user.commands" />
<permissions v-model:perm="user.perm" />
<commands v-if="isExecEnabled" v-model:commands="user.commands" />
<div v-if="!isDefault">
<h3>{{ $t("settings.rules") }}</h3>
<p class="small">{{ $t("settings.rulesHelp") }}</p>
<rules :rules.sync="user.rules" />
<rules v-model:rules="user.rules" />
</div>
</div>
</template>

View File

@ -103,7 +103,7 @@ const removeEmpty = (obj) =>
export const rtlLanguages = ["he", "ar"];
const i18n = createI18n({
export const i18n = createI18n({
locale: detectLocale(),
fallbackLocale: "en",
messages: {

View File

@ -1,51 +1,36 @@
import "whatwg-fetch";
import cssVars from "css-vars-ponyfill";
import { createApp, configureCompat } from "vue";
import store from "@/store";
import { createApp } from "vue";
import VueLazyload from "vue-lazyload";
import createPinia from "@/stores";
import router from "@/router";
import i18n from "@/i18n";
import { recaptcha, loginPage } from "@/utils/constants";
import { login, validateLogin } from "@/utils/auth";
import App from "@/App.vue";
cssVars();
configureCompat({
MODE: 2,
});
const pinia = createPinia(router);
const app = createApp(App);
app.use(store);
app.use(router);
app.use(VueLazyload);
app.use(i18n);
app.use(pinia);
app.use(router);
async function start() {
try {
if (loginPage) {
await validateLogin();
} else {
await login("", "", "");
}
} catch (e) {
console.log(e);
}
app.mixin({
mounted() {
// expose vue instance to components
this.$el.__vue__ = this;
},
});
if (recaptcha) {
await new Promise((resolve) => {
const check = () => {
if (typeof window.grecaptcha === "undefined") {
setTimeout(check, 100);
} else {
resolve();
}
};
// provide v-focus for components
app.directive("focus", {
mounted: (el) => {
// initiate focus for the element
el.focus();
},
});
check();
});
}
router.isReady().then(() => app.mount("#app"));
}
start();
router.isReady().then(() => app.mount("#app"));

View File

@ -10,9 +10,11 @@ import GlobalSettings from "@/views/settings/Global.vue";
import ProfileSettings from "@/views/settings/Profile.vue";
import Shares from "@/views/settings/Shares.vue";
import Errors from "@/views/Errors.vue";
import store from "@/store";
import { useAuthStore } from "@/stores/auth";
import { baseURL, name } from "@/utils/constants";
import i18n, { rtlLanguages } from "@/i18n";
import { i18n, rtlLanguages } from "@/i18n";
import { recaptcha, loginPage } from "@/utils/constants";
import { login, validateLogin } from "@/utils/auth";
const titles = {
Login: "sidebar.login",
@ -34,20 +36,13 @@ const routes = [
path: "/login",
name: "Login",
component: Login,
beforeEnter: (to, from, next) => {
if (store.getters.isLogged) {
return next({ path: "/files" });
}
next();
},
},
{
path: "/share",
component: Layout,
children: [
{
path: ":pathMatch(.*)*",
path: ":path*",
name: "Share",
component: Share,
},
@ -56,20 +51,23 @@ const routes = [
{
path: "/files",
component: Layout,
meta: {
requiresAuth: true,
},
children: [
{
path: ":pathMatch(.*)*",
path: ":path*",
name: "Files",
component: Files,
meta: {
requiresAuth: true,
},
},
],
},
{
path: "/settings",
component: Layout,
meta: {
requiresAuth: true,
},
children: [
{
path: "",
@ -78,9 +76,6 @@ const routes = [
redirect: {
path: "/settings/profile",
},
meta: {
requiresAuth: true,
},
children: [
{
path: "profile",
@ -159,21 +154,50 @@ const routes = [
},
];
async function start() {
try {
if (loginPage) {
await validateLogin();
} else {
await login("", "", "");
}
} catch (e) {
console.log(e);
}
if (recaptcha) {
await new Promise((resolve) => {
const check = () => {
if (typeof window.grecaptcha === "undefined") {
setTimeout(check, 100);
} else {
resolve();
}
};
check();
});
}
}
const router = createRouter({
history: createWebHistory(baseURL),
routes,
});
router.beforeEach((to, from, next) => {
// const title = i18n.t(titles[to.name]);
const title = titles[to.name];
router.beforeResolve(async (to, from, next) => {
let title;
try {
title = i18n.global.t(titles[to.name]);
} catch (error) {
title = to.name;
}
// const title = titles[to.name];
document.title = title + " - " + name;
console.log({ from, to });
/*** RTL related settings per route ****/
const rtlSet = document.querySelector("body").classList.contains("rtl");
const shouldSetRtl = rtlLanguages.includes(i18n.locale);
const shouldSetRtl = rtlLanguages.includes(i18n.global.locale);
switch (true) {
case shouldSetRtl && !rtlSet:
document.querySelector("body").classList.add("rtl");
@ -183,8 +207,19 @@ router.beforeEach((to, from, next) => {
break;
}
const authStore = useAuthStore();
if (from.name == null) {
await start();
}
if (to.path.endsWith("/login") && authStore.isLoggedIn) {
next({ path: "/files/" });
return;
}
if (to.matched.some((record) => record.meta.requiresAuth)) {
if (!store.getters.isLogged) {
if (!authStore.isLoggedIn) {
next({
path: "/login",
query: { redirect: to.fullPath },
@ -194,7 +229,7 @@ router.beforeEach((to, from, next) => {
}
if (to.matched.some((record) => record.meta.requiresAdmin)) {
if (!store.state.user.perm.admin) {
if (!authStore.user.perm.admin) {
next({ path: "/403" });
return;
}
@ -204,4 +239,4 @@ router.beforeEach((to, from, next) => {
next();
});
export default router;
export { router, router as default };

View File

@ -1,48 +0,0 @@
const getters = {
isLogged: (state) => state.user !== null,
isFiles: (state) => !state.loading && state.route.name === "Files",
isListing: (state, getters) => getters.isFiles && state.req.isDir,
selectedCount: (state) => state.selected.length,
progress: (state) => {
if (state.upload.progress.length == 0) {
return 0;
}
let totalSize = state.upload.sizes.reduce((a, b) => a + b, 0);
let sum = state.upload.progress.reduce((acc, val) => acc + val);
return Math.ceil((sum / totalSize) * 100);
},
filesInUploadCount: (state) => {
let total =
Object.keys(state.upload.uploads).length + state.upload.queue.length;
return total;
},
filesInUpload: (state) => {
let files = [];
for (let index in state.upload.uploads) {
let upload = state.upload.uploads[index];
let id = upload.id;
let type = upload.type;
let name = upload.file.name;
let size = state.upload.sizes[id];
let isDir = upload.file.isDir;
let progress = isDir
? 100
: Math.ceil((state.upload.progress[id] / size) * 100);
files.push({
id,
name,
progress,
type,
isDir,
});
}
return files.sort((a, b) => a.progress - b.progress);
},
};
export default getters;

View File

@ -1,38 +0,0 @@
import { createStore } from "vuex";
import mutations from "./mutations";
import getters from "./getters";
import upload from "./modules/upload";
import router from "@/router";
const state = {
user: null,
req: {},
oldReq: {},
clipboard: {
key: "",
items: [],
},
jwt: "",
progress: 0,
loading: false,
reload: false,
selected: [],
multiple: false,
show: null,
showShell: false,
showConfirm: null,
showAction: null,
get route() {
return router.currentRoute.value;
},
};
const store = createStore({
strict: true,
state,
getters,
mutations,
modules: { upload },
});
export default store;

View File

@ -1,110 +0,0 @@
import Vue from "vue";
import { files as api } from "@/api";
import throttle from "lodash.throttle";
import buttons from "@/utils/buttons";
const UPLOADS_LIMIT = 5;
const state = {
id: 0,
sizes: [],
progress: [],
queue: [],
uploads: {},
};
const mutations = {
setProgress(state, { id, loaded }) {
Vue.set(state.progress, id, loaded);
},
reset: (state) => {
state.id = 0;
state.sizes = [];
state.progress = [];
},
addJob: (state, item) => {
state.queue.push(item);
state.sizes[state.id] = item.file.size;
state.id++;
},
moveJob(state) {
const item = state.queue[0];
state.queue.shift();
Vue.set(state.uploads, item.id, item);
},
removeJob(state, id) {
Vue.delete(state.uploads, id);
delete state.uploads[id];
},
};
const beforeUnload = (event) => {
event.preventDefault();
event.returnValue = "";
};
const actions = {
upload: (context, item) => {
let uploadsCount = Object.keys(context.state.uploads).length;
let isQueueEmpty = context.state.queue.length == 0;
let isUploadsEmpty = uploadsCount == 0;
if (isQueueEmpty && isUploadsEmpty) {
window.addEventListener("beforeunload", beforeUnload);
buttons.loading("upload");
}
context.commit("addJob", item);
context.dispatch("processUploads");
},
finishUpload: (context, item) => {
context.commit("setProgress", { id: item.id, loaded: item.file.size });
context.commit("removeJob", item.id);
context.dispatch("processUploads");
},
processUploads: async (context) => {
let uploadsCount = Object.keys(context.state.uploads).length;
let isBellowLimit = uploadsCount < UPLOADS_LIMIT;
let isQueueEmpty = context.state.queue.length == 0;
let isUploadsEmpty = uploadsCount == 0;
let isFinished = isQueueEmpty && isUploadsEmpty;
let canProcess = isBellowLimit && !isQueueEmpty;
if (isFinished) {
window.removeEventListener("beforeunload", beforeUnload);
buttons.success("upload");
context.commit("reset");
context.commit("setReload", true, { root: true });
}
if (canProcess) {
const item = context.state.queue[0];
context.commit("moveJob");
if (item.file.isDir) {
await api.post(item.path).catch(Vue.prototype.$showError);
} else {
let onUpload = throttle(
(event) =>
context.commit("setProgress", {
id: item.id,
loaded: event.loaded,
}),
100,
{ leading: true, trailing: false }
);
await api
.post(item.path, item.file, item.overwrite, onUpload)
.catch(Vue.prototype.$showError);
}
context.dispatch("finishUpload", item);
}
},
};
export default { state, mutations, actions, namespaced: true };

View File

@ -1,91 +0,0 @@
import * as i18n from "@/i18n";
import moment from "moment";
const mutations = {
closeHovers: (state) => {
state.show = null;
state.showConfirm = null;
state.showAction = null;
},
toggleShell: (state) => {
state.showShell = !state.showShell;
},
showHover: (state, value) => {
if (typeof value !== "object") {
state.show = value;
return;
}
state.show = value.prompt;
state.showConfirm = value.confirm;
if (value.action !== undefined) {
state.showAction = value.action;
}
},
showError: (state) => {
state.show = "error";
},
showSuccess: (state) => {
state.show = "success";
},
setLoading: (state, value) => {
state.loading = value;
},
setReload: (state, value) => {
state.reload = value;
},
setUser: (state, value) => {
if (value === null) {
state.user = null;
return;
}
let locale = value.locale;
if (locale === "") {
locale = i18n.detectLocale();
}
moment.locale(locale);
i18n.default.locale = locale;
state.user = value;
},
setJWT: (state, value) => (state.jwt = value),
multiple: (state, value) => (state.multiple = value),
addSelected: (state, value) => state.selected.push(value),
removeSelected: (state, value) => {
let i = state.selected.indexOf(value);
if (i === -1) return;
state.selected.splice(i, 1);
},
resetSelected: (state) => {
state.selected = [];
},
updateUser: (state, value) => {
if (typeof value !== "object") return;
for (let field in value) {
if (field === "locale") {
moment.locale(value[field]);
i18n.default.locale = value[field];
}
state.user[field] = value[field];
}
},
updateRequest: (state, value) => {
state.oldReq = state.req;
state.req = value;
},
updateClipboard: (state, value) => {
state.clipboard.key = value.key;
state.clipboard.items = value.items;
state.clipboard.path = value.path;
},
resetClipboard: (state) => {
state.clipboard.key = "";
state.clipboard.items = [];
},
};
export default mutations;

View File

@ -0,0 +1,45 @@
import { defineStore } from "pinia";
// import { useAuthPreferencesStore } from "./auth-preferences";
// import { useAuthEmailStore } from "./auth-email";
export const useAuthStore = defineStore("auth", {
// convert to a function
state: () => ({
user: null,
jwt: "",
}),
getters: {
// user and jwt getter removed, no longer needed
isLoggedIn: (state) => state.user !== null,
},
actions: {
// no context as first argument, use `this` instead
setUser(value) {
if (value === null) {
this.user = null;
return;
}
// const locale = value.locale || i18n.detectLocale();
// moment.locale(locale);
// i18n.default.locale = locale;
this.user = value;
},
updateUser(value) {
if (typeof value !== "object") return;
for (let field in value) {
// if (field === "locale") {
// moment.locale(value[field]);
// i18n.default.locale = value[field];
// }
this.user[field] = structuredClone(value[field]);
}
},
// easily reset state using `$reset`
clearUser() {
this.$reset();
},
},
});

View File

@ -0,0 +1,25 @@
import { defineStore } from "pinia";
export const useClipboardStore = defineStore("clipboard", {
// convert to a function
state: () => ({
key: "",
items: [],
path: undefined,
}),
getters: {
// user and jwt getter removed, no longer needed
},
actions: {
// no context as first argument, use `this` instead
updateClipboard(value) {
this.key = value.key;
this.items = value.items;
this.path = value.path;
},
// easily reset state using `$reset`
resetClipboard() {
this.$reset();
},
},
});

View File

@ -0,0 +1,48 @@
import { defineStore } from "pinia";
import { useRouterStore } from "./router";
// import { useAuthPreferencesStore } from "./auth-preferences";
// import { useAuthEmailStore } from "./auth-email";
export const useFileStore = defineStore("file", {
// convert to a function
state: () => ({
req: {},
oldReq: {},
reload: false,
selected: [],
multiple: false,
}),
getters: {
// user and jwt getter removed, no longer needed
selectedCount: (state) => state.selected.length,
route: () => {
const routerStore = useRouterStore();
return routerStore.router.currentRoute;
},
isFiles(state) {
return !state.loading && this.route.name === "Files";
},
isListing(state) {
return this.isFiles && state.req.isDir;
},
},
actions: {
// no context as first argument, use `this` instead
toggleMultiple() {
this.multiple = !this.multiple;
},
updateRequest(value) {
this.oldReq = this.req;
this.req = value;
},
removeSelected(value) {
let i = this.selected.indexOf(value);
if (i === -1) return;
this.selected.splice(i, 1);
},
// easily reset state using `$reset`
clearFile() {
this.$reset();
},
},
});

View File

@ -0,0 +1,11 @@
import { createPinia as _createPinia } from "pinia";
import { markRaw } from "vue";
export default function createPinia(router) {
const pinia = _createPinia();
pinia.use(({ store }) => {
store.router = markRaw(router);
});
return pinia;
}

View File

@ -0,0 +1,50 @@
import { defineStore } from "pinia";
// import { useAuthPreferencesStore } from "./auth-preferences";
// import { useAuthEmailStore } from "./auth-email";
export const useLayoutStore = defineStore("layout", {
// convert to a function
state: () => ({
loading: false,
show: null,
showConfirm: null,
showAction: null,
showShell: false,
}),
getters: {
// user and jwt getter removed, no longer needed
},
actions: {
// no context as first argument, use `this` instead
toggleShell() {
this.showShell = !this.showShell;
},
showHover(value) {
if (typeof value !== "object") {
this.show = value;
return;
}
this.show = value.prompt;
this.showConfirm = value.confirm;
if (value.action !== undefined) {
this.showAction = value.action;
}
},
showError() {
this.show = "error";
},
showSuccess() {
this.show = "success";
},
closeHovers() {
this.show = null;
this.showConfirm = null;
this.showAction = null;
},
// easily reset state using `$reset`
clearLayout() {
this.$reset();
},
},
});

View File

@ -0,0 +1,6 @@
import { defineStore } from "pinia";
export const useRouterStore = defineStore("routerStore", () => {
// can be an empty definition
return {};
});

View File

@ -0,0 +1,157 @@
import Vue from "vue";
import { defineStore } from "pinia";
import { useFileStore } from "./file";
import { files as api } from "@/api";
import throttle from "lodash.throttle";
import buttons from "@/utils/buttons";
const UPLOADS_LIMIT = 5;
const beforeUnload = (event) => {
event.preventDefault();
event.returnValue = "";
};
export const useUploadStore = defineStore("upload", {
// convert to a function
state: () => ({
id: 0,
sizes: [],
progress: [],
queue: [],
uploads: {},
}),
getters: {
// user and jwt getter removed, no longer needed
getProgress: (state) => {
if (state.progress.length == 0) {
return 0;
}
const totalSize = state.sizes.reduce((a, b) => a + b, 0);
const sum = state.progress.reduce((acc, val) => acc + val);
return Math.ceil((sum / totalSize) * 100);
},
filesInUploadCount: (state) => {
const total = Object.keys(state.uploads).length + state.queue.length;
return total;
},
filesInUpload: (state) => {
const files = [];
for (let index in state.uploads) {
const upload = state.uploads[index];
const id = upload.id;
const type = upload.type;
const name = upload.file.name;
const size = state.sizes[id];
const isDir = upload.file.isDir;
const progress = isDir
? 100
: Math.ceil((state.progress[id] / size) * 100);
files.push({
id,
name,
progress,
type,
isDir,
});
}
return files.sort((a, b) => a.progress - b.progress);
},
},
actions: {
// no context as first argument, use `this` instead
setProgress({ id, loaded }) {
Vue.set(this.progress, id, loaded);
},
reset() {
this.id = 0;
this.sizes = [];
this.progress = [];
},
addJob(item) {
this.queue.push(item);
this.sizes[this.id] = item.file.size;
this.id++;
},
moveJob() {
const item = this.queue[0];
this.queue.shift();
Vue.set(this.uploads, item.id, item);
},
removeJob(id) {
Vue.delete(this.uploads, id);
delete this.uploads[id];
},
upload(item) {
let uploadsCount = Object.keys(this.uploads).length;
let isQueueEmpty = this.queue.length == 0;
let isUploadsEmpty = uploadsCount == 0;
if (isQueueEmpty && isUploadsEmpty) {
window.addEventListener("beforeunload", beforeUnload);
buttons.loading("upload");
}
this.addJob(item);
this.processUploads();
},
finishUpload(item) {
this.setProgress({ id: item.id, loaded: item.file.size });
this.removeJob(item.id);
this.processUploads();
},
async processUploads() {
const uploadsCount = Object.keys(this.uploads).length;
const isBellowLimit = uploadsCount < UPLOADS_LIMIT;
const isQueueEmpty = this.queue.length == 0;
const isUploadsEmpty = uploadsCount == 0;
const isFinished = isQueueEmpty && isUploadsEmpty;
const canProcess = isBellowLimit && !isQueueEmpty;
if (isFinished) {
const fileStore = useFileStore();
window.removeEventListener("beforeunload", beforeUnload);
buttons.success("upload");
this.reset();
fileStore.reload = true;
}
if (canProcess) {
const item = this.queue[0];
this.moveJob();
if (item.file.isDir) {
await api.post(item.path).catch(Vue.prototype.$showError);
} else {
let onUpload = throttle(
(event) =>
this.setProgress({
id: item.id,
loaded: event.loaded,
}),
100,
{ leading: true, trailing: false }
);
await api
.post(item.path, item.file, item.overwrite, onUpload)
.catch(Vue.prototype.$showError);
}
this.finishUpload(item);
}
},
// easily reset state using `$reset`
clearUpload() {
this.$reset();
},
},
});

View File

@ -1,22 +1,20 @@
import store from "@/store";
import { useAuthStore } from "@/stores/auth";
import router from "@/router";
import { Base64 } from "js-base64";
import jwt_decode from "jwt-decode";
import { baseURL } from "@/utils/constants";
export function parseToken(token) {
const parts = token.split(".");
// falsy or malformed jwt will throw InvalidTokenError
const data = jwt_decode(token);
console.log(data);
if (parts.length !== 3) {
throw new Error("token malformed");
}
const data = JSON.parse(Base64.decode(parts[1]));
document.cookie = `auth=${token}; path=/`;
document.cookie = `auth=${token}; Path=/; SameSite=Strict;`;
localStorage.setItem("jwt", token);
store.commit("setJWT", token);
store.commit("setUser", data.user);
const authStore = useAuthStore();
authStore.jwt = token;
authStore.setUser(data.user);
}
export async function validateLogin() {
@ -25,7 +23,7 @@ export async function validateLogin() {
await renew(localStorage.getItem("jwt"));
}
} catch (_) {
console.warn('Invalid JWT token in storage') // eslint-disable-line
console.warn("Invalid JWT token in storage"); // eslint-disable-line
}
}
@ -83,10 +81,11 @@ export async function signup(username, password) {
}
export function logout() {
document.cookie = "auth=; expires=Thu, 01 Jan 1970 00:00:01 GMT; path=/";
document.cookie = "auth=; Max-Age=0; Path=/; SameSite=Strict;";
const authStore = useAuthStore();
authStore.clearUser();
store.commit("setJWT", "");
store.commit("setUser", null);
localStorage.setItem("jwt", null);
router.push({ path: "/login" });
}

View File

@ -1,4 +1,4 @@
import store from "@/store";
import { useUploadStore } from "@/stores/upload";
import url from "@/utils/url";
export function checkConflict(files, items) {
@ -110,8 +110,10 @@ function detectType(mimetype) {
}
export function handleFiles(files, base, overwrite = false) {
const uploadStore = useUploadStore();
for (let i = 0; i < files.length; i++) {
let id = store.state.upload.id;
let id = uploadStore.id;
let path = base;
let file = files[i];
@ -133,6 +135,6 @@ export function handleFiles(files, base, overwrite = false) {
...(!file.isDir && { type: detectType(file.type) }),
};
store.dispatch("upload/upload", item);
uploadStore.upload(item);
}
}

View File

@ -7,18 +7,24 @@ export function removeLastDir(url) {
return arr.join("/");
}
// this code borrow from mozilla
// this function is taken from mozilla
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent#Examples
export function encodeRFC5987ValueChars(str) {
return (
encodeURIComponent(str)
// Note that although RFC3986 reserves "!", RFC5987 does not,
// so we do not need to escape it
.replace(/['()]/g, escape) // i.e., %27 %28 %29
.replace(/\*/g, "%2A")
// The following creates the sequences %27 %28 %29 %2A (Note that
// the valid encoding of "*" is %2A, which necessitates calling
// toUpperCase() to properly encode). Although RFC3986 reserves "!",
// RFC5987 does not, so we do not need to escape it.
.replace(
/['()*]/g,
(c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`
)
// The following are not required for percent-encoding per RFC5987,
// so we can allow for a little better readability over the wire: |`^
.replace(/%(?:7C|60|5E)/g, unescape)
.replace(/%(7C|60|5E)/g, (str, hex) =>
String.fromCharCode(parseInt(hex, 16))
)
);
}

View File

@ -21,7 +21,9 @@
<script>
import { files as api } from "@/api";
import { mapState, mapMutations } from "vuex";
import { mapState, mapActions, mapWritableState } from "pinia";
import { useFileStore } from "@/stores/file";
import { useLayoutStore } from "@/stores/layout";
import HeaderBar from "@/components/header/HeaderBar.vue";
import Breadcrumbs from "@/components/Breadcrumbs.vue";
@ -50,7 +52,14 @@ export default {
};
},
computed: {
...mapState(["req", "reload", "loading", "show"]),
...mapWritableState(useFileStore, [
"req",
"reload",
"selected",
"multiple",
]),
...mapState(useLayoutStore, ["show", "showShell"]),
...mapWritableState(useLayoutStore, ["loading"]),
currentView() {
if (this.req.type == undefined) {
return null;
@ -82,26 +91,27 @@ export default {
mounted() {
window.addEventListener("keydown", this.keyEvent);
},
beforeDestroy() {
beforeUnmount() {
window.removeEventListener("keydown", this.keyEvent);
},
destroyed() {
if (this.$store.state.showShell) {
this.$store.commit("toggleShell");
unmounted() {
if (this.showShell) {
this.toggleShell();
}
this.$store.commit("updateRequest", {});
this.updateRequest({});
},
methods: {
...mapMutations(["setLoading"]),
...mapActions(useLayoutStore, ["toggleShell", "showHover", "closeHovers"]),
...mapActions(useFileStore, ["updateRequest"]),
async fetchData() {
// Reset view information.
this.$store.commit("setReload", false);
this.$store.commit("resetSelected");
this.$store.commit("multiple", false);
this.$store.commit("closeHovers");
this.reload = false;
this.selected = [];
this.multiple = false;
this.closeHovers();
// Set loading to true and reset the error.
this.setLoading(true);
this.loading = true;
this.error = null;
let url = this.$route.path;
@ -111,25 +121,29 @@ export default {
try {
const res = await api.fetch(url);
if (clean(res.path) !== clean(`/${this.$route.params.pathMatch}`)) {
return;
if (
clean(res.path) !==
clean(`/${this.$route.params.path}`).replace(/,/g, "/")
) {
throw new Error("Data Mismatch!");
}
this.$store.commit("updateRequest", res);
this.updateRequest(res);
document.title = `${res.name} - ${document.title}`;
} catch (e) {
this.error = e;
} finally {
this.setLoading(false);
this.loading = false;
}
},
keyEvent(event) {
// F1!
if (event.keyCode === 112) {
event.preventDefault();
this.$store.commit("showHover", "help");
this.showHover("help");
}
},
},
};
</script>
@/stores/file@/stores/layout

View File

@ -6,7 +6,7 @@
<sidebar></sidebar>
<main>
<router-view></router-view>
<shell v-if="isExecEnabled && isLogged && user.perm.execute" />
<shell v-if="isExecEnabled && isLoggedIn && user.perm.execute" />
</main>
<prompts></prompts>
<upload-files></upload-files>
@ -14,11 +14,14 @@
</template>
<script>
import { mapState, mapGetters } from "vuex";
import { mapActions, mapState, mapWritableState } from "pinia";
import { useAuthStore } from "@/stores/auth";
import { useLayoutStore } from "@/stores/layout";
import { useFileStore } from "@/stores/file";
import Sidebar from "@/components/Sidebar.vue";
import Prompts from "@/components/prompts/Prompts.vue";
import Shell from "@/components/Shell.vue";
import UploadFiles from "../components/prompts/UploadFiles.vue";
import UploadFiles from "@/components/prompts/UploadFiles.vue";
import { enableExec } from "@/utils/constants";
export default {
@ -30,17 +33,23 @@ export default {
UploadFiles,
},
computed: {
...mapGetters(["isLogged", "progress"]),
...mapState(["user"]),
...mapState(useAuthStore, ["isLoggedIn", "user"]),
...mapState(useLayoutStore, ["progress", "show"]),
...mapWritableState(useFileStore, ["selected", "multiple"]),
isExecEnabled: () => enableExec,
},
methods: {
...mapActions(useLayoutStore, ["closeHovers"]),
},
watch: {
$route: function () {
this.$store.commit("resetSelected");
this.$store.commit("multiple", false);
if (this.$store.state.show !== "success")
this.$store.commit("closeHovers");
this.selected = [];
this.multiple = false;
if (this.show !== "success") {
this.closeHovers();
}
},
},
};
</script>
@/stores/auth@/stores/layout@/stores/file

View File

@ -117,6 +117,7 @@ export default {
await auth.login(this.username, this.password, captcha);
this.$router.push({ path: redirect });
} catch (e) {
console.error(e);
if (e.message == 409) {
this.error = this.$t("login.usernameTaken");
} else {

View File

@ -50,9 +50,10 @@
</template>
<script>
import { mapState } from "vuex";
import { mapState } from "pinia";
import { useAuthStore } from "@/stores/auth";
import HeaderBar from "@/components/header/HeaderBar.vue";
import { useLayoutStore } from "@/stores/layout";
export default {
name: "settings",
@ -60,7 +61,9 @@ export default {
HeaderBar,
},
computed: {
...mapState(["user", "loading"]),
...mapState(useAuthStore, ["user"]),
...mapState(useLayoutStore, ["loading"]),
},
};
</script>
@/stores/auth@/stores/file

View File

@ -147,13 +147,10 @@
</div>
</div>
<div
:class="{ active: $store.state.multiple }"
id="multiple-selection"
>
<div :class="{ active: multiple }" id="multiple-selection">
<p>{{ $t("files.multipleSelectionEnabled") }}</p>
<div
@click="$store.commit('multiple', false)"
@click="() => (multiple = false)"
tabindex="0"
role="button"
:title="$t('files.clear')"
@ -180,10 +177,11 @@
</template>
<script>
import { mapState, mapMutations, mapGetters } from "vuex";
import { mapState, mapActions, mapWritableState } from "pinia";
import { pub as api } from "@/api";
import { filesize } from "filesize";
import moment from "moment";
import { Base64 } from "js-base64";
import HeaderBar from "@/components/header/HeaderBar.vue";
import Action from "@/components/header/Action.vue";
@ -192,6 +190,8 @@ import Errors from "@/views/Errors.vue";
import QrcodeVue from "qrcode.vue";
import Item from "@/components/files/ListingItem.vue";
import Clipboard from "clipboard";
import { useFileStore } from "@/stores/file";
import { useLayoutStore } from "@/stores/layout";
export default {
name: "share",
@ -231,13 +231,14 @@ export default {
this.$showSuccess(this.$t("success.linkCopied"));
});
},
beforeDestroy() {
beforeUnmount() {
window.removeEventListener("keydown", this.keyEvent);
this.clip.destroy();
},
computed: {
...mapState(["req", "loading", "multiple", "selected"]),
...mapGetters(["selectedCount"]),
...mapState(useFileStore, ["req", "selectedCount"]),
...mapWritableState(useFileStore, ["reload", "multiple", "selected"]),
...mapWritableState(useLayoutStore, ["loading"]),
icon: function () {
if (this.req.isDir) return "folder";
if (this.req.type === "image") return "insert_photo";
@ -266,19 +267,20 @@ export default {
},
},
methods: {
...mapMutations(["resetSelected", "updateRequest", "setLoading"]),
...mapActions(useFileStore, ["updateRequest", "toggleMultiple"]),
...mapActions(useLayoutStore, ["showHover", "closeHovers"]),
base64: function (name) {
return window.btoa(unescape(encodeURIComponent(name)));
return Base64.encodeURI(name);
},
fetchData: async function () {
// Reset view information.
this.$store.commit("setReload", false);
this.$store.commit("resetSelected");
this.$store.commit("multiple", false);
this.$store.commit("closeHovers");
this.reload = false;
this.selected = [];
this.multiple = false;
this.closeHovers();
// Set loading to true and reset the error.
this.setLoading(true);
this.loading = true;
this.error = null;
if (this.password !== "") {
@ -300,7 +302,7 @@ export default {
} catch (e) {
this.error = e;
} finally {
this.setLoading(false);
this.loading = false;
}
},
keyEvent(event) {
@ -309,12 +311,12 @@ export default {
// If we're on a listing, unselect all
// files and folders.
if (this.selectedCount > 0) {
this.resetSelected();
this.selected = [];
}
}
},
toggleMultipleSelection() {
this.$store.commit("multiple", !this.multiple);
this.toggleMultiple();
},
isSingleFile: function () {
return (
@ -332,10 +334,10 @@ export default {
return;
}
this.$store.commit("showHover", {
this.showHover({
prompt: "download",
confirm: (format) => {
this.$store.commit("closeHovers");
this.closeHovers();
let files = [];
@ -358,3 +360,4 @@ export default {
},
};
</script>
@/stores/file@/stores/layout

View File

@ -20,18 +20,21 @@
</template>
<script>
import { mapState } from "vuex";
import { mapActions, mapState } from "pinia";
import { files as api } from "@/api";
import { theme } from "@/utils/constants";
import buttons from "@/utils/buttons";
import url from "@/utils/url";
import { version as ace_version } from "ace-builds";
import ace from "ace-builds/src-min-noconflict/ace.js";
import modelist from "ace-builds/src-min-noconflict/ext-modelist.js";
import HeaderBar from "@/components/header/HeaderBar.vue";
import Action from "@/components/header/Action.vue";
import Breadcrumbs from "@/components/Breadcrumbs.vue";
import { useAuthStore } from "@/stores/auth";
import { useFileStore } from "@/stores/file";
export default {
name: "editor",
@ -44,7 +47,8 @@ export default {
return {};
},
computed: {
...mapState(["req", "user"]),
...mapState(useAuthStore, ["user"]),
...mapState(useFileStore, ["req"]),
breadcrumbs() {
let parts = this.$route.path.split("/");
@ -78,13 +82,18 @@ export default {
created() {
window.addEventListener("keydown", this.keyEvent);
},
beforeDestroy() {
beforeUnmount() {
window.removeEventListener("keydown", this.keyEvent);
this.editor.destroy();
},
mounted: function () {
const fileContent = this.req.content || "";
ace.config.set(
"basePath",
`https://cdn.jsdelivr.net/npm/ace-builds@${ace_version}/src-min-noconflict/`
);
this.editor = ace.edit("editor", {
value: fileContent,
showPrintMargin: false,
@ -99,6 +108,7 @@ export default {
}
},
methods: {
...mapActions(useFileStore, ["updateRequest"]),
back() {
let uri = url.removeLastDir(this.$route.path) + "/";
this.$router.push({ path: uri });
@ -128,7 +138,7 @@ export default {
}
},
close() {
this.$store.commit("updateRequest", {});
this.updateRequest({});
let uri = url.removeLastDir(this.$route.path) + "/";
this.$router.push({ path: uri });
@ -136,3 +146,4 @@ export default {
},
};
</script>
@/stores/auth@/stores/file

View File

@ -51,7 +51,7 @@
v-if="headerButtons.shell"
icon="code"
:label="$t('buttons.shell')"
@action="$store.commit('toggleShell')"
@action="toggleShell"
/>
<action
:icon="viewIcon"
@ -248,10 +248,10 @@
multiple
/>
<div :class="{ active: $store.state.multiple }" id="multiple-selection">
<div :class="{ active: multiple }" id="multiple-selection">
<p>{{ $t("files.multipleSelectionEnabled") }}</p>
<div
@click="$store.commit('multiple', false)"
@click="() => (multiple = false)"
tabindex="0"
role="button"
:title="$t('files.clear')"
@ -268,12 +268,18 @@
<script>
import Vue from "vue";
import { mapState, mapGetters, mapMutations } from "vuex";
import { mapState, mapWritableState, mapActions, mapStores } from "pinia";
import { useAuthStore } from "@/stores/auth";
import { useClipboardStore } from "@/stores/clipboard";
import { useFileStore } from "@/stores/file";
import { useLayoutStore } from "@/stores/layout";
import { users, files as api } from "@/api";
import { enableExec } from "@/utils/constants";
import * as upload from "@/utils/upload";
import css from "@/utils/css";
import throttle from "lodash.throttle";
import { Base64 } from "js-base64";
import HeaderBar from "@/components/header/HeaderBar.vue";
import Action from "@/components/header/Action.vue";
@ -298,16 +304,17 @@ export default {
};
},
computed: {
...mapState([
...mapStores(useClipboardStore),
...mapState(useAuthStore, ["user"]),
...mapState(useFileStore, ["selectedCount", "toggleMultiple"]),
...mapState(useLayoutStore, ["show"]),
...mapWritableState(useFileStore, [
"req",
"selected",
"user",
"show",
"multiple",
"selected",
"loading",
"reload",
]),
...mapGetters(["selectedCount"]),
nameSorted() {
return this.req.sorting.by === "name";
},
@ -425,7 +432,7 @@ export default {
document.addEventListener("dragleave", this.dragLeave);
document.addEventListener("drop", this.drop);
},
beforeDestroy() {
beforeUnmount() {
// Remove event listeners before destroying this page.
window.removeEventListener("keydown", this.keyEvent);
window.removeEventListener("scroll", this.scrollEvent);
@ -438,9 +445,10 @@ export default {
document.removeEventListener("drop", this.drop);
},
methods: {
...mapMutations(["updateUser", "addSelected"]),
...mapActions(useAuthStore, ["updateUser"]),
...mapActions(useLayoutStore, ["showHover", "closeHovers", "toggleShell"]),
base64: function (name) {
return window.btoa(unescape(encodeURIComponent(name)));
return Base64.encodeURI(name);
},
keyEvent(event) {
// No prompts are shown
@ -451,7 +459,7 @@ export default {
// Esc!
if (event.keyCode === 27) {
// Reset files selection.
this.$store.commit("resetSelected");
this.selected = [];
}
// Del!
@ -459,7 +467,7 @@ export default {
if (!this.user.perm.delete || this.selectedCount == 0) return;
// Show delete prompt.
this.$store.commit("showHover", "delete");
this.showHover("delete");
}
// F2!
@ -467,7 +475,7 @@ export default {
if (!this.user.perm.rename || this.selectedCount !== 1) return;
// Show rename prompt.
this.$store.commit("showHover", "rename");
this.showHover("rename");
}
// Ctrl is pressed
@ -480,7 +488,7 @@ export default {
switch (key) {
case "f":
event.preventDefault();
this.$store.commit("showHover", "search");
this.showHover("search");
break;
case "c":
case "x":
@ -492,13 +500,13 @@ export default {
case "a":
event.preventDefault();
for (let file of this.items.files) {
if (this.$store.state.selected.indexOf(file.index) === -1) {
this.addSelected(file.index);
if (this.selected.indexOf(file.index) === -1) {
this.selected.push(file.index);
}
}
for (let dir of this.items.dirs) {
if (this.$store.state.selected.indexOf(dir.index) === -1) {
this.addSelected(dir.index);
if (this.selected.indexOf(dir.index) === -1) {
this.selected.push(dir.index);
}
}
break;
@ -530,7 +538,7 @@ export default {
return;
}
this.$store.commit("updateClipboard", {
this.clipboardStore.updateClipboard({
key: key,
items: items,
path: this.$route.path,
@ -543,7 +551,7 @@ export default {
let items = [];
for (let item of this.$store.state.clipboard.items) {
for (let item of this.clipboardStore.items) {
const from = item.from.endsWith("/")
? item.from.slice(0, -1)
: item.from;
@ -559,24 +567,24 @@ export default {
api
.copy(items, overwrite, rename)
.then(() => {
this.$store.commit("setReload", true);
this.reload = true;
})
.catch(this.$showError);
};
if (this.$store.state.clipboard.key === "x") {
if (this.clipboardStore.key === "x") {
action = (overwrite, rename) => {
api
.move(items, overwrite, rename)
.then(() => {
this.$store.commit("resetClipboard");
this.$store.commit("setReload", true);
this.clipboardStore.resetClipboard();
this.reload = true;
})
.catch(this.$showError);
};
}
if (this.$store.state.clipboard.path == this.$route.path) {
if (this.clipboardStore.path == this.$route.path) {
action(false, true);
return;
@ -588,14 +596,14 @@ export default {
let rename = false;
if (conflict) {
this.$store.commit("showHover", {
this.showHover({
prompt: "replace-rename",
confirm: (event, option) => {
overwrite = option == "overwrite";
rename = option == "rename";
event.preventDefault();
this.$store.commit("closeHovers");
this.closeHovers();
action(overwrite, rename);
},
});
@ -695,16 +703,16 @@ export default {
let conflict = upload.checkConflict(files, items);
if (conflict) {
this.$store.commit("showHover", {
this.showHover({
prompt: "replace",
action: (event) => {
event.preventDefault();
this.$store.commit("closeHovers");
this.closeHovers();
upload.handleFiles(files, path, false);
},
confirm: (event) => {
event.preventDefault();
this.$store.commit("closeHovers");
this.closeHovers();
upload.handleFiles(files, path, true);
},
});
@ -715,7 +723,7 @@ export default {
upload.handleFiles(files, path);
},
uploadInput(event) {
this.$store.commit("closeHovers");
this.closeHovers();
let files = event.currentTarget.files;
let folder_upload =
@ -735,16 +743,16 @@ export default {
let conflict = upload.checkConflict(files, this.req.items);
if (conflict) {
this.$store.commit("showHover", {
this.showHover({
prompt: "replace",
action: (event) => {
event.preventDefault();
this.$store.commit("closeHovers");
this.closeHovers();
upload.handleFiles(files, path, false);
},
confirm: (event) => {
event.preventDefault();
this.$store.commit("closeHovers");
this.closeHovers();
upload.handleFiles(files, path, true);
},
});
@ -786,14 +794,14 @@ export default {
this.$showError(e);
}
this.$store.commit("setReload", true);
this.reload = true;
},
openSearch() {
this.$store.commit("showHover", "search");
this.showHover("search");
},
toggleMultipleSelection() {
this.$store.commit("multiple", !this.multiple);
this.$store.commit("closeHovers");
this.toggleMultiple();
this.closeHovers();
},
windowsResize: throttle(function () {
this.colunmsResize();
@ -814,10 +822,10 @@ export default {
return;
}
this.$store.commit("showHover", {
this.showHover({
prompt: "download",
confirm: (format) => {
this.$store.commit("closeHovers");
this.closeHovers();
let files = [];
@ -834,7 +842,7 @@ export default {
});
},
switchView: async function () {
this.$store.commit("closeHovers");
this.closeHovers();
const modes = {
list: "mosaic",
@ -850,7 +858,7 @@ export default {
users.update(data, ["viewMode"]).catch(this.$showError);
// Await ensures correct value for setItemWeight()
await this.$store.commit("updateUser", data);
await this.updateUser(data);
this.setItemWeight();
this.fillWindow();
@ -860,7 +868,7 @@ export default {
typeof window.DataTransferItem !== "undefined" &&
typeof DataTransferItem.prototype.webkitGetAsEntry !== "undefined"
) {
this.$store.commit("showHover", "upload");
this.showHover("upload");
} else {
document.getElementById("upload-input").click();
}
@ -897,3 +905,4 @@ export default {
},
};
</script>
@/stores/auth@/stores/clipboard@/stores/file@/stores/layout

View File

@ -143,7 +143,11 @@
</template>
<script>
import { mapState } from "vuex";
import { mapActions, mapState } from "pinia";
import { useAuthStore } from "@/stores/auth";
import { useFileStore } from "@/stores/file";
import { useLayoutStore } from "@/stores/layout";
import { files as api } from "@/api";
import { resizePreview } from "@/utils/constants";
import url from "@/utils/url";
@ -177,7 +181,9 @@ export default {
};
},
computed: {
...mapState(["req", "user", "oldReq", "jwt", "loading", "show"]),
...mapState(useAuthStore, ["user", "jwt"]),
...mapState(useFileStore, ["req", "oldReq", "loading"]),
...mapState(useLayoutStore, ["show"]),
hasPrevious() {
return this.previousLink !== "";
},
@ -195,7 +201,7 @@ export default {
return api.getDownloadURL(this.req, true);
},
showMore() {
return this.$store.state.show === "more";
return this.show === "more";
},
isResizeEnabled() {
return resizePreview;
@ -218,12 +224,14 @@ export default {
this.listing = this.oldReq.items;
this.updatePreview();
},
beforeDestroy() {
beforeUnmount() {
window.removeEventListener("keydown", this.key);
},
methods: {
...mapActions(useFileStore, ["updateRequest"]),
...mapActions(useLayoutStore, ["showHover", "closeHovers"]),
deleteFile() {
this.$store.commit("showHover", {
this.showHover({
prompt: "delete",
confirm: () => {
this.listing = this.listing.filter((item) => item.name !== this.name);
@ -320,10 +328,10 @@ export default {
: api.getPreviewURL(item, "big");
},
openMore() {
this.$store.commit("showHover", "more");
this.showHover("more");
},
resetPrompts() {
this.$store.commit("closeHovers");
this.closeHovers();
},
toggleSize() {
this.fullSize = !this.fullSize;
@ -341,7 +349,7 @@ export default {
}, 1500);
}, 500),
close() {
this.$store.commit("updateRequest", {});
this.updateRequest({});
let uri = url.removeLastDir(this.$route.path) + "/";
this.$router.push({ path: uri });
@ -352,3 +360,4 @@ export default {
},
};
</script>
@/stores/auth@/stores/file@/stores/layout

View File

@ -29,7 +29,7 @@
<h3>{{ $t("settings.rules") }}</h3>
<p class="small">{{ $t("settings.globalRules") }}</p>
<rules :rules.sync="settings.rules" />
<rules v-model:rules="settings.rules" />
<div v-if="isExecEnabled">
<h3>{{ $t("settings.executeOnShell") }}</h3>
@ -75,7 +75,7 @@
<label for="theme">{{ $t("settings.themes.title") }}</label>
<themes
class="input input--block"
:theme.sync="settings.branding.theme"
v-model:theme="settings.branding.theme"
id="theme"
></themes>
</p>
@ -156,7 +156,7 @@
<user-form
:isNew="false"
:isDefault="true"
:user.sync="settings.defaults"
v-model:user="settings.defaults"
/>
</div>
@ -220,7 +220,9 @@
</template>
<script>
import { mapState, mapMutations } from "vuex";
import { mapState, mapWritableState } from "pinia";
import { useAuthStore } from "@/stores/auth";
import { useLayoutStore } from "@/stores/layout";
import { settings as api } from "@/api";
import { enableExec } from "@/utils/constants";
import UserForm from "@/components/settings/UserForm.vue";
@ -245,7 +247,8 @@ export default {
};
},
computed: {
...mapState(["user", "loading"]),
...mapState(useAuthStore, ["user"]),
...mapWritableState(useLayoutStore, ["loading"]),
isExecEnabled: () => enableExec,
formattedChunkSize: {
get() {
@ -268,7 +271,7 @@ export default {
},
async created() {
try {
this.setLoading(true);
this.loading = true;
const original = await api.get();
let settings = { ...original, commands: [] };
@ -287,11 +290,10 @@ export default {
} catch (e) {
this.error = e;
} finally {
this.setLoading(false);
this.loading = false;
}
},
methods: {
...mapMutations(["setLoading"]),
capitalize(name, where = "_") {
if (where === "caps") where = /(?=[A-Z])/;
let splitted = name.split(where);
@ -358,7 +360,7 @@ export default {
return `${size}${units[unitIndex]}`;
},
// Clear the debounce timeout when the component is destroyed
beforeDestroy() {
beforeUnmount() {
if (this.debounceTimeout) {
clearTimeout(this.debounceTimeout);
}
@ -366,3 +368,4 @@ export default {
},
};
</script>
@/stores/auth@/stores/file

View File

@ -22,7 +22,7 @@
<h3>{{ $t("settings.language") }}</h3>
<languages
class="input input--block"
:locale.sync="locale"
v-model:locale="locale"
></languages>
</div>
@ -72,7 +72,9 @@
</template>
<script>
import { mapState, mapMutations } from "vuex";
import { mapActions, mapState, mapWritableState } from "pinia";
import { useAuthStore } from "@/stores/auth";
import { useLayoutStore } from "@/stores/layout";
import { users as api } from "@/api";
import Languages from "@/components/settings/Languages.vue";
import i18n, { rtlLanguages } from "@/i18n";
@ -93,7 +95,8 @@ export default {
};
},
computed: {
...mapState(["user"]),
...mapState(useAuthStore, ["user"]),
...mapWritableState(useLayoutStore, ["loading"]),
passwordClass() {
const baseClass = "input input--block";
@ -109,14 +112,14 @@ export default {
},
},
created() {
this.setLoading(false);
this.loading = true;
this.locale = this.user.locale;
this.hideDotfiles = this.user.hideDotfiles;
this.singleClick = this.user.singleClick;
this.dateFormat = this.user.dateFormat;
},
methods: {
...mapMutations(["updateUser", "setLoading"]),
...mapActions(useAuthStore, ["updateUser"]),
async updatePassword(event) {
event.preventDefault();
@ -165,3 +168,4 @@ export default {
},
};
</script>
@/stores/auth@/stores/file

View File

@ -61,8 +61,10 @@
</template>
<script>
import { mapState, mapWritableState } from "pinia";
import { useAuthStore } from "@/stores/auth";
import { useLayoutStore } from "@/stores/layout";
import { share as api, users } from "@/api";
import { mapState, mapMutations } from "vuex";
import moment from "moment";
import Clipboard from "clipboard";
import Errors from "@/views/Errors.vue";
@ -72,7 +74,10 @@ export default {
components: {
Errors,
},
computed: mapState(["user", "loading"]),
computed: {
...mapState(useAuthStore, ["user"]),
...mapWritableState(useLayoutStore, ["loading"]),
},
data: function () {
return {
error: null,
@ -81,7 +86,7 @@ export default {
};
},
async created() {
this.setLoading(true);
this.loading = true;
try {
let links = await api.list();
@ -98,7 +103,7 @@ export default {
} catch (e) {
this.error = e;
} finally {
this.setLoading(false);
this.loading = false;
}
},
mounted() {
@ -107,18 +112,17 @@ export default {
this.$showSuccess(this.$t("success.linkCopied"));
});
},
beforeDestroy() {
beforeUnmount() {
this.clip.destroy();
},
methods: {
...mapMutations(["setLoading"]),
deleteLink: async function (event, link) {
event.preventDefault();
this.$store.commit("showHover", {
this.showHover({
prompt: "share-delete",
confirm: () => {
this.$store.commit("closeHovers");
this.closeHovers();
try {
api.remove(link.hash);
@ -139,3 +143,4 @@ export default {
},
};
</script>
@/stores/auth@/stores/file

View File

@ -10,8 +10,8 @@
<div class="card-content">
<user-form
:user.sync="user"
:createUserDir.sync="createUserDir"
v-model:user="user"
v-model:createUserDir="createUserDir"
:isDefault="false"
:isNew="isNew"
/>
@ -37,7 +37,7 @@
</form>
</div>
<div v-if="$store.state.show === 'deleteUser'" class="card floating">
<div v-if="show === 'deleteUser'" class="card floating">
<div class="card-content">
<p>Are you sure you want to delete this user?</p>
</div>
@ -61,7 +61,9 @@
</template>
<script>
import { mapState, mapMutations } from "vuex";
import { mapActions, mapState, mapWritableState } from "pinia";
import { useAuthStore } from "@/stores/auth";
import { useLayoutStore } from "@/stores/layout";
import { users as api, settings } from "@/api";
import UserForm from "@/components/settings/UserForm.vue";
import Errors from "@/views/Errors.vue";
@ -85,10 +87,12 @@ export default {
this.fetchData();
},
computed: {
...mapState(useAuthStore, ["user"]),
...mapState(useLayoutStore, ["show"]),
...mapWritableState(useLayoutStore, ["loading"]),
isNew() {
return this.$route.path === "/settings/users/new";
},
...mapState(["loading"]),
},
watch: {
$route: "fetchData",
@ -98,9 +102,10 @@ export default {
},
},
methods: {
...mapMutations(["closeHovers", "showHover", "setUser", "setLoading"]),
...mapActions(useAuthStore, ["setUser"]),
...mapActions(useLayoutStore, ["closeHovers", "showHover"]),
async fetchData() {
this.setLoading(true);
this.loading = true;
try {
if (this.isNew) {
@ -121,7 +126,7 @@ export default {
} catch (e) {
this.error = e;
} finally {
this.setLoading(false);
this.loading = false;
}
},
deletePrompt() {
@ -155,7 +160,7 @@ export default {
} else {
await api.update(user);
if (user.id === this.$store.state.user.id) {
if (user.id === this.user.id) {
this.setUser({ ...deepClone(user) });
}
@ -168,3 +173,4 @@ export default {
},
};
</script>
@/stores/auth@/stores/file@/stores/layout

View File

@ -42,7 +42,8 @@
</template>
<script>
import { mapState, mapMutations } from "vuex";
import { mapWritableState } from "pinia";
import { useLayoutStore } from "@/stores/layout";
import { users as api } from "@/api";
import Errors from "@/views/Errors.vue";
@ -58,21 +59,19 @@ export default {
};
},
async created() {
this.setLoading(true);
this.loading = true;
try {
this.users = await api.getAll();
} catch (e) {
this.error = e;
} finally {
this.setLoading(false);
this.loading = false;
}
},
computed: {
...mapState(["loading"]),
},
methods: {
...mapMutations(["setLoading"]),
...mapWritableState(useLayoutStore, ["loading"]),
},
};
</script>
@/stores/file

View File

@ -4,6 +4,7 @@ import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import VueI18nPlugin from "@intlify/unplugin-vue-i18n/vite";
import { compression } from "vite-plugin-compression2";
import pluginRewriteAll from "vite-plugin-rewrite-all";
// https://vitejs.dev/config/
export default defineConfig({
@ -19,6 +20,7 @@ export default defineConfig({
}),
VueI18nPlugin(),
compression({ include: /\.js$/i, deleteOriginalAssets: true }),
pluginRewriteAll(), // Fixes 404 error with paths containing dot (will be fixed in Vite 5)
],
resolve: {
alias: {
@ -26,6 +28,15 @@ export default defineConfig({
"@/": fileURLToPath(new URL("./src/", import.meta.url)),
},
},
server: {
proxy: {
"/api/command": {
target: "ws://127.0.0.1:8080",
ws: true,
},
"/api": "http://127.0.0.1:8080",
},
},
base: "",
experimental: {
renderBuiltUrl(filename, { hostType }) {