feat(web):add fetch button & modal

pull/3834/head
banbxio 2025-03-27 01:40:45 +08:00
parent 7da134980e
commit 11ef778381
5 changed files with 170 additions and 17 deletions

View File

@ -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;
}

View File

@ -3,7 +3,8 @@ import * as share from "./share";
import * as users from "./users";
import * as settings from "./settings";
import * as pub from "./pub";
import * as fetcher from "./fetch.tsx";
import search from "./search";
import commands from "./commands";
export { files, share, users, settings, pub, commands, search };
export { files, share, users, settings, pub, fetcher, commands, search };

View File

@ -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>

View File

@ -25,6 +25,7 @@ import Share from "./Share.vue";
import ShareDelete from "./ShareDelete.vue";
import Upload from "./Upload.vue";
import DiscardEditorChanges from "./DiscardEditorChanges.vue";
import Fetch from "@/components/prompts/Fetch.vue";
const layoutStore = useLayoutStore();
@ -47,6 +48,7 @@ const components = new Map<string, any>([
["share-delete", ShareDelete],
["deleteUser", DeleteUser],
["discardEditorChanges", DiscardEditorChanges],
["fetch", Fetch],
]);
watch(currentPromptName, (newValue) => {

View File

@ -58,6 +58,12 @@
:label="t('buttons.switchView')"
@action="switchView"
/>
<action
v-if="headerButtons.fetch"
icon="cloud_download"
:label="t('buttons.fetch')"
@action="fetch"
/>
<action
v-if="headerButtons.download"
icon="file_download"
@ -281,7 +287,7 @@ import { useClipboardStore } from "@/stores/clipboard";
import { useFileStore } from "@/stores/file";
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 * as upload from "@/utils/upload";
import css from "@/utils/css";
@ -292,15 +298,7 @@ import HeaderBar from "@/components/header/HeaderBar.vue";
import Action from "@/components/header/Action.vue";
import Search from "@/components/Search.vue";
import Item from "@/components/files/ListingItem.vue";
import {
computed,
inject,
nextTick,
onBeforeUnmount,
onMounted,
ref,
watch,
} from "vue";
import { computed, inject, nextTick, onBeforeUnmount, onMounted, ref, watch } from "vue";
import { useRoute } from "vue-router";
import { useI18n } from "vue-i18n";
import { storeToRefs } from "pinia";
@ -327,19 +325,19 @@ const { t } = useI18n();
const listing = ref<HTMLElement | null>(null);
const nameSorted = computed(() =>
fileStore.req ? fileStore.req.sorting.by === "name" : false
fileStore.req ? fileStore.req.sorting.by === "name" : false,
);
const sizeSorted = computed(() =>
fileStore.req ? fileStore.req.sorting.by === "size" : false
fileStore.req ? fileStore.req.sorting.by === "size" : false,
);
const modifiedSorted = computed(() =>
fileStore.req ? fileStore.req.sorting.by === "modified" : false
fileStore.req ? fileStore.req.sorting.by === "modified" : false,
);
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));
@ -412,6 +410,7 @@ const headerButtons = computed(() => {
share: fileStore.selectedCount === 1 && authStore.user?.perm.share,
move: fileStore.selectedCount > 0 && authStore.user?.perm.rename,
copy: fileStore.selectedCount > 0 && authStore.user?.perm.create,
fetch: authStore.user?.perm.create,
};
});
@ -644,7 +643,7 @@ const colunmsResize = () => {
if (items_ === null) return;
let columns = Math.floor(
(document.querySelector("main")?.offsetWidth ?? 0) / columnWidth.value
(document.querySelector("main")?.offsetWidth ?? 0) / columnWidth.value,
);
if (columns === 0) columns = 1;
items_.style.width = `calc(${100 / columns}% - 1em)`;
@ -854,6 +853,16 @@ const windowsResize = throttle(() => {
fillWindow();
}, 100);
const fetch = () => {
layoutStore.showHover({
prompt: "fetch",
confirm: (format: any) => {
layoutStore.closeHovers();
},
});
};
const download = () => {
if (fileStore.req === null) return;
@ -942,7 +951,7 @@ const fillWindow = (fit = false) => {
// Quantity of items needed to fill 2x of the window height
const showQuantity = Math.ceil(
(windowHeight + windowHeight * 2) / itemWeight.value
(windowHeight + windowHeight * 2) / itemWeight.value,
);
// Less items to display than current