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 "node": true
}, },
"extends": [ "extends": [
"plugin:vue/essential", "plugin:vue/vue3-essential",
"eslint:recommended", "eslint:recommended",
"@vue/eslint-config-prettier" "@vue/eslint-config-prettier"
], ],

View File

@ -8,25 +8,19 @@
content="width=device-width, initial-scale=1, user-scalable=no" content="width=device-width, initial-scale=1, user-scalable=no"
/> />
[{[ if .ReCaptcha -]}] <title>File Browser</title>
<script src="[{[ .ReCaptchaHost ]}]/recaptcha/api.js?render=explicit"></script>
[{[ end ]}]
<title>
[{[ if .Name -]}][{[ .Name ]}][{[ else ]}]File Browser[{[ end ]}]
</title>
<link <link
rel="icon" rel="icon"
type="image/png" type="image/png"
sizes="32x32" sizes="32x32"
href="[{[ .StaticURL ]}]/img/icons/favicon-32x32.png" href="/img/icons/favicon-32x32.png"
/> />
<link <link
rel="icon" rel="icon"
type="image/png" type="image/png"
sizes="16x16" 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 --> <!-- Add to home screen for Android and modern mobile browsers -->
@ -35,49 +29,63 @@
id="manifestPlaceholder" id="manifestPlaceholder"
crossorigin="use-credentials" crossorigin="use-credentials"
/> />
<meta <meta name="theme-color" content="#2979ff" />
name="theme-color"
content="[{[ if .Color -]}][{[ .Color ]}][{[ else ]}]#2979ff[{[ end ]}]"
/>
<!-- Add to home screen for Safari on iOS/iPadOS --> <!-- Add to home screen for Safari on iOS/iPadOS -->
<meta name="apple-mobile-web-app-capable" content="yes" /> <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-status-bar-style" content="black" />
<meta name="apple-mobile-web-app-title" content="assets" /> <meta name="apple-mobile-web-app-title" content="assets" />
<link <link rel="apple-touch-icon" href="/img/icons/apple-touch-icon.png" />
rel="apple-touch-icon"
href="[{[ .StaticURL ]}]/img/icons/apple-touch-icon.png"
/>
<!-- Add to home screen for Windows --> <!-- Add to home screen for Windows -->
<meta <meta
name="msapplication-TileImage" name="msapplication-TileImage"
content="[{[ .StaticURL ]}]/img/icons/mstile-144x144.png" content="/img/icons/mstile-144x144.png"
/>
<meta
name="msapplication-TileColor"
content="[{[ if .Color -]}][{[ .Color ]}][{[ else ]}]#2979ff[{[ end ]}]"
/> />
<meta name="msapplication-TileColor" content="#2979ff" />
<!-- Inject Some Variables and generate the manifest json --> <!-- Inject Some Variables and generate the manifest json -->
<script> <script>
<!-- Json is actually a JS object, assign it directly --> // We can assign JSON directly
window.FileBrowser = [{[ .Json ]}]; window.FileBrowser = {
<!-- Global function to prepend static url --> 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) => { window.__prependStaticUrl = (url) => {
return `${window.FileBrowser.StaticURL}/${url.replace(/^\/+/, '')}`; return `${window.FileBrowser.StaticURL}/${url.replace(/^\/+/, "")}`;
}; };
var dynamicManifest = { var dynamicManifest = {
name: window.FileBrowser.Name || "File Browser", name: window.FileBrowser.Name || "File Browser",
short_name: window.FileBrowser.Name || "File Browser", short_name: window.FileBrowser.Name || "File Browser",
icons: [ icons: [
{ {
src: window.__prependStaticUrl("/img/icons/android-chrome-192x192.png"), src: window.__prependStaticUrl(
"/img/icons/android-chrome-192x192.png"
),
sizes: "192x192", sizes: "192x192",
type: "image/png", type: "image/png",
}, },
{ {
src: window.__prependStaticUrl("/img/icons/android-chrome-512x512.png"), src: window.__prependStaticUrl(
"/img/icons/android-chrome-512x512.png"
),
sizes: "512x512", sizes: "512x512",
type: "image/png", type: "image/png",
}, },
@ -180,14 +188,5 @@
</div> </div>
<script type="module" src="/src/main.js"></script> <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> </body>
</html> </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": { "dependencies": {
"@vue/compat": "^3.3.4", "@vue/compat": "^3.3.4",
"ace-builds": "^1.23.4", "@vueuse/core": "^10.4.1",
"ace-builds": "^1.24.1",
"clipboard": "^2.0.11", "clipboard": "^2.0.11",
"core-js": "^3.32.0", "core-js": "^3.32.1",
"css-vars-ponyfill": "^2.4.8", "css-vars-ponyfill": "^2.4.8",
"filesize": "^10.0.8", "filesize": "^10.0.12",
"js-base64": "^3.7.5", "js-base64": "^3.7.5",
"jwt-decode": "^3.1.2",
"lodash.clonedeep": "^4.5.0", "lodash.clonedeep": "^4.5.0",
"lodash.throttle": "^4.1.1", "lodash.throttle": "^4.1.1",
"material-icons": "^1.13.9", "material-icons": "^1.13.10",
"moment": "^2.29.4", "moment": "^2.29.4",
"normalize.css": "^8.0.1", "normalize.css": "^8.0.1",
"noty": "^3.2.0-beta", "noty": "^3.2.0-beta",
"pinia": "^2.1.6",
"pretty-bytes": "^6.1.1", "pretty-bytes": "^6.1.1",
"qrcode.vue": "^3.4.1", "qrcode.vue": "^3.4.1",
"tus-js-client": "^3.1.1", "tus-js-client": "^3.1.1",
"utif": "^3.1.0", "utif": "^3.1.0",
"vue": "^3.3.4", "vue": "^3.3.4",
"vue-async-computed": "^3.9.0",
"vue-i18n": "^9.2.2", "vue-i18n": "^9.2.2",
"vue-lazyload": "^1.3.5", "vue-lazyload": "^3.0.0",
"vue-router": "^4.2.4", "vue-router": "^4.2.4",
"vue-simple-progress": "^1.1.1", "vue-simple-progress": "^1.1.1",
"vuex": "^4.1.0",
"whatwg-fetch": "^3.6.17" "whatwg-fetch": "^3.6.17"
}, },
"devDependencies": { "devDependencies": {
"@intlify/unplugin-vue-i18n": "^0.12.2", "@intlify/unplugin-vue-i18n": "^0.12.3",
"@vitejs/plugin-legacy": "^4.1.1", "@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", "@vue/eslint-config-prettier": "^8.0.0",
"autoprefixer": "^10.4.14", "autoprefixer": "^10.4.15",
"eslint": "^8.46.0", "eslint": "^8.48.0",
"eslint-plugin-prettier": "^5.0.0", "eslint-plugin-prettier": "^5.0.0",
"eslint-plugin-vue": "^9.16.1", "eslint-plugin-vue": "^9.17.0",
"jsdom": "^22.1.0", "jsdom": "^22.1.0",
"postcss": "^8.4.27", "postcss": "^8.4.28",
"prettier": "^3.0.1", "prettier": "^3.0.2",
"terser": "^5.19.2", "terser": "^5.19.2",
"vite": "^4.4.9", "vite": "^4.4.9",
"vite-plugin-compression2": "^0.10.3" "vite-plugin-compression2": "^0.10.4",
"vite-plugin-rewrite-all": "^1.0.1"
}, },
"browserslist": [ "browserslist": [
"> 1%", "> 1%",

View File

@ -1,63 +1,99 @@
<!DOCTYPE html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no"> <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> <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
<link rel="icon" type="image/png" sizes="16x16" href="[{[ .StaticURL ]}]/img/icons/favicon-16x16.png"> 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 --> <!-- Add to home screen for Android and modern mobile browsers -->
<link rel="manifest" id="manifestPlaceholder" crossorigin="use-credentials"> <link
<meta name="theme-color" content="[{[ if .Color -]}][{[ .Color ]}][{[ else ]}]#2979ff[{[ end ]}]"> 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 --> <!-- Add to home screen for Safari on iOS/iPadOS -->
<meta name="apple-mobile-web-app-capable" content="yes"> <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-status-bar-style" content="black" />
<meta name="apple-mobile-web-app-title" content="assets"> <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="[{[ .StaticURL ]}]/img/icons/apple-touch-icon.png"
/>
<!-- Add to home screen for Windows --> <!-- Add to home screen for Windows -->
<meta name="msapplication-TileImage" content="[{[ .StaticURL ]}]/img/icons/mstile-144x144.png"> <meta
<meta name="msapplication-TileColor" content="[{[ if .Color -]}][{[ .Color ]}][{[ else ]}]#2979ff[{[ end ]}]"> 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 --> <!-- Inject Some Variables and generate the manifest json -->
<script> <script>
window.FileBrowser = JSON.parse('[{[ .Json ]}]'); // We can assign JSON directly
window.FileBrowser = [{[ .Json ]}];
var fullStaticURL = window.location.origin + window.FileBrowser.StaticURL; // Global function to prepend static url
window.__prependStaticUrl = (url) => {
return `${window.FileBrowser.StaticURL}/${url.replace(/^\/+/, "")}`;
};
var dynamicManifest = { var dynamicManifest = {
"name": window.FileBrowser.Name || 'File Browser', name: window.FileBrowser.Name || "File Browser",
"short_name": window.FileBrowser.Name || 'File Browser', short_name: window.FileBrowser.Name || "File Browser",
"icons": [ icons: [
{ {
"src": fullStaticURL + "/img/icons/android-chrome-192x192.png", src: window.__prependStaticUrl("/img/icons/android-chrome-192x192.png"),
"sizes": "192x192", sizes: "192x192",
"type": "image/png" type: "image/png",
}, },
{ {
"src": fullStaticURL + "/img/icons/android-chrome-512x512.png", src: window.__prependStaticUrl("/img/icons/android-chrome-512x512.png"),
"sizes": "512x512", sizes: "512x512",
"type": "image/png" type: "image/png",
} },
], ],
"start_url": window.location.origin + window.FileBrowser.BaseURL, start_url: window.location.origin + window.FileBrowser.BaseURL,
"display": "standalone", display: "standalone",
"background_color": "#ffffff", background_color: "#ffffff",
"theme_color": window.FileBrowser.Color || "#455a64" theme_color: window.FileBrowser.Color || "#455a64",
} };
const stringManifest = JSON.stringify(dynamicManifest); const stringManifest = JSON.stringify(dynamicManifest);
const blob = new Blob([stringManifest], {type: 'application/json'}); const blob = new Blob([stringManifest], { type: "application/json" });
const manifestURL = URL.createObjectURL(blob); const manifestURL = URL.createObjectURL(blob);
document.querySelector('#manifestPlaceholder').setAttribute('href', manifestURL); document
.querySelector("#manifestPlaceholder")
.setAttribute("href", manifestURL);
</script> </script>
<style> <style>
@ -69,8 +105,8 @@
height: 100%; height: 100%;
background: #fff; background: #fff;
z-index: 9999; z-index: 9999;
transition: .1s ease opacity; transition: 0.1s ease opacity;
-webkit-transition: .1s ease opacity; -webkit-transition: 0.1s ease opacity;
} }
#loading.done { #loading.done {
@ -108,17 +144,26 @@
} }
@-webkit-keyframes sk-bouncedelay { @-webkit-keyframes sk-bouncedelay {
0%, 80%, 100% { -webkit-transform: scale(0) } 0%,
40% { -webkit-transform: scale(1.0) } 80%,
100% {
-webkit-transform: scale(0);
}
40% {
-webkit-transform: scale(1);
}
} }
@keyframes sk-bouncedelay { @keyframes sk-bouncedelay {
0%, 80%, 100% { 0%,
80%,
100% {
-webkit-transform: scale(0); -webkit-transform: scale(0);
transform: scale(0); transform: scale(0);
} 40% { }
-webkit-transform: scale(1.0); 40% {
transform: scale(1.0); -webkit-transform: scale(1);
transform: scale(1);
} }
} }
</style> </style>
@ -134,10 +179,14 @@
</div> </div>
</div> </div>
<script type="module" src="/src/main.js"></script>
[{[ if .Theme -]}] [{[ if .Theme -]}]
<link rel="stylesheet" href="[{[ .StaticURL ]}]/themes/[{[ .Theme ]}].css" /> <link
[{[ end ]}] rel="stylesheet"
[{[ if .CSS -]}] href="[{[ .StaticURL ]}]/themes/[{[ .Theme ]}].css"
/>
[{[ end ]}] [{[ if .CSS -]}]
<link rel="stylesheet" href="[{[ .StaticURL ]}]/custom.css" /> <link rel="stylesheet" href="[{[ .StaticURL ]}]/custom.css" />
[{[ end ]}] [{[ end ]}]
</body> </body>

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
import * as tus from "tus-js-client"; import * as tus from "tus-js-client";
import { baseURL, tusEndpoint, tusSettings } from "@/utils/constants"; import { baseURL, tusEndpoint, tusSettings } from "@/utils/constants";
import store from "@/store"; import { useAuthStore } from "@/stores/auth";
import { removePrefix } from "@/api/utils"; import { removePrefix } from "@/api/utils";
import { fetchURL } from "./utils"; import { fetchURL } from "./utils";
@ -23,6 +23,7 @@ export async function upload(
await createUpload(resourcePath); await createUpload(resourcePath);
const authStore = useAuthStore();
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let upload = new tus.Upload(content, { let upload = new tus.Upload(content, {
uploadUrl: `${baseURL}${resourcePath}`, uploadUrl: `${baseURL}${resourcePath}`,
@ -31,7 +32,7 @@ export async function upload(
parallelUploads: 1, parallelUploads: 1,
storeFingerprintForResuming: false, storeFingerprintForResuming: false,
headers: { headers: {
"X-Auth": store.state.jwt, "X-Auth": authStore.jwt,
}, },
onError: function (error) { onError: function (error) {
reject("Upload failed: " + 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 { renew, logout } from "@/utils/auth";
import { baseURL } from "@/utils/constants"; import { baseURL } from "@/utils/constants";
import { encodePath } from "@/utils/url"; import { encodePath } from "@/utils/url";
export async function fetchURL(url, opts, auth = true) { export async function fetchURL(url, opts, auth = true) {
const authStore = useAuthStore();
opts = opts || {}; opts = opts || {};
opts.headers = opts.headers || {}; opts.headers = opts.headers || {};
@ -12,7 +14,7 @@ export async function fetchURL(url, opts, auth = true) {
try { try {
res = await fetch(`${baseURL}${url}`, { res = await fetch(`${baseURL}${url}`, {
headers: { headers: {
"X-Auth": store.state.jwt, "X-Auth": authStore.jwt,
...headers, ...headers,
}, },
...rest, ...rest,
@ -25,7 +27,7 @@ export async function fetchURL(url, opts, auth = true) {
} }
if (auth && res.headers.get("X-Renew-Token") === "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) { if (res.status < 200 || res.status > 299) {
@ -61,6 +63,8 @@ export function removePrefix(url) {
} }
export function createURL(endpoint, params = {}, auth = true) { export function createURL(endpoint, params = {}, auth = true) {
const authStore = useAuthStore();
let prefix = baseURL; let prefix = baseURL;
if (!prefix.endsWith("/")) { if (!prefix.endsWith("/")) {
prefix = prefix + "/"; prefix = prefix + "/";
@ -68,7 +72,7 @@ export function createURL(endpoint, params = {}, auth = true) {
const url = new URL(prefix + encodePath(endpoint), origin); const url = new URL(prefix + encodePath(endpoint), origin);
const searchParams = { const searchParams = {
...(auth && { auth: store.state.jwt }), ...(auth && { auth: authStore.jwt }),
...params, ...params,
}; };

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -10,9 +10,11 @@ import GlobalSettings from "@/views/settings/Global.vue";
import ProfileSettings from "@/views/settings/Profile.vue"; import ProfileSettings from "@/views/settings/Profile.vue";
import Shares from "@/views/settings/Shares.vue"; import Shares from "@/views/settings/Shares.vue";
import Errors from "@/views/Errors.vue"; import Errors from "@/views/Errors.vue";
import store from "@/store"; import { useAuthStore } from "@/stores/auth";
import { baseURL, name } from "@/utils/constants"; 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 = { const titles = {
Login: "sidebar.login", Login: "sidebar.login",
@ -34,20 +36,13 @@ const routes = [
path: "/login", path: "/login",
name: "Login", name: "Login",
component: Login, component: Login,
beforeEnter: (to, from, next) => {
if (store.getters.isLogged) {
return next({ path: "/files" });
}
next();
},
}, },
{ {
path: "/share", path: "/share",
component: Layout, component: Layout,
children: [ children: [
{ {
path: ":pathMatch(.*)*", path: ":path*",
name: "Share", name: "Share",
component: Share, component: Share,
}, },
@ -56,20 +51,23 @@ const routes = [
{ {
path: "/files", path: "/files",
component: Layout, component: Layout,
children: [
{
path: ":pathMatch(.*)*",
name: "Files",
component: Files,
meta: { meta: {
requiresAuth: true, requiresAuth: true,
}, },
children: [
{
path: ":path*",
name: "Files",
component: Files,
}, },
], ],
}, },
{ {
path: "/settings", path: "/settings",
component: Layout, component: Layout,
meta: {
requiresAuth: true,
},
children: [ children: [
{ {
path: "", path: "",
@ -78,9 +76,6 @@ const routes = [
redirect: { redirect: {
path: "/settings/profile", path: "/settings/profile",
}, },
meta: {
requiresAuth: true,
},
children: [ children: [
{ {
path: "profile", 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({ const router = createRouter({
history: createWebHistory(baseURL), history: createWebHistory(baseURL),
routes, routes,
}); });
router.beforeEach((to, from, next) => { router.beforeResolve(async (to, from, next) => {
// const title = i18n.t(titles[to.name]); let title;
const title = titles[to.name]; try {
title = i18n.global.t(titles[to.name]);
} catch (error) {
title = to.name;
}
// const title = titles[to.name];
document.title = title + " - " + name; document.title = title + " - " + name;
console.log({ from, to });
/*** RTL related settings per route ****/ /*** RTL related settings per route ****/
const rtlSet = document.querySelector("body").classList.contains("rtl"); const rtlSet = document.querySelector("body").classList.contains("rtl");
const shouldSetRtl = rtlLanguages.includes(i18n.locale); const shouldSetRtl = rtlLanguages.includes(i18n.global.locale);
switch (true) { switch (true) {
case shouldSetRtl && !rtlSet: case shouldSetRtl && !rtlSet:
document.querySelector("body").classList.add("rtl"); document.querySelector("body").classList.add("rtl");
@ -183,8 +207,19 @@ router.beforeEach((to, from, next) => {
break; 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 (to.matched.some((record) => record.meta.requiresAuth)) {
if (!store.getters.isLogged) { if (!authStore.isLoggedIn) {
next({ next({
path: "/login", path: "/login",
query: { redirect: to.fullPath }, query: { redirect: to.fullPath },
@ -194,7 +229,7 @@ router.beforeEach((to, from, next) => {
} }
if (to.matched.some((record) => record.meta.requiresAdmin)) { if (to.matched.some((record) => record.meta.requiresAdmin)) {
if (!store.state.user.perm.admin) { if (!authStore.user.perm.admin) {
next({ path: "/403" }); next({ path: "/403" });
return; return;
} }
@ -204,4 +239,4 @@ router.beforeEach((to, from, next) => {
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 router from "@/router";
import { Base64 } from "js-base64"; import jwt_decode from "jwt-decode";
import { baseURL } from "@/utils/constants"; import { baseURL } from "@/utils/constants";
export function parseToken(token) { 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) { document.cookie = `auth=${token}; Path=/; SameSite=Strict;`;
throw new Error("token malformed");
}
const data = JSON.parse(Base64.decode(parts[1]));
document.cookie = `auth=${token}; path=/`;
localStorage.setItem("jwt", token); 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() { export async function validateLogin() {
@ -25,7 +23,7 @@ export async function validateLogin() {
await renew(localStorage.getItem("jwt")); await renew(localStorage.getItem("jwt"));
} }
} catch (_) { } 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() { 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); localStorage.setItem("jwt", null);
router.push({ path: "/login" }); router.push({ path: "/login" });
} }

View File

@ -1,4 +1,4 @@
import store from "@/store"; import { useUploadStore } from "@/stores/upload";
import url from "@/utils/url"; import url from "@/utils/url";
export function checkConflict(files, items) { export function checkConflict(files, items) {
@ -110,8 +110,10 @@ function detectType(mimetype) {
} }
export function handleFiles(files, base, overwrite = false) { export function handleFiles(files, base, overwrite = false) {
const uploadStore = useUploadStore();
for (let i = 0; i < files.length; i++) { for (let i = 0; i < files.length; i++) {
let id = store.state.upload.id; let id = uploadStore.id;
let path = base; let path = base;
let file = files[i]; let file = files[i];
@ -133,6 +135,6 @@ export function handleFiles(files, base, overwrite = false) {
...(!file.isDir && { type: detectType(file.type) }), ...(!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("/"); 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 // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent#Examples
export function encodeRFC5987ValueChars(str) { export function encodeRFC5987ValueChars(str) {
return ( return (
encodeURIComponent(str) encodeURIComponent(str)
// Note that although RFC3986 reserves "!", RFC5987 does not, // The following creates the sequences %27 %28 %29 %2A (Note that
// so we do not need to escape it // the valid encoding of "*" is %2A, which necessitates calling
.replace(/['()]/g, escape) // i.e., %27 %28 %29 // toUpperCase() to properly encode). Although RFC3986 reserves "!",
.replace(/\*/g, "%2A") // 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, // The following are not required for percent-encoding per RFC5987,
// so we can allow for a little better readability over the wire: |`^ // 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> <script>
import { files as api } from "@/api"; 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 HeaderBar from "@/components/header/HeaderBar.vue";
import Breadcrumbs from "@/components/Breadcrumbs.vue"; import Breadcrumbs from "@/components/Breadcrumbs.vue";
@ -50,7 +52,14 @@ export default {
}; };
}, },
computed: { computed: {
...mapState(["req", "reload", "loading", "show"]), ...mapWritableState(useFileStore, [
"req",
"reload",
"selected",
"multiple",
]),
...mapState(useLayoutStore, ["show", "showShell"]),
...mapWritableState(useLayoutStore, ["loading"]),
currentView() { currentView() {
if (this.req.type == undefined) { if (this.req.type == undefined) {
return null; return null;
@ -82,26 +91,27 @@ export default {
mounted() { mounted() {
window.addEventListener("keydown", this.keyEvent); window.addEventListener("keydown", this.keyEvent);
}, },
beforeDestroy() { beforeUnmount() {
window.removeEventListener("keydown", this.keyEvent); window.removeEventListener("keydown", this.keyEvent);
}, },
destroyed() { unmounted() {
if (this.$store.state.showShell) { if (this.showShell) {
this.$store.commit("toggleShell"); this.toggleShell();
} }
this.$store.commit("updateRequest", {}); this.updateRequest({});
}, },
methods: { methods: {
...mapMutations(["setLoading"]), ...mapActions(useLayoutStore, ["toggleShell", "showHover", "closeHovers"]),
...mapActions(useFileStore, ["updateRequest"]),
async fetchData() { async fetchData() {
// Reset view information. // Reset view information.
this.$store.commit("setReload", false); this.reload = false;
this.$store.commit("resetSelected"); this.selected = [];
this.$store.commit("multiple", false); this.multiple = false;
this.$store.commit("closeHovers"); this.closeHovers();
// Set loading to true and reset the error. // Set loading to true and reset the error.
this.setLoading(true); this.loading = true;
this.error = null; this.error = null;
let url = this.$route.path; let url = this.$route.path;
@ -111,25 +121,29 @@ export default {
try { try {
const res = await api.fetch(url); const res = await api.fetch(url);
if (clean(res.path) !== clean(`/${this.$route.params.pathMatch}`)) { if (
return; 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}`; document.title = `${res.name} - ${document.title}`;
} catch (e) { } catch (e) {
this.error = e; this.error = e;
} finally { } finally {
this.setLoading(false); this.loading = false;
} }
}, },
keyEvent(event) { keyEvent(event) {
// F1! // F1!
if (event.keyCode === 112) { if (event.keyCode === 112) {
event.preventDefault(); event.preventDefault();
this.$store.commit("showHover", "help"); this.showHover("help");
} }
}, },
}, },
}; };
</script> </script>
@/stores/file@/stores/layout

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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