feat(web):add fetch button & modal
parent
7da134980e
commit
11ef778381
|
@ -0,0 +1,21 @@
|
||||||
|
import { fetchURL, removePrefix } from "@/api/utils.ts";
|
||||||
|
|
||||||
|
export async function fetchUrlFile(
|
||||||
|
savePath: string,
|
||||||
|
saveName: string,
|
||||||
|
fetchUrl: string,
|
||||||
|
opts: ApiOpts
|
||||||
|
) {
|
||||||
|
const res = await fetchURL(`/api/download/`, {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify({
|
||||||
|
url: fetchUrl,
|
||||||
|
pathname: removePrefix(savePath),
|
||||||
|
filename: saveName,
|
||||||
|
}),
|
||||||
|
...opts,
|
||||||
|
});
|
||||||
|
const taskID = await res.text();
|
||||||
|
console.log("on create download task: ", taskID);
|
||||||
|
return taskID;
|
||||||
|
}
|
|
@ -3,7 +3,8 @@ import * as share from "./share";
|
||||||
import * as users from "./users";
|
import * as users from "./users";
|
||||||
import * as settings from "./settings";
|
import * as settings from "./settings";
|
||||||
import * as pub from "./pub";
|
import * as pub from "./pub";
|
||||||
|
import * as fetcher from "./fetch.tsx";
|
||||||
import search from "./search";
|
import search from "./search";
|
||||||
import commands from "./commands";
|
import commands from "./commands";
|
||||||
|
|
||||||
export { files, share, users, settings, pub, commands, search };
|
export { files, share, users, settings, pub, fetcher, commands, search };
|
||||||
|
|
|
@ -0,0 +1,120 @@
|
||||||
|
<template>
|
||||||
|
<div class="card floating" id="fetch">
|
||||||
|
<div class="card-title">
|
||||||
|
<h2>{{ t("prompts.fetch") }}</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-content">
|
||||||
|
<p>{{ t("prompts.fetchUrl") }}</p>
|
||||||
|
<input
|
||||||
|
id="focus-prompt"
|
||||||
|
class="input input--block"
|
||||||
|
type="text"
|
||||||
|
@keyup.enter="submit"
|
||||||
|
v-model.trim="fetchUrl"
|
||||||
|
tabindex="1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-content">
|
||||||
|
<p>{{ t("prompts.fetchSaveName") }}</p>
|
||||||
|
<input
|
||||||
|
class="input input--block"
|
||||||
|
type="text"
|
||||||
|
@keyup.enter="submit"
|
||||||
|
v-model.trim="saveName"
|
||||||
|
tabindex="2"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-action">
|
||||||
|
<button
|
||||||
|
class="button button--flat button--grey"
|
||||||
|
@click="layoutStore.closeHovers"
|
||||||
|
:aria-label="t('buttons.cancel')"
|
||||||
|
:title="t('buttons.cancel')"
|
||||||
|
tabindex="3"
|
||||||
|
>
|
||||||
|
{{ t("buttons.cancel") }}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="button button--flat"
|
||||||
|
:aria-label="t('buttons.create')"
|
||||||
|
:title="t('buttons.create')"
|
||||||
|
@click="submit"
|
||||||
|
tabindex="2"
|
||||||
|
>
|
||||||
|
{{ t("buttons.create") }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useLayoutStore } from "@/stores/layout.ts";
|
||||||
|
import { useFileStore } from "@/stores/file.ts";
|
||||||
|
import { useI18n } from "vue-i18n";
|
||||||
|
import { inject, ref, watch } from "vue";
|
||||||
|
import url from "@/utils/url.ts";
|
||||||
|
import { fetcher as api } from "@/api";
|
||||||
|
import { useRoute } from "vue-router";
|
||||||
|
|
||||||
|
const $showError = inject<IToastError>("$showError")!;
|
||||||
|
|
||||||
|
const layoutStore = useLayoutStore();
|
||||||
|
const fileStore = useFileStore();
|
||||||
|
const route = useRoute();
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const fetchUrl = ref<string>("");
|
||||||
|
const saveName = ref<string>("");
|
||||||
|
const taskID = ref<string>("");
|
||||||
|
|
||||||
|
watch(fetchUrl, (value) => {
|
||||||
|
try {
|
||||||
|
if (saveName.value !== "") return;
|
||||||
|
if (!(value.startsWith("http://") || value.startsWith("https://"))) return;
|
||||||
|
saveName.value = new URL(value).pathname.split("/").pop() ?? "";
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
} catch (e) {}
|
||||||
|
});
|
||||||
|
|
||||||
|
const submit = async (event: Event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
if (
|
||||||
|
!(
|
||||||
|
fetchUrl.value.startsWith("http://") ||
|
||||||
|
fetchUrl.value.startsWith("https://")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return;
|
||||||
|
if (saveName.value === "") return;
|
||||||
|
|
||||||
|
// Build the path of the new directory.
|
||||||
|
let uri = fileStore.isFiles ? route.path + "/" : "/";
|
||||||
|
|
||||||
|
if (!fileStore.isListing) {
|
||||||
|
uri = url.removeLastDir(uri) + "/";
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const createdTaskID = await api.fetchUrlFile(
|
||||||
|
uri,
|
||||||
|
saveName.value,
|
||||||
|
fetchUrl.value,
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
if (createdTaskID) {
|
||||||
|
taskID.value = createdTaskID;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof Error) {
|
||||||
|
$showError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
layoutStore.closeHovers();
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped></style>
|
|
@ -25,6 +25,7 @@ import Share from "./Share.vue";
|
||||||
import ShareDelete from "./ShareDelete.vue";
|
import ShareDelete from "./ShareDelete.vue";
|
||||||
import Upload from "./Upload.vue";
|
import Upload from "./Upload.vue";
|
||||||
import DiscardEditorChanges from "./DiscardEditorChanges.vue";
|
import DiscardEditorChanges from "./DiscardEditorChanges.vue";
|
||||||
|
import Fetch from "@/components/prompts/Fetch.vue";
|
||||||
|
|
||||||
const layoutStore = useLayoutStore();
|
const layoutStore = useLayoutStore();
|
||||||
|
|
||||||
|
@ -47,6 +48,7 @@ const components = new Map<string, any>([
|
||||||
["share-delete", ShareDelete],
|
["share-delete", ShareDelete],
|
||||||
["deleteUser", DeleteUser],
|
["deleteUser", DeleteUser],
|
||||||
["discardEditorChanges", DiscardEditorChanges],
|
["discardEditorChanges", DiscardEditorChanges],
|
||||||
|
["fetch", Fetch],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
watch(currentPromptName, (newValue) => {
|
watch(currentPromptName, (newValue) => {
|
||||||
|
|
|
@ -58,6 +58,12 @@
|
||||||
:label="t('buttons.switchView')"
|
:label="t('buttons.switchView')"
|
||||||
@action="switchView"
|
@action="switchView"
|
||||||
/>
|
/>
|
||||||
|
<action
|
||||||
|
v-if="headerButtons.fetch"
|
||||||
|
icon="cloud_download"
|
||||||
|
:label="t('buttons.fetch')"
|
||||||
|
@action="fetch"
|
||||||
|
/>
|
||||||
<action
|
<action
|
||||||
v-if="headerButtons.download"
|
v-if="headerButtons.download"
|
||||||
icon="file_download"
|
icon="file_download"
|
||||||
|
@ -281,7 +287,7 @@ import { useClipboardStore } from "@/stores/clipboard";
|
||||||
import { useFileStore } from "@/stores/file";
|
import { useFileStore } from "@/stores/file";
|
||||||
import { useLayoutStore } from "@/stores/layout";
|
import { useLayoutStore } from "@/stores/layout";
|
||||||
|
|
||||||
import { users, files as api } from "@/api";
|
import { files as api, users } 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";
|
||||||
|
@ -292,15 +298,7 @@ import HeaderBar from "@/components/header/HeaderBar.vue";
|
||||||
import Action from "@/components/header/Action.vue";
|
import Action from "@/components/header/Action.vue";
|
||||||
import Search from "@/components/Search.vue";
|
import Search from "@/components/Search.vue";
|
||||||
import Item from "@/components/files/ListingItem.vue";
|
import Item from "@/components/files/ListingItem.vue";
|
||||||
import {
|
import { computed, inject, nextTick, onBeforeUnmount, onMounted, ref, watch } from "vue";
|
||||||
computed,
|
|
||||||
inject,
|
|
||||||
nextTick,
|
|
||||||
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 { storeToRefs } from "pinia";
|
import { storeToRefs } from "pinia";
|
||||||
|
@ -327,19 +325,19 @@ const { t } = useI18n();
|
||||||
const listing = ref<HTMLElement | null>(null);
|
const listing = ref<HTMLElement | null>(null);
|
||||||
|
|
||||||
const nameSorted = computed(() =>
|
const nameSorted = computed(() =>
|
||||||
fileStore.req ? fileStore.req.sorting.by === "name" : false
|
fileStore.req ? fileStore.req.sorting.by === "name" : false,
|
||||||
);
|
);
|
||||||
|
|
||||||
const sizeSorted = computed(() =>
|
const sizeSorted = computed(() =>
|
||||||
fileStore.req ? fileStore.req.sorting.by === "size" : false
|
fileStore.req ? fileStore.req.sorting.by === "size" : false,
|
||||||
);
|
);
|
||||||
|
|
||||||
const modifiedSorted = computed(() =>
|
const modifiedSorted = computed(() =>
|
||||||
fileStore.req ? fileStore.req.sorting.by === "modified" : false
|
fileStore.req ? fileStore.req.sorting.by === "modified" : false,
|
||||||
);
|
);
|
||||||
|
|
||||||
const ascOrdered = computed(() =>
|
const ascOrdered = computed(() =>
|
||||||
fileStore.req ? fileStore.req.sorting.asc : false
|
fileStore.req ? fileStore.req.sorting.asc : false,
|
||||||
);
|
);
|
||||||
|
|
||||||
const dirs = computed(() => items.value.dirs.slice(0, showLimit.value));
|
const dirs = computed(() => items.value.dirs.slice(0, showLimit.value));
|
||||||
|
@ -412,6 +410,7 @@ const headerButtons = computed(() => {
|
||||||
share: fileStore.selectedCount === 1 && authStore.user?.perm.share,
|
share: fileStore.selectedCount === 1 && authStore.user?.perm.share,
|
||||||
move: fileStore.selectedCount > 0 && authStore.user?.perm.rename,
|
move: fileStore.selectedCount > 0 && authStore.user?.perm.rename,
|
||||||
copy: fileStore.selectedCount > 0 && authStore.user?.perm.create,
|
copy: fileStore.selectedCount > 0 && authStore.user?.perm.create,
|
||||||
|
fetch: authStore.user?.perm.create,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -644,7 +643,7 @@ const colunmsResize = () => {
|
||||||
if (items_ === null) return;
|
if (items_ === null) return;
|
||||||
|
|
||||||
let columns = Math.floor(
|
let columns = Math.floor(
|
||||||
(document.querySelector("main")?.offsetWidth ?? 0) / columnWidth.value
|
(document.querySelector("main")?.offsetWidth ?? 0) / columnWidth.value,
|
||||||
);
|
);
|
||||||
if (columns === 0) columns = 1;
|
if (columns === 0) columns = 1;
|
||||||
items_.style.width = `calc(${100 / columns}% - 1em)`;
|
items_.style.width = `calc(${100 / columns}% - 1em)`;
|
||||||
|
@ -854,6 +853,16 @@ const windowsResize = throttle(() => {
|
||||||
fillWindow();
|
fillWindow();
|
||||||
}, 100);
|
}, 100);
|
||||||
|
|
||||||
|
const fetch = () => {
|
||||||
|
layoutStore.showHover({
|
||||||
|
prompt: "fetch",
|
||||||
|
confirm: (format: any) => {
|
||||||
|
layoutStore.closeHovers();
|
||||||
|
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const download = () => {
|
const download = () => {
|
||||||
if (fileStore.req === null) return;
|
if (fileStore.req === null) return;
|
||||||
|
|
||||||
|
@ -942,7 +951,7 @@ const fillWindow = (fit = false) => {
|
||||||
|
|
||||||
// Quantity of items needed to fill 2x of the window height
|
// Quantity of items needed to fill 2x of the window height
|
||||||
const showQuantity = Math.ceil(
|
const showQuantity = Math.ceil(
|
||||||
(windowHeight + windowHeight * 2) / itemWeight.value
|
(windowHeight + windowHeight * 2) / itemWeight.value,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Less items to display than current
|
// Less items to display than current
|
||||||
|
|
Loading…
Reference in New Issue