Merge remote-tracking branch 'kloon15/vue3' into vue3

This commit is contained in:
Joep 2023-09-11 21:52:59 +02:00
commit bf99823977
No known key found for this signature in database
GPG Key ID: 6F5588F1DC2A8209
41 changed files with 1075 additions and 1041 deletions

View File

@ -3,26 +3,20 @@
"env": { "env": {
"node": true "node": true
}, },
"parser": "vue-eslint-parser",
"extends": [ "extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended",
"plugin:vue/vue3-essential", "plugin:vue/vue3-essential",
"eslint:recommended",
"@vue/eslint-config-typescript",
"@vue/eslint-config-prettier" "@vue/eslint-config-prettier"
], ],
"rules": { "rules": {
"vue/multi-word-component-names": "off", "vue/multi-word-component-names": "off",
"vue/no-reserved-component-names": "warn", "vue/no-reserved-component-names": "warn",
"vue/no-mutating-props": "warn", "vue/no-mutating-props": "warn",
"@typescript-eslint/no-explicit-any": "off", "no-undef": "error"
"@typescript-eslint/ban-ts-comment": "off",
"no-undef": "off"
}, },
"parserOptions": { "parserOptions": {
"ecmaVersion": "latest", "ecmaVersion": "latest",
"sourceType": "module", "sourceType": "module"
"parser": "@typescript-eslint/parser"
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -14,17 +14,17 @@
"format": "prettier --write ." "format": "prettier --write ."
}, },
"dependencies": { "dependencies": {
"@vue/eslint-config-typescript": "^11.0.3",
"@vueuse/core": "^10.4.1", "@vueuse/core": "^10.4.1",
"ace-builds": "^1.24.1", "ace-builds": "^1.24.2",
"clipboard": "^2.0.11", "clipboard": "^2.0.11",
"core-js": "^3.32.1", "core-js": "^3.32.2",
"dayjs": "^1.11.9", "dayjs": "^1.11.9",
"filesize": "^10.0.12", "filesize": "^10.0.12",
"js-base64": "^3.7.5", "js-base64": "^3.7.5",
"jwt-decode": "^3.1.2", "jwt-decode": "^3.1.2",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"material-icons": "^1.13.10", "material-icons": "^1.13.11",
"moment": "^2.29.4",
"normalize.css": "^8.0.1", "normalize.css": "^8.0.1",
"pinia": "^2.1.6", "pinia": "^2.1.6",
"pretty-bytes": "^6.1.1", "pretty-bytes": "^6.1.1",
@ -32,26 +32,32 @@
"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-i18n": "^9.2.2", "vue-i18n": "^9.3.0",
"vue-lazyload": "^3.0.0", "vue-lazyload": "^3.0.0",
"vue-router": "^4.2.4", "vue-router": "^4.2.4",
"vue-toastification": "^2.0.0-rc.5" "vue-toastification": "^2.0.0-rc.5"
}, },
"devDependencies": { "devDependencies": {
<<<<<<< HEAD
"@intlify/unplugin-vue-i18n": "^0.12.3", "@intlify/unplugin-vue-i18n": "^0.12.3",
"@types/lodash-es": "^4.17.9", "@types/lodash-es": "^4.17.9",
=======
"@intlify/unplugin-vue-i18n": "^1.0.1",
"@types/lodash-es": "^4.17.9",
"@types/node": "^20.6.0",
>>>>>>> kloon15/vue3
"@typescript-eslint/eslint-plugin": "^6.6.0", "@typescript-eslint/eslint-plugin": "^6.6.0",
"@vitejs/plugin-legacy": "^4.1.1", "@vitejs/plugin-legacy": "^4.1.1",
"@vitejs/plugin-vue": "^4.3.3", "@vitejs/plugin-vue": "^4.3.4",
"@vue/eslint-config-prettier": "^8.0.0", "@vue/eslint-config-prettier": "^8.0.0",
"autoprefixer": "^10.4.15", "autoprefixer": "^10.4.15",
"eslint": "^8.48.0", "eslint": "^8.49.0",
"eslint-plugin-prettier": "^5.0.0", "eslint-plugin-prettier": "^5.0.0",
"eslint-plugin-vue": "^9.17.0", "eslint-plugin-vue": "^9.17.0",
"jsdom": "^22.1.0", "jsdom": "^22.1.0",
"postcss": "^8.4.28", "postcss": "^8.4.29",
"prettier": "^3.0.2", "prettier": "^3.0.3",
"terser": "^5.19.2", "terser": "^5.19.4",
"vite": "^4.4.9", "vite": "^4.4.9",
"vite-plugin-compression2": "^0.10.4", "vite-plugin-compression2": "^0.10.4",
"vite-plugin-rewrite-all": "^1.0.1" "vite-plugin-rewrite-all": "^1.0.1"

View File

@ -1,7 +1,7 @@
:root { :root {
--background: #141D24; --background: #141d24;
--surfacePrimary: #20292F; --surfacePrimary: #20292f;
--surfaceSecondary: #3A4147; --surfaceSecondary: #3a4147;
--divider: rgba(255, 255, 255, 0.12); --divider: rgba(255, 255, 255, 0.12);
--icon: #ffffff; --icon: #ffffff;
--textPrimary: rgba(255, 255, 255, 0.87); --textPrimary: rgba(255, 255, 255, 0.87);
@ -16,7 +16,8 @@ body {
#loading { #loading {
background: var(--background); background: var(--background);
} }
#loading .spinner div, main .spinner div { #loading .spinner div,
main .spinner div {
background: var(--icon); background: var(--icon);
} }
@ -56,7 +57,7 @@ header {
color: var(--textPrimary) !important; color: var(--textPrimary) !important;
} }
.action:hover { .action:hover {
background-color: rgba(255, 255, 255, .1); background-color: rgba(255, 255, 255, 0.1);
} }
.action i { .action i {
color: var(--icon) !important; color: var(--icon) !important;
@ -77,7 +78,7 @@ nav > div {
color: var(--textPrimary) !important; color: var(--textPrimary) !important;
} }
.breadcrumbs a:hover { .breadcrumbs a:hover {
background-color: rgba(255, 255, 255, .1); background-color: rgba(255, 255, 255, 0.1);
} }
#listing .item { #listing .item {
@ -141,11 +142,11 @@ nav > div {
border-color: rgba(255, 255, 255, 0.15); border-color: rgba(255, 255, 255, 0.15);
} }
.input--red { .input--red {
background: #73302D; background: #73302d;
} }
.input--green { .input--green {
background: #147A41; background: #147a41;
} }
.dashboard #nav .wrapper, .dashboard #nav .wrapper,
@ -166,7 +167,7 @@ table th {
.file-list li:before { .file-list li:before {
color: var(--textSecondary); color: var(--textSecondary);
} }
.file-list li[aria-selected=true]:before { .file-list li[aria-selected="true"]:before {
color: var(--icon); color: var(--icon);
} }

View File

@ -2,8 +2,15 @@ import { createURL, fetchURL, removePrefix } from "./utils";
import { baseURL } from "@/utils/constants"; import { baseURL } from "@/utils/constants";
import { useAuthStore } from "@/stores/auth"; import { useAuthStore } from "@/stores/auth";
import { upload as postTus, useTus } from "./tus"; import { upload as postTus, useTus } from "./tus";
import type {
ApiContent,
ApiMethod,
ApiOpts,
ChecksumAlgs,
IFile,
} from "@/types";
export async function fetch(url: ApiUrl) { export async function fetch(url: string) {
url = removePrefix(url); url = removePrefix(url);
const res = await fetchURL(`/api/resources${url}`, {}); const res = await fetchURL(`/api/resources${url}`, {});
@ -29,7 +36,7 @@ export async function fetch(url: ApiUrl) {
return data; return data;
} }
async function resourceAction(url: ApiUrl, method: ApiMethod, content?: any) { async function resourceAction(url: string, method: ApiMethod, content?: any) {
url = removePrefix(url); url = removePrefix(url);
const opts: ApiOpts = { const opts: ApiOpts = {
@ -45,11 +52,11 @@ async function resourceAction(url: ApiUrl, method: ApiMethod, content?: any) {
return res; return res;
} }
export async function remove(url: ApiUrl) { export async function remove(url: string) {
return resourceAction(url, "DELETE"); return resourceAction(url, "DELETE");
} }
export async function put(url: ApiUrl, content = "") { export async function put(url: string, content = "") {
return resourceAction(url, "PUT", content); return resourceAction(url, "PUT", content);
} }
@ -83,7 +90,7 @@ export function download(format: any, ...files: string[]) {
} }
export async function post( export async function post(
url: ApiUrl, url: string,
content: ApiContent = "", content: ApiContent = "",
overwrite = false, overwrite = false,
onupload: any = () => {} onupload: any = () => {}
@ -103,7 +110,7 @@ export async function post(
} }
async function postResources( async function postResources(
url: ApiUrl, url: string,
content: ApiContent = "", content: ApiContent = "",
overwrite = false, overwrite = false,
onupload: any onupload: any
@ -178,7 +185,7 @@ export function copy(items: any[], overwrite = false, rename = false) {
return moveCopy(items, true, overwrite, rename); return moveCopy(items, true, overwrite, rename);
} }
export async function checksum(url: ApiUrl, algo: algo) { export async function checksum(url: string, algo: ChecksumAlgs) {
const data = await resourceAction(`${url}?checksum=${algo}`, "GET"); const data = await resourceAction(`${url}?checksum=${algo}`, "GET");
return (await data.json()).checksums[algo]; return (await data.json()).checksums[algo];
} }
@ -213,7 +220,7 @@ export function getSubtitlesURL(file: IFile) {
return subtitles; return subtitles;
} }
export async function usage(url: ApiUrl) { export async function usage(url: string) {
url = removePrefix(url); url = removePrefix(url);
const res = await fetchURL(`/api/usage${url}`, {}); const res = await fetchURL(`/api/usage${url}`, {});

View File

@ -1,7 +1,8 @@
import type { IShare } from "@/types";
import { fetchURL, removePrefix, createURL } from "./utils"; import { fetchURL, removePrefix, createURL } from "./utils";
import { baseURL } from "@/utils/constants"; import { baseURL } from "@/utils/constants";
export async function fetch(url: ApiUrl, password: string = "") { export async function fetch(url: string, password: string = "") {
url = removePrefix(url); url = removePrefix(url);
const res = await fetchURL( const res = await fetchURL(
@ -66,7 +67,7 @@ export function download(
window.open(url); window.open(url);
} }
export function getDownloadURL(share: IFile, inline = false) { export function getDownloadURL(share: IShare, inline = false) {
const params = { const params = {
...(inline && { inline: "true" }), ...(inline && { inline: "true" }),
...(share.token && { token: share.token }), ...(share.token && { token: share.token }),

View File

@ -1,7 +1,8 @@
import { fetchURL, removePrefix } from "./utils"; import { fetchURL, removePrefix } from "./utils";
import url from "../utils/url"; import url from "../utils/url";
import type { Item } from "@/types";
export default async function search(base: apiUrl, query: string) { export default async function search(base: string, query: string) {
base = removePrefix(base); base = removePrefix(base);
query = encodeURIComponent(query); query = encodeURIComponent(query);
@ -13,7 +14,7 @@ export default async function search(base: apiUrl, query: string) {
let data = await res.json(); let data = await res.json();
data = data.map((item: item) => { data = data.map((item: Item) => {
item.url = `/files${base}` + url.encodePath(item.path); item.url = `/files${base}` + url.encodePath(item.path);
if (item.dir) { if (item.dir) {

View File

@ -1,3 +1,4 @@
import type { ISettings } from "@/types";
import { fetchURL, fetchJSON } from "./utils"; import { fetchURL, fetchJSON } from "./utils";
export function get() { export function get() {

View File

@ -1,3 +1,4 @@
import type { ApiUrl, IShare } from "@/types";
import { fetchURL, fetchJSON, removePrefix, createURL } from "./utils"; import { fetchURL, fetchJSON, removePrefix, createURL } from "./utils";
export async function list() { export async function list() {

View File

@ -3,6 +3,7 @@ import { baseURL, tusEndpoint, tusSettings } from "@/utils/constants";
import { useAuthStore } from "@/stores/auth"; import { useAuthStore } from "@/stores/auth";
import { removePrefix } from "@/api/utils"; import { removePrefix } from "@/api/utils";
import { fetchURL } from "./utils"; import { fetchURL } from "./utils";
import type { ApiContent, TusSettings } from "@/types";
const RETRY_BASE_DELAY = 1000; const RETRY_BASE_DELAY = 1000;
const RETRY_MAX_DELAY = 20000; const RETRY_MAX_DELAY = 20000;
@ -57,7 +58,7 @@ export async function upload(
}); });
} }
async function createUpload(resourcePath: resourcePath) { async function createUpload(resourcePath: string) {
const headResp = await fetchURL(resourcePath, { const headResp = await fetchURL(resourcePath, {
method: "POST", method: "POST",
}); });
@ -68,7 +69,7 @@ async function createUpload(resourcePath: resourcePath) {
} }
} }
function computeRetryDelays(tusSettings: tusSettings): number[] | undefined { function computeRetryDelays(tusSettings: TusSettings): number[] | undefined {
if (!tusSettings.retryCount || tusSettings.retryCount < 1) { if (!tusSettings.retryCount || tusSettings.retryCount < 1) {
// Disable retries altogether // Disable retries altogether
return undefined; return undefined;

View File

@ -1,3 +1,4 @@
import type { User } from "@/types/user";
import { fetchURL, fetchJSON } from "./utils"; import { fetchURL, fetchJSON } from "./utils";
export async function getAll() { export async function getAll() {
@ -8,7 +9,7 @@ export async function get(id: number) {
return fetchJSON(`/api/users/${id}`, {}); return fetchJSON(`/api/users/${id}`, {});
} }
export async function create(user: user) { export async function create(user: User) {
const res = await fetchURL(`/api/users`, { const res = await fetchURL(`/api/users`, {
method: "POST", method: "POST",
body: JSON.stringify({ body: JSON.stringify({
@ -23,7 +24,7 @@ export async function create(user: user) {
} }
} }
export async function update(user: user, which = ["all"]) { export async function update(user: User, which = ["all"]) {
await fetchURL(`/api/users/${user.id}`, { await fetchURL(`/api/users/${user.id}`, {
method: "PUT", method: "PUT",
body: JSON.stringify({ body: JSON.stringify({

View File

@ -1,9 +1,20 @@
import { useAuthStore } from "@/stores/auth"; import { useAuthStore } from "@/stores/auth";
import type { ApiOpts, SearchParams } from "@/types";
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: ApiUrl, opts: ApiOpts, auth = true) { export class StatusError extends Error {
constructor(
message: any,
public status?: number
) {
super(message);
this.name = "StatusError";
}
}
export async function fetchURL(url: string, opts: ApiOpts, auth = true) {
const authStore = useAuthStore(); const authStore = useAuthStore();
opts = opts || {}; opts = opts || {};
@ -20,11 +31,7 @@ export async function fetchURL(url: ApiUrl, opts: ApiOpts, auth = true) {
...rest, ...rest,
}); });
} catch { } catch {
const error = new Error("000 No connection"); throw new StatusError("000 No connection", 0);
// @ts-ignore don't know yet how to solve
error.status = 0;
throw error;
} }
if (auth && res.headers.get("X-Renew-Token") === "true") { if (auth && res.headers.get("X-Renew-Token") === "true") {
@ -32,9 +39,7 @@ export async function fetchURL(url: ApiUrl, opts: ApiOpts, auth = true) {
} }
if (res.status < 200 || res.status > 299) { if (res.status < 200 || res.status > 299) {
const error = new Error(await res.text()); const error = new StatusError(await res.text(), res.status);
// @ts-ignore don't know yet how to solve
error.status = res.status;
if (auth && res.status == 401) { if (auth && res.status == 401) {
logout(); logout();
@ -46,7 +51,7 @@ export async function fetchURL(url: ApiUrl, opts: ApiOpts, auth = true) {
return res; return res;
} }
export async function fetchJSON(url: ApiUrl, opts?: any) { export async function fetchJSON(url: string, opts?: any) {
const res = await fetchURL(url, opts); const res = await fetchURL(url, opts);
if (res.status === 200) { if (res.status === 200) {
@ -56,7 +61,7 @@ export async function fetchJSON(url: ApiUrl, opts?: any) {
} }
} }
export function removePrefix(url: ApiUrl) { export function removePrefix(url: string) {
url = url.split("/").splice(2).join("/"); url = url.split("/").splice(2).join("/");
if (url === "") url = "/"; if (url === "") url = "/";
@ -64,7 +69,7 @@ export function removePrefix(url: ApiUrl) {
return url; return url;
} }
export function createURL(endpoint: ApiUrl, params = {}, auth = true) { export function createURL(endpoint: string, params = {}, auth = true) {
const authStore = useAuthStore(); const authStore = useAuthStore();
let prefix = baseURL; let prefix = baseURL;
@ -73,7 +78,7 @@ export function createURL(endpoint: ApiUrl, params = {}, auth = true) {
} }
const url = new URL(prefix + encodePath(endpoint), origin); const url = new URL(prefix + encodePath(endpoint), origin);
const searchParams: searchParams = { const searchParams: SearchParams = {
...(auth && { auth: authStore.jwt }), ...(auth && { auth: authStore.jwt }),
...params, ...params,
}; };

View File

@ -74,6 +74,10 @@ import Commands from "./Commands.vue";
import { enableExec } from "@/utils/constants"; import { enableExec } from "@/utils/constants";
import { computed, onMounted, ref, watch } from "vue"; import { computed, onMounted, ref, watch } from "vue";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
<<<<<<< HEAD
=======
import { IUser } from "@/types";
>>>>>>> kloon15/vue3
const { t } = useI18n(); const { t } = useI18n();
@ -82,9 +86,15 @@ const originalUserScope = ref<string | null>(null);
const props = defineProps<{ const props = defineProps<{
user: IUser; user: IUser;
<<<<<<< HEAD
createUserDir: boolean; createUserDir: boolean;
isNew: boolean; isNew: boolean;
isDefault: boolean; isDefault: boolean;
=======
isNew: boolean;
isDefault: boolean;
createUserDir?: boolean;
>>>>>>> kloon15/vue3
}>(); }>();
onMounted(() => { onMounted(() => {

View File

@ -24,29 +24,9 @@ import("dayjs/locale/uk");
import("dayjs/locale/zh-cn"); import("dayjs/locale/zh-cn");
import("dayjs/locale/zh-tw"); import("dayjs/locale/zh-tw");
import he from "./he.json"; // All i18n resources specified in the plugin `include` option can be loaded
import hu from "./hu.json"; // at once using the import syntax
import ar from "./ar.json"; import messages from "@intlify/unplugin-vue-i18n/messages";
import de from "./de.json";
import en from "./en.json";
import es from "./es.json";
import fr from "./fr.json";
import is from "./is.json";
import it from "./it.json";
import ja from "./ja.json";
import ko from "./ko.json";
import nlBE from "./nl-be.json";
import pl from "./pl.json";
import pt from "./pt.json";
import ptBR from "./pt-br.json";
import ro from "./ro.json";
import ru from "./ru.json";
import sk from "./sk.json";
import tr from "./tr.json";
import uk from "./uk.json";
import svSE from "./sv-se.json";
import zhCN from "./zh-cn.json";
import zhTW from "./zh-tw.json";
export function detectLocale() { export function detectLocale() {
// locale is an RFC 5646 language tag // locale is an RFC 5646 language tag
@ -132,7 +112,8 @@ export function detectLocale() {
return locale; return locale;
} }
const removeEmpty = (obj) => // TODO: was this really necessary?
function removeEmpty(obj: Record<string, any>): void {
Object.keys(obj) Object.keys(obj)
.filter((k) => obj[k] !== null && obj[k] !== undefined && obj[k] !== "") // Remove undef. and null and empty.string. .filter((k) => obj[k] !== null && obj[k] !== undefined && obj[k] !== "") // Remove undef. and null and empty.string.
.reduce( .reduce(
@ -142,37 +123,14 @@ const removeEmpty = (obj) =>
: Object.assign(newObj, { [k]: obj[k] }), // Copy value. : Object.assign(newObj, { [k]: obj[k] }), // Copy value.
{} {}
); );
}
export const rtlLanguages = ["he", "ar"]; export const rtlLanguages = ["he", "ar"];
export const i18n = createI18n({ export const i18n = createI18n({
locale: detectLocale(), locale: detectLocale(),
fallbackLocale: "en", fallbackLocale: "en",
messages: { messages,
he: removeEmpty(he),
hu: removeEmpty(hu),
ar: removeEmpty(ar),
de: removeEmpty(de),
en: en,
es: removeEmpty(es),
fr: removeEmpty(fr),
is: removeEmpty(is),
it: removeEmpty(it),
ja: removeEmpty(ja),
ko: removeEmpty(ko),
"nl-be": removeEmpty(nlBE),
pl: removeEmpty(pl),
"pt-br": removeEmpty(ptBR),
pt: removeEmpty(pt),
ru: removeEmpty(ru),
ro: removeEmpty(ro),
sk: removeEmpty(sk),
sv: removeEmpty(svSE),
tr: removeEmpty(tr),
uk: removeEmpty(uk),
"zh-cn": removeEmpty(zhCN),
"zh-tw": removeEmpty(zhTW),
},
// expose i18n.global for outside components // expose i18n.global for outside components
legacy: true, legacy: true,
}); });

View File

@ -1,4 +1,3 @@
// @ts-nocheck
import { disableExternal } from "@/utils/constants"; import { disableExternal } from "@/utils/constants";
import { createApp } from "vue"; import { createApp } from "vue";
import VueLazyload from "vue-lazyload"; import VueLazyload from "vue-lazyload";
@ -64,7 +63,7 @@ const toastConfig = {
icon: true, icon: true,
}; };
app.provide("$showSuccess", (message) => { app.provide("$showSuccess", (message: string) => {
const $toast = useToast(); const $toast = useToast();
$toast.success( $toast.success(
{ {
@ -73,22 +72,27 @@ app.provide("$showSuccess", (message) => {
message: message, message: message,
}, },
}, },
// their type defs are messed up
//@ts-ignore
{ ...toastConfig, rtl: rtlLanguages.includes(i18n.global.locale) } { ...toastConfig, rtl: rtlLanguages.includes(i18n.global.locale) }
); );
}); });
app.provide("$showError", (error, displayReport = true) => { app.provide("$showError", (error: Error | string, displayReport = true) => {
const $toast = useToast(); const $toast = useToast();
$toast.error( $toast.error(
{ {
component: CustomToast, component: CustomToast,
props: { props: {
message: error.message || error, message: (error as Error).message || error,
isReport: !disableExternal && displayReport, isReport: !disableExternal && displayReport,
// TODO: i couldnt use $t inside the component // TODO: i couldnt use $t inside the component
//@ts-ignore
reportText: i18n.global.t("buttons.reportIssue"), reportText: i18n.global.t("buttons.reportIssue"),
}, },
}, },
// their type defs are messed up
//@ts-ignore
{ {
...toastConfig, ...toastConfig,
timeout: 0, timeout: 0,

View File

@ -176,12 +176,10 @@ const router = createRouter({
routes, routes,
}); });
router.beforeResolve(async (to: RouteLocation, from, next) => { router.beforeResolve(async (to, from, next) => {
let title; let title;
try { try {
// this should not fail after we finished the migration title = i18n.global.t(titles[to.name as keyof typeof titles]);
// @ts-ignore
title = i18n.global.t(titles[to.name]);
} catch (error) { } catch (error) {
console.error(error); console.error(error);
} }

View File

@ -1,4 +1,4 @@
// @ts-nocheck import type { IUser } from "@/types";
import { defineStore } from "pinia"; import { defineStore } from "pinia";
import dayjs from "dayjs"; import dayjs from "dayjs";
import i18n, { detectLocale } from "@/i18n"; import i18n, { detectLocale } from "@/i18n";
@ -19,33 +19,29 @@ export const useAuthStore = defineStore("auth", {
}, },
actions: { actions: {
// no context as first argument, use `this` instead // no context as first argument, use `this` instead
setUser(value: user) { setUser(user: IUser | null) {
if (value === null) { if (user === null) {
this.user = null; this.user = null;
return; return;
} }
const locale = value.locale || detectLocale(); const locale = user.locale || detectLocale();
dayjs.locale(locale); dayjs.locale(locale);
// @ts-ignore Don't know how to fix this yet // according to doc u only need .value if legacy: false but they lied
// https://vue-i18n.intlify.dev/guide/essentials/scope.html#local-scope-1
//@ts-ignore
i18n.global.locale.value = locale; i18n.global.locale.value = locale;
this.user = value; this.user = user;
}, },
updateUser(value: user) { updateUser(user: Partial<IUser>) {
if (typeof value !== "object") return; if (user.locale) {
dayjs.locale(user.locale);
let field: userKey; // see above
for (field in value) { //@ts-ignore
if (field === "locale") { i18n.global.locale.value = user.locale;
const locale = value[field];
dayjs.locale(locale);
// @ts-ignore Don't know how to fix this yet
i18n.global.locale.value = locale;
}
// @ts-ignore to fix
this.user[field] = cloneDeep(value[field]);
} }
this.user = { ...this.user, ...cloneDeep(user) } as IUser;
}, },
// easily reset state using `$reset` // easily reset state using `$reset`
clearUser() { clearUser() {

View File

@ -1,3 +1,4 @@
import type { IFile } from "@/types";
import { defineStore } from "pinia"; import { defineStore } from "pinia";
export const useFileStore = defineStore("file", { export const useFileStore = defineStore("file", {

View File

@ -1,3 +1,4 @@
import type { LayoutValue } from "@/types";
import { defineStore } from "pinia"; import { defineStore } from "pinia";
// import { useAuthPreferencesStore } from "./auth-preferences"; // import { useAuthPreferencesStore } from "./auth-preferences";
// import { useAuthEmailStore } from "./auth-email"; // import { useAuthEmailStore } from "./auth-email";

View File

@ -1,10 +1,11 @@
// @ts-nocheck
import { defineStore } from "pinia"; import { defineStore } from "pinia";
import { useFileStore } from "./file"; import { useFileStore } from "./file";
import { files as api } from "@/api"; import { files as api } from "@/api";
import throttle from "lodash/throttle"; import throttle from "lodash/throttle";
import buttons from "@/utils/buttons"; import buttons from "@/utils/buttons";
import type { Item, Uploads } from "@/types";
// TODO: make this into a user setting
const UPLOADS_LIMIT = 5; const UPLOADS_LIMIT = 5;
const beforeUnload = (event: Event) => { const beforeUnload = (event: Event) => {
@ -20,8 +21,8 @@ export const useUploadStore = defineStore("upload", {
sizes: any[]; sizes: any[];
progress: any[]; progress: any[];
queue: any[]; queue: any[];
uploads: uploads; uploads: Uploads;
error: any; error: Error | null;
} => ({ } => ({
id: 0, id: 0,
sizes: [], sizes: [],
@ -39,7 +40,6 @@ export const useUploadStore = defineStore("upload", {
const totalSize = state.sizes.reduce((a, b) => a + b, 0); const totalSize = state.sizes.reduce((a, b) => a + b, 0);
// @ts-ignore
const sum: number = state.progress.reduce((acc, val) => acc + val); const sum: number = state.progress.reduce((acc, val) => acc + val);
return Math.ceil((sum / totalSize) * 100); return Math.ceil((sum / totalSize) * 100);
}, },
@ -80,7 +80,7 @@ export const useUploadStore = defineStore("upload", {
const { id, loaded } = obj; const { id, loaded } = obj;
this.progress[id] = loaded; this.progress[id] = loaded;
}, },
setError(error) { setError(error: Error) {
this.error = error; this.error = error;
}, },
reset() { reset() {
@ -88,7 +88,7 @@ export const useUploadStore = defineStore("upload", {
this.sizes = []; this.sizes = [];
this.progress = []; this.progress = [];
}, },
addJob(item: item) { addJob(item: Item) {
this.queue.push(item); this.queue.push(item);
this.sizes[this.id] = item.file.size; this.sizes[this.id] = item.file.size;
this.id++; this.id++;
@ -103,7 +103,7 @@ export const useUploadStore = defineStore("upload", {
// Vue.delete(this.uploads, id); // Vue.delete(this.uploads, id);
delete this.uploads[id]; delete this.uploads[id];
}, },
upload(item: item) { upload(item: Item) {
const uploadsCount = Object.keys(this.uploads).length; const uploadsCount = Object.keys(this.uploads).length;
const isQueueEmpty = this.queue.length == 0; const isQueueEmpty = this.queue.length == 0;
@ -117,7 +117,7 @@ export const useUploadStore = defineStore("upload", {
this.addJob(item); this.addJob(item);
this.processUploads(); this.processUploads();
}, },
finishUpload(item: item) { finishUpload(item: Item) {
this.setProgress({ id: item.id, loaded: item.file.size > 0 }); this.setProgress({ id: item.id, loaded: item.file.size > 0 });
this.removeJob(item.id); this.removeJob(item.id);
this.processUploads(); this.processUploads();

View File

@ -1,40 +1,45 @@
type ApiUrl = string; // Can also be set as a path eg: "path1" | "path2" export type ApiUrl = string; // Can also be set as a path eg: "path1" | "path2"
type resourcePath = string; export type ApiMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
type ApiMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH"; export type ApiContent =
type ApiContent =
| Blob | Blob
| File | File
| Pick<ReadableStreamDefaultReader<any>, "read"> | Pick<ReadableStreamDefaultReader<any>, "read">
| ""; | "";
interface ApiOpts { export interface ApiOpts {
method?: ApiMethod; method?: ApiMethod;
headers?: object; headers?: object;
body?: any; body?: any;
} }
interface tusSettings { export interface TusSettings {
retryCount: number; retryCount: number;
chunkSize: number; chunkSize: number;
} }
type algo = any; export type ChecksumAlgs = "md5" | "sha1" | "sha256" | "sha512";
type inline = any; type inline = any;
<<<<<<< HEAD
interface IShare { interface IShare {
expire: any; expire: any;
=======
export interface IShare {
>>>>>>> kloon15/vue3
hash: string; hash: string;
path: string; path: string;
userID: number; expire?: any;
token: string; userID?: number;
token?: string;
} }
interface settings { interface settings {
any; any;
} }
type searchParams = any; export interface SearchParams {
[key: string]: string;
}

View File

@ -1,4 +1,4 @@
interface IFile { export interface IFile {
index?: number; index?: number;
name: string; name: string;
modified: string; modified: string;
@ -7,14 +7,14 @@ interface IFile {
isDir: boolean; isDir: boolean;
size: number; size: number;
fullPath: string; fullPath: string;
type: uploadType; type: FileType;
items: IFile[]; items: IFile[];
token?: string; token?: string;
hash: string; hash: string;
url?: string; url?: string;
} }
type uploadType = export type FileType =
| "video" | "video"
| "audio" | "audio"
| "image" | "image"
@ -37,12 +37,22 @@ type req = {
hash: string; hash: string;
}; };
interface uploads { export interface Uploads {
[key: string]: upload; [key: string]: Upload;
} }
interface upload { export interface Upload {
id: number; id: number;
file: file; file: IFile;
type: string; type: string;
} }
export interface Item {
id: number;
url?: string;
path: string;
file: IFile;
dir?: boolean;
overwrite?: boolean;
type?: FileType;
}

6
frontend/src/types/index.d.ts vendored Normal file
View File

@ -0,0 +1,6 @@
export * from "./api";
export * from "./file";
export * from "./layout";
export * from "./settings";
export * from "./toast";
export * from "./user";

View File

@ -1,4 +1,4 @@
interface LayoutValue { export interface LayoutValue {
prompt: string; prompt: string;
confirm: any; confirm: any;
action?: boolean; action?: boolean;

View File

@ -1,8 +1,10 @@
interface ISettings { import type { Permissions } from "./user";
export interface ISettings {
signup: boolean; signup: boolean;
createUserDir: boolean; createUserDir: boolean;
userHomeBasePath: string; userHomeBasePath: string;
defaults: Defaults; defaults: SettingsDefaults;
rules: any[]; rules: any[];
branding: SettingsBranding; branding: SettingsBranding;
tus: SettingsTus; tus: SettingsTus;
@ -16,7 +18,7 @@ interface SettingsDefaults {
viewMode: string; viewMode: string;
singleClick: boolean; singleClick: boolean;
sorting: SettingsSorting; sorting: SettingsSorting;
perm: SettingsPerm; perm: Permissions;
commands: any[]; commands: any[];
hideDotfiles: boolean; hideDotfiles: boolean;
dateFormat: boolean; dateFormat: boolean;
@ -27,17 +29,6 @@ interface SettingsSorting {
asc: boolean; asc: boolean;
} }
interface SettingsPerm {
admin: boolean;
execute: boolean;
create: boolean;
rename: boolean;
modify: boolean;
delete: boolean;
share: boolean;
download: boolean;
}
interface SettingsBranding { interface SettingsBranding {
name: string; name: string;
disableExternal: boolean; disableExternal: boolean;
@ -52,7 +43,7 @@ interface SettingsTus {
retryCount: number; retryCount: number;
} }
interface SettingsCommand { export interface SettingsCommand {
after_copy?: string[]; after_copy?: string[];
after_delete?: string[]; after_delete?: string[];
after_rename?: string[]; after_rename?: string[];
@ -65,7 +56,7 @@ interface SettingsCommand {
before_upload?: string[]; before_upload?: string[];
} }
interface SettingsUnit { export interface SettingsUnit {
KB: number; KB: number;
MB: number; MB: number;
GB: number; GB: number;

View File

@ -1 +1,5 @@
type TToast = (message: string) => void; export type IToastSuccess = (message: string) => void;
export type IToastError = (
error: Error | string,
displayReport?: boolean
) => void;

View File

@ -1,11 +1,16 @@
<<<<<<< HEAD
type UserKey = keyof IUser; type UserKey = keyof IUser;
interface IUser { interface IUser {
=======
export interface IUser {
>>>>>>> kloon15/vue3
id: number; id: number;
username: string; username: string;
password: string; password: string;
scope: string; scope: string;
locale: string; locale: string;
<<<<<<< HEAD
lockPassword: boolean; lockPassword: boolean;
viewMode: string; viewMode: string;
singleClick: boolean; singleClick: boolean;
@ -32,3 +37,44 @@ interface UserSorting {
by: string; by: string;
asc: boolean; asc: boolean;
} }
=======
perm: Permissions;
commands: string[];
rules: IRule[];
lockPassword: boolean;
hideDotfiles: boolean;
singleClick: boolean;
dateFormat: boolean;
}
export interface Permissions {
admin: boolean;
copy: boolean;
create: boolean;
delete: boolean;
download: boolean;
execute: boolean;
modify: boolean;
move: boolean;
rename: boolean;
share: boolean;
shell: boolean;
upload: boolean;
}
export interface UserSorting {
by: string;
asc: boolean;
}
export interface IRule {
allow: boolean;
path: string;
regex: boolean;
regexp: IRegexp;
}
interface IRegexp {
raw: string;
}
>>>>>>> kloon15/vue3

View File

@ -2,10 +2,12 @@ import { useAuthStore } from "@/stores/auth";
import router from "@/router"; import router from "@/router";
import jwt_decode from "jwt-decode"; import jwt_decode from "jwt-decode";
import { baseURL } from "./constants"; import { baseURL } from "./constants";
import { StatusError } from "@/api/utils";
import type { User } from "@/types";
export function parseToken(token: string) { export function parseToken(token: string) {
// falsy or malformed jwt will throw InvalidTokenError // falsy or malformed jwt will throw InvalidTokenError
const data = jwt_decode<{ [key: string]: any; user: user }>(token); const data = jwt_decode<{ [key: string]: any; user: User }>(token);
document.cookie = `auth=${token}; Path=/; SameSite=Strict;`; document.cookie = `auth=${token}; Path=/; SameSite=Strict;`;
@ -80,8 +82,7 @@ export async function signup(username: string, password: string) {
}); });
if (res.status !== 200) { if (res.status !== 200) {
// @ts-ignore still need to fix these errors throw new StatusError(res.statusText, res.status);
throw new Error(res.status);
} }
} }

View File

@ -1,7 +1,8 @@
import { useUploadStore } from "@/stores/upload"; import { useUploadStore } from "@/stores/upload";
import type { IFile, Item, FileType } from "@/types";
import url from "@/utils/url"; import url from "@/utils/url";
export function checkConflict(files: file[], items: item[]) { export function checkConflict(files: IFile[], items: Item[]) {
if (typeof items === "undefined" || items === null) { if (typeof items === "undefined" || items === null) {
items = []; items = [];
} }
@ -34,7 +35,7 @@ export function checkConflict(files: file[], items: item[]) {
return conflict; return conflict;
} }
export function scanFiles(dt: { [key: string]: any; item: item }) { export function scanFiles(dt: { [key: string]: any; item: Item }) {
return new Promise((resolve) => { return new Promise((resolve) => {
let reading = 0; let reading = 0;
const contents: any[] = []; const contents: any[] = [];
@ -56,7 +57,7 @@ export function scanFiles(dt: { [key: string]: any; item: item }) {
function readEntry(entry: any, directory = "") { function readEntry(entry: any, directory = "") {
if (entry.isFile) { if (entry.isFile) {
reading++; reading++;
entry.file((file: file) => { entry.file((file: IFile) => {
reading--; reading--;
file.fullPath = `${directory}${file.name}`; file.fullPath = `${directory}${file.name}`;
@ -101,7 +102,7 @@ export function scanFiles(dt: { [key: string]: any; item: item }) {
}); });
} }
function detectType(mimetype: string): uploadType { function detectType(mimetype: string): FileType {
if (mimetype.startsWith("video")) return "video"; if (mimetype.startsWith("video")) return "video";
if (mimetype.startsWith("audio")) return "audio"; if (mimetype.startsWith("audio")) return "audio";
if (mimetype.startsWith("image")) return "image"; if (mimetype.startsWith("image")) return "image";
@ -110,7 +111,7 @@ function detectType(mimetype: string): uploadType {
return "blob"; return "blob";
} }
export function handleFiles(files: file[], base: string, overwrite = false) { export function handleFiles(files: IFile[], base: string, overwrite = false) {
const uploadStore = useUploadStore(); const uploadStore = useUploadStore();
for (let i = 0; i < files.length; i++) { for (let i = 0; i < files.length; i++) {
@ -128,7 +129,7 @@ export function handleFiles(files: file[], base: string, overwrite = false) {
path += "/"; path += "/";
} }
const item = { const item: Item = {
id, id,
path, path,
file, file,

View File

@ -1,67 +0,0 @@
import Vue from "vue";
import Noty from "noty";
import VueLazyload from "vue-lazyload";
// @ts-ignore
import i18n from "@/i18n";
import { disableExternal } from "@/utils/constants";
Vue.use(VueLazyload);
Vue.config.productionTip = true;
const notyDefault = {
type: "info",
layout: "bottomRight",
timeout: 1000,
progressBar: true,
};
Vue.prototype.$noty = (opts) => {
new Noty(Object.assign({}, notyDefault, opts)).show();
};
Vue.prototype.$showSuccess = (message) => {
new Noty(
Object.assign({}, notyDefault, {
text: message,
type: "success",
})
).show();
};
Vue.prototype.$showError = (error, displayReport = true) => {
let btns = [
Noty.button(i18n.t("buttons.close"), "", function () {
n.close();
}),
];
if (!disableExternal && displayReport) {
btns.unshift(
Noty.button(i18n.t("buttons.reportIssue"), "", function () {
window.open(
"https://github.com/filebrowser/filebrowser/issues/new/choose"
);
})
);
}
let n = new Noty(
Object.assign({}, notyDefault, {
text: error.message || error,
type: "error",
timeout: null,
buttons: btns,
})
);
n.show();
};
Vue.directive("focus", {
inserted: function (el) {
el.focus();
},
});
export default Vue;

View File

@ -34,6 +34,7 @@ import {
watch, watch,
} from "vue"; } from "vue";
import { files as api } from "@/api"; import { files as api } from "@/api";
import { storeToRefs } from "pinia";
import { useFileStore } from "@/stores/file"; import { useFileStore } from "@/stores/file";
import { useLayoutStore } from "@/stores/layout"; import { useLayoutStore } from "@/stores/layout";
import { useUploadStore } from "@/stores/upload"; import { useUploadStore } from "@/stores/upload";
@ -51,6 +52,9 @@ const layoutStore = useLayoutStore();
const fileStore = useFileStore(); const fileStore = useFileStore();
const uploadStore = useUploadStore(); const uploadStore = useUploadStore();
const { reload } = storeToRefs(fileStore);
const { error: uploadError } = storeToRefs(uploadStore);
const route = useRoute(); const route = useRoute();
const { t } = useI18n({}); const { t } = useI18n({});
@ -98,14 +102,11 @@ onUnmounted(() => {
}); });
watch(route, () => fetchData()); watch(route, () => fetchData());
// @ts-ignore watch(reload, (newValue) => {
watch(fileStore.reload, (val) => { newValue && fetchData();
if (val) {
fetchData();
}
}); });
watch(uploadStore.error, (newValue, oldValue) => { watch(uploadError, (newValue) => {
newValue && newValue !== oldValue && layoutStore.showError(); newValue && layoutStore.showError();
}); });
// Define functions // Define functions

View File

@ -1,8 +1,8 @@
<template> <template>
<div> <div>
<!-- <div v-if="true" class="layoutStore.progress"> <div v-if="uploadStore.getProgress" class="progress">
<div v-bind:style="{ width: this.layoutStore.progress + '%' }"></div> <div v-bind:style="{ width: uploadStore.getProgress + '%' }"></div>
</div> --> </div>
<sidebar></sidebar> <sidebar></sidebar>
<main> <main>
<router-view></router-view> <router-view></router-view>
@ -21,6 +21,7 @@
import { useAuthStore } from "@/stores/auth"; import { useAuthStore } from "@/stores/auth";
import { useLayoutStore } from "@/stores/layout"; import { useLayoutStore } from "@/stores/layout";
import { useFileStore } from "@/stores/file"; import { useFileStore } from "@/stores/file";
import { useUploadStore } from "@/stores/upload";
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";
@ -32,6 +33,7 @@ import { useRoute } from "vue-router";
const layoutStore = useLayoutStore(); const layoutStore = useLayoutStore();
const authStore = useAuthStore(); const authStore = useAuthStore();
const fileStore = useFileStore(); const fileStore = useFileStore();
const uploadStore = useUploadStore();
const route = useRoute(); const route = useRoute();
watch(route, () => { watch(route, () => {

View File

@ -71,10 +71,7 @@ const submit = async (event: Event) => {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
let redirect = route.query.redirect; const redirect = (route.query.redirect || "/files/") as string;
if (redirect === "" || redirect === undefined || redirect === null) {
redirect = "/files/";
}
let captcha = ""; let captcha = "";
if (recaptcha) { if (recaptcha) {
@ -99,7 +96,6 @@ const submit = async (event: Event) => {
} }
await auth.login(username.value, password.value, captcha); await auth.login(username.value, password.value, captcha);
// @ts-ignore
router.push({ path: redirect }); router.push({ path: redirect });
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);

View File

@ -194,9 +194,10 @@ import Item from "@/components/files/ListingItem.vue";
import Clipboard from "clipboard"; import Clipboard from "clipboard";
import { useFileStore } from "@/stores/file"; import { useFileStore } from "@/stores/file";
import { useLayoutStore } from "@/stores/layout"; import { useLayoutStore } from "@/stores/layout";
import { computed, onBeforeUnmount, onMounted, ref, watch } from "vue"; import { computed, inject, onBeforeUnmount, onMounted, ref, watch } from "vue";
import { useRoute } from "vue-router"; import { useRoute } from "vue-router";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import type { IToastSuccess } from "@/types";
const error = ref<null | any>(null); const error = ref<null | any>(null);
const showLimit = ref<number>(100); const showLimit = ref<number>(100);
@ -206,6 +207,8 @@ const hash = ref<any>(null);
const token = ref<any>(null); const token = ref<any>(null);
const clip = ref<any>(null); const clip = ref<any>(null);
const $showSuccess = inject<IToastSuccess>("$showError")!;
const { t } = useI18n({}); const { t } = useI18n({});
const route = useRoute(); const route = useRoute();
@ -256,7 +259,7 @@ const fetchData = async () => {
fileStore.reload = false; fileStore.reload = false;
fileStore.selected = []; fileStore.selected = [];
fileStore.multiple = false; fileStore.multiple = false;
// fileStore.closeHovers(); layoutStore.closeHovers();
// Set loading to true and reset the error. // Set loading to true and reset the error.
layoutStore.loading = true; layoutStore.loading = true;
@ -324,7 +327,6 @@ const download = () => {
files.push(req.value.items[i].path); files.push(req.value.items[i].path);
} }
// @ts-ignore
api.download(format, hash.value, token.value, ...files); api.download(format, hash.value, token.value, ...files);
}, },
}); });
@ -332,8 +334,7 @@ const download = () => {
const linkSelected = () => { const linkSelected = () => {
return isSingleFile() && req.value return isSingleFile() && req.value
? // @ts-ignore ? api.getDownloadURL({
api.getDownloadURL({
hash: hash.value, hash: hash.value,
path: req.value.items[fileStore.selected[0]].path, path: req.value.items[fileStore.selected[0]].path,
}) })
@ -348,7 +349,7 @@ onMounted(async () => {
window.addEventListener("keydown", keyEvent); window.addEventListener("keydown", keyEvent);
clip.value = new Clipboard(".copy-clipboard"); clip.value = new Clipboard(".copy-clipboard");
clip.value.on("success", () => { clip.value.on("success", () => {
// $showSuccess(this.t("success.linkCopied")); $showSuccess(t("success.linkCopied"));
}); });
}); });

View File

@ -158,6 +158,7 @@
<div class="card-content"> <div class="card-content">
<p class="small">{{ t("settings.defaultUserDescription") }}</p> <p class="small">{{ t("settings.defaultUserDescription") }}</p>
<!-- TODO: idk how to fix this ts error -->
<user-form <user-form
:isNew="false" :isNew="false"
:isDefault="true" :isDefault="true"
@ -239,6 +240,13 @@ import Themes from "@/components/settings/Themes.vue";
import Errors from "@/views/Errors.vue"; import Errors from "@/views/Errors.vue";
import { computed, inject, onBeforeUnmount, onMounted, ref } from "vue"; import { computed, inject, onBeforeUnmount, onMounted, ref } from "vue";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import type {
ISettings,
IToastError,
IToastSuccess,
SettingsCommand,
SettingsUnit,
} from "@/types";
const error = ref<any>(null); const error = ref<any>(null);
const originalSettings = ref<ISettings | null>(null); const originalSettings = ref<ISettings | null>(null);
@ -246,12 +254,12 @@ const settings = ref<ISettings | null>(null);
const debounceTimeout = ref<number | null>(null); const debounceTimeout = ref<number | null>(null);
const commandObject = ref<{ const commandObject = ref<{
[key in keyof SettingsCommand]: string; [key in keyof SettingsCommand]: string[] | string;
}>({}); }>({});
const shellValue = ref<string>(""); const shellValue = ref<string>("");
const $showError = inject<TToast>("$showError") as TToast; const $showError = inject<IToastError>("$showError")!;
const $showSuccess = inject<TToast>("$showSuccess") as TToast; const $showSuccess = inject<IToastSuccess>("$showSuccess")!;
const { t } = useI18n(); const { t } = useI18n();
@ -304,6 +312,7 @@ const save = async () => {
commands: {}, commands: {},
}; };
<<<<<<< HEAD
// @ts-ignore // @ts-ignore
for (const name of Object.keys(settings.value.commands)) { for (const name of Object.keys(settings.value.commands)) {
// @ts-ignore // @ts-ignore
@ -317,6 +326,22 @@ const save = async () => {
} else { } else {
// @ts-ignore // @ts-ignore
newSettings.commands[name] = newValue; newSettings.commands[name] = newValue;
=======
const keys = Object.keys(settings.value.commands) as Array<
keyof SettingsCommand
>;
for (const key of keys) {
// not sure if we can safely assert non-null
const newValue = commandObject.value[key];
if (!newValue) continue;
if (Array.isArray(newValue)) {
newSettings.commands[key] = newValue;
} else if (key in commandObject.value) {
newSettings.commands[key] = newValue
.split("\n")
.filter((cmd: string) => cmd !== "");
>>>>>>> kloon15/vue3
} }
} }
newSettings.shell = shellValue.value.split("\n"); newSettings.shell = shellValue.value.split("\n");
@ -371,16 +396,20 @@ onMounted(async () => {
const original: ISettings = await api.get(); const original: ISettings = await api.get();
let newSettings: ISettings = { ...original, commands: {} }; let newSettings: ISettings = { ...original, commands: {} };
for (const key in original.commands) { const keys = Object.keys(original.commands) as Array<keyof SettingsCommand>;
// @ts-ignore for (const key in keys) {
//@ts-ignore
newSettings.commands[key] = original.commands[key]; newSettings.commands[key] = original.commands[key];
// @ts-ignore //@ts-ignore
commandObject.value[key] = original.commands[key].join("\n"); commandObject.value[key] = original.commands[key].join("\n");
} }
originalSettings.value = original; originalSettings.value = original;
settings.value = newSettings; settings.value = newSettings;
<<<<<<< HEAD
// @ts-ignore // @ts-ignore
=======
>>>>>>> kloon15/vue3
shellValue.value = newSettings.shell.join("\n"); shellValue.value = newSettings.shell.join("\n");
} catch (e) { } catch (e) {
error.value = e; error.value = e;

View File

@ -83,13 +83,22 @@ import Languages from "@/components/settings/Languages.vue";
// import i18n, { rtlLanguages } from "@/i18n"; // import i18n, { rtlLanguages } from "@/i18n";
import { inject, onMounted, ref } from "vue"; import { inject, onMounted, ref } from "vue";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
<<<<<<< HEAD
=======
import type { IToastError, IToastSuccess } from "@/types";
>>>>>>> kloon15/vue3
const layoutStore = useLayoutStore(); const layoutStore = useLayoutStore();
const authStore = useAuthStore(); const authStore = useAuthStore();
const { t } = useI18n(); const { t } = useI18n();
<<<<<<< HEAD
const $showError = inject("$showError") as TToast; const $showError = inject("$showError") as TToast;
const $showSuccess = inject("$showSuccess") as TToast; const $showSuccess = inject("$showSuccess") as TToast;
=======
const $showError = inject<IToastError>("$showError")!;
const $showSuccess = inject<IToastSuccess>("$showSuccess")!;
>>>>>>> kloon15/vue3
const password = ref<string>(""); const password = ref<string>("");
const passwordConf = ref<string>(""); const passwordConf = ref<string>("");

View File

@ -71,9 +71,16 @@ import Clipboard from "clipboard";
import Errors from "@/views/Errors.vue"; import Errors from "@/views/Errors.vue";
import { inject, onBeforeUnmount, ref, onMounted } from "vue"; import { inject, onBeforeUnmount, ref, onMounted } from "vue";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
<<<<<<< HEAD
const $showError = inject("$showError") as TToast; const $showError = inject("$showError") as TToast;
const $showSuccess = inject("$showSuccess") as TToast; const $showSuccess = inject("$showSuccess") as TToast;
=======
import type { IShare, IToastError, IToastSuccess } from "@/types";
const $showError = inject<IToastError>("$showError")!;
const $showSuccess = inject<IToastSuccess>("$showSuccess")!;
>>>>>>> kloon15/vue3
const { t } = useI18n(); const { t } = useI18n();
const layoutStore = useLayoutStore(); const layoutStore = useLayoutStore();

View File

@ -75,19 +75,31 @@ 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";
<<<<<<< HEAD
// @ts-ignore // @ts-ignore
import { cloneDeep } from "lodash-es"; import { cloneDeep } from "lodash-es";
import { computed, inject, onMounted, ref, watch } from "vue"; import { computed, inject, onMounted, ref, watch } from "vue";
import { useRoute, useRouter } from "vue-router"; import { useRoute, useRouter } from "vue-router";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
=======
import { computed, inject, onMounted, ref, watch } from "vue";
import { useRoute, useRouter } from "vue-router";
import { useI18n } from "vue-i18n";
import type { IToastError, IToastSuccess, IUser } from "@/types";
>>>>>>> kloon15/vue3
const error = ref<any | null>(null); const error = ref<any | null>(null);
const originalUser = ref<IUser | null>(null); const originalUser = ref<IUser | null>(null);
const user = ref<IUser | null>(null); const user = ref<IUser | null>(null);
const createUserDir = ref<boolean>(false); const createUserDir = ref<boolean>(false);
<<<<<<< HEAD
const $showError = inject("$showError") as TToast; const $showError = inject("$showError") as TToast;
const $showSuccess = inject("$showSuccess") as TToast; const $showSuccess = inject("$showSuccess") as TToast;
=======
const $showError = inject<IToastError>("$showError")!;
const $showSuccess = inject<IToastSuccess>("$showSuccess")!;
>>>>>>> kloon15/vue3
const authStore = useAuthStore(); const authStore = useAuthStore();
const layoutStore = useLayoutStore(); const layoutStore = useLayoutStore();
@ -121,6 +133,8 @@ const fetchData = async () => {
rules: [], rules: [],
lockPassword: false, lockPassword: false,
id: 0, id: 0,
<<<<<<< HEAD
=======
}; };
} else { } else {
const id = Array.isArray(route.params.id) const id = Array.isArray(route.params.id)
@ -137,6 +151,48 @@ const fetchData = async () => {
const deletePrompt = () => layoutStore.showHover("deleteUser"); const deletePrompt = () => layoutStore.showHover("deleteUser");
const deleteUser = async (e: Event) => {
e.preventDefault();
if (user.value === null) {
return false;
}
try {
await api.remove(user.value.id);
router.push({ path: "/settings/users" });
$showSuccess(t("settings.userDeleted"));
} catch (e: any) {
e.message === "403" ? $showError(t("errors.forbidden")) : $showError(e);
}
};
const save = async (event: Event) => {
event.preventDefault();
if (originalUser.value === null || user.value === null) {
return false;
}
try {
if (isNew.value) {
const newUser: IUser = {
...originalUser.value,
...user.value,
>>>>>>> kloon15/vue3
};
} else {
const id = Array.isArray(route.params.id)
? route.params.id.join("")
: route.params.id;
user.value = { ...(await api.get(parseInt(id))) };
}
} catch (e) {
error.value = e;
} finally {
layoutStore.loading = false;
}
};
<<<<<<< HEAD
const deletePrompt = () => layoutStore.showHover("deleteUser");
const deleteUser = async (e: Event) => { const deleteUser = async (e: Event) => {
e.preventDefault(); e.preventDefault();
if (user.value === null) { if (user.value === null) {
@ -170,6 +226,16 @@ const save = async (event: Event) => {
if (user.value.id === authStore.user?.id) { if (user.value.id === authStore.user?.id) {
authStore.setUser({ ...cloneDeep(user) }); authStore.setUser({ ...cloneDeep(user) });
=======
const loc = (await api.create(newUser)) as string;
router.push({ path: loc });
$showSuccess(t("settings.userCreated"));
} else {
await api.update(user);
if (user.value.id === authStore.user?.id) {
authStore.updateUser(user.value);
>>>>>>> kloon15/vue3
} }
$showSuccess(t("settings.userUpdated")); $showSuccess(t("settings.userUpdated"));

View File

@ -47,6 +47,10 @@ import { users as api } from "@/api";
import Errors from "@/views/Errors.vue"; import Errors from "@/views/Errors.vue";
import { onMounted, ref } from "vue"; import { onMounted, ref } from "vue";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
<<<<<<< HEAD
=======
import type { IUser } from "@/types";
>>>>>>> kloon15/vue3
const error = ref<any>(null); const error = ref<any>(null);
const users = ref<IUser[]>([]); const users = ref<IUser[]>([]);

View File

@ -5,26 +5,19 @@
"target": "ESNext", "target": "ESNext",
"useDefineForClassFields": true, "useDefineForClassFields": true,
"module": "ESNext", "module": "ESNext",
"moduleResolution": "Node", "moduleResolution": "Node10",
"strict": true, "strict": true,
"jsx": "preserve",
"sourceMap": true, "sourceMap": true,
"outDir": "../dist/frontend",
"resolveJsonModule": true, "resolveJsonModule": true,
"isolatedModules": true, "isolatedModules": true,
"esModuleInterop": true, "esModuleInterop": true,
"lib": ["ESNext", "DOM"], "lib": ["ESNext", "DOM"],
"skipLibCheck": true, "skipLibCheck": true,
"types": ["vite/client"], "types": ["vite/client", "@intlify/unplugin-vue-i18n/messages"],
"typeRoots": ["./node_modules/@types", "./some-custom-lib"],
"paths": { "paths": {
"@/*": ["./src/*"] "@/*": ["./src/*"]
} }
}, },
"include": [ "include": ["src/**/*.ts", "src/**/*.vue", "src/i18n/index.ts.bak"],
"./**/*.ts",
"src/**/*.d.ts",
"src/**/*.vue",
],
"exclude": ["node_modules", "dist"] "exclude": ["node_modules", "dist"]
} }

View File

@ -8,7 +8,9 @@ import pluginRewriteAll from "vite-plugin-rewrite-all";
const plugins = [ const plugins = [
vue(), vue(),
VueI18nPlugin({}), VueI18nPlugin({
include: [path.resolve(__dirname, "./src/i18n/**.json")],
}),
legacy({ legacy({
// defaults already drop IE support // defaults already drop IE support
targets: ["defaults"], targets: ["defaults"],