diff --git a/.dockerignore b/.dockerignore index d1f98f1b..c8e50a27 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,4 +1,5 @@ * !docker/* +!healthcheck.sh !docker_config.json !filebrowser \ No newline at end of file diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index ab601df6..c504686a 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -95,6 +95,6 @@ jobs: uses: goreleaser/goreleaser-action@v2 with: version: latest - args: release --rm-dist + args: release --clean env: GITHUB_TOKEN: ${{ secrets.GH_PAT }} diff --git a/.goreleaser.yml b/.goreleaser.yml index 4642d939..d500f967 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -3,33 +3,33 @@ project_name: filebrowser env: - GO111MODULE=on -build: - env: - - CGO_ENABLED=0 - ldflags: - - -s -w -X github.com/filebrowser/filebrowser/v2/version.Version={{ .Version }} -X github.com/filebrowser/filebrowser/v2/version.CommitSHA={{ .ShortCommit }} - main: main.go - binary: filebrowser - goos: - - darwin - - linux - - windows - - freebsd - goarch: - - amd64 - - 386 - - arm - - arm64 - - riscv64 - goarm: - - 5 - - 6 - - 7 - ignore: - - goos: darwin - goarch: 386 - - goos: freebsd - goarch: arm +builds: + - env: + - CGO_ENABLED=0 + ldflags: + - -s -w -X github.com/filebrowser/filebrowser/v2/version.Version={{ .Version }} -X github.com/filebrowser/filebrowser/v2/version.CommitSHA={{ .ShortCommit }} + main: main.go + binary: filebrowser + goos: + - darwin + - linux + - windows + - freebsd + goarch: + - amd64 + - 386 + - arm + - arm64 + - riscv64 + goarm: + - 5 + - 6 + - 7 + ignore: + - goos: darwin + goarch: 386 + - goos: freebsd + goarch: arm archives: - @@ -186,7 +186,7 @@ docker_manifests: - "filebrowser/filebrowser:v{{ .Major }}-arm64-s6" brews: - name: filebrowser - tap: + repository: owner: filebrowser name: homebrew-tap folder: Formula diff --git a/CHANGELOG.md b/CHANGELOG.md index 43178270..1c620c20 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,65 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [2.26.0](https://github.com/filebrowser/filebrowser/compare/v2.25.0...v2.26.0) (2023-11-02) + + +### Features + +* add modern greek translation ([#2778](https://github.com/filebrowser/filebrowser/issues/2778)) ([c3079d3](https://github.com/filebrowser/filebrowser/commit/c3079d30e22385d7e677f172324cd9cbab6487ce)) +* make user session timeout configurable ([#2753](https://github.com/filebrowser/filebrowser/issues/2753)) ([7fabadc](https://github.com/filebrowser/filebrowser/commit/7fabadc871ea91ea22fe9454e2ca4b33e5c211be)) + + +### Bug Fixes + +* avoid the front-end calling api/renew loop ([#2792](https://github.com/filebrowser/filebrowser/issues/2792)) ([edd808f](https://github.com/filebrowser/filebrowser/commit/edd808f124f4ada99bcbe4bca98ddbe20e5a424c)) +* disable static resource files listing ([da1fe7c](https://github.com/filebrowser/filebrowser/commit/da1fe7c9d76a9c6a25bfa19ebd6cf8023eff5d62)) +* display file size as base 2 (KiB instead of KB) ([#2779](https://github.com/filebrowser/filebrowser/issues/2779)) ([cdcd9a3](https://github.com/filebrowser/filebrowser/commit/cdcd9a313aa50c2e6806a182b6838462d42dcafe)) +* goreleaser yaml ([4d0a68e](https://github.com/filebrowser/filebrowser/commit/4d0a68e7875274f4c939f2bfa15739a9b0ecf70a)) +* revert fetchURL changes in auth (Fixes [#2729](https://github.com/filebrowser/filebrowser/issues/2729)) ([#2739](https://github.com/filebrowser/filebrowser/issues/2739)) ([bd3c194](https://github.com/filebrowser/filebrowser/commit/bd3c1941ff8289a5dae877e08f7e25fa9b2a92c5)) +* solve docker build failed issue ([#2797](https://github.com/filebrowser/filebrowser/issues/2797)) ([6a31af6](https://github.com/filebrowser/filebrowser/commit/6a31af6c0a144128af865d802c8039fa5250e946)) + + +### Build + +* **deps-dev:** bump postcss from 8.4.27 to 8.4.31 in /frontend ([#2749](https://github.com/filebrowser/filebrowser/issues/2749)) ([21d361a](https://github.com/filebrowser/filebrowser/commit/21d361ad308d109d2a6b323597019aaa09ce1781)) +* **deps:** bump @babel/traverse in /frontend ([#2775](https://github.com/filebrowser/filebrowser/issues/2775)) ([bb4bb50](https://github.com/filebrowser/filebrowser/commit/bb4bb508a9d71516e8fa80b3a6285fe002a059d2)) +* **deps:** bump golang.org/x/image from 0.5.0 to 0.10.0 ([#2800](https://github.com/filebrowser/filebrowser/issues/2800)) ([a744bd2](https://github.com/filebrowser/filebrowser/commit/a744bd224f0ff1efc53ab94481fa76ef68788df1)) +* **deps:** bump golang.org/x/net from 0.11.0 to 0.17.0 ([#2758](https://github.com/filebrowser/filebrowser/issues/2758)) ([d574fb6](https://github.com/filebrowser/filebrowser/commit/d574fb6d1af41ec31778b0f402674e5111a7875d)) +* fix deprecated goreleaser config options ([38f7788](https://github.com/filebrowser/filebrowser/commit/38f77882559133b9ff330cfb955a9d4ea4728cf8)) + +## [2.25.0](https://github.com/filebrowser/filebrowser/compare/v2.24.2...v2.25.0) (2023-09-14) + + +### Features + +* add new folder button to move/create dialogs ([#2667](https://github.com/filebrowser/filebrowser/issues/2667)) ([5994224](https://github.com/filebrowser/filebrowser/commit/599422446849fa37d5ab448bbf464afb7304b99d)) +* added shell resizing ([#2648](https://github.com/filebrowser/filebrowser/issues/2648)) ([584b706](https://github.com/filebrowser/filebrowser/commit/584b706b1e310297acc2580c60442ff5c11ae432)) +* implement abort upload functionality ([#2673](https://github.com/filebrowser/filebrowser/issues/2673)) ([a404fb0](https://github.com/filebrowser/filebrowser/commit/a404fb043da2573bf04385863b2d34b1f918b8e1)) +* implement upload speed calculation and ETA estimation ([#2677](https://github.com/filebrowser/filebrowser/issues/2677)) ([ecdd684](https://github.com/filebrowser/filebrowser/commit/ecdd684bf1d537a4591caa38348102b61dd51e5d)) + + +### Bug Fixes + +* refactor path resolution logic for project root ([#2674](https://github.com/filebrowser/filebrowser/issues/2674)) ([95fec7f](https://github.com/filebrowser/filebrowser/commit/95fec7f69430c108e5cf95c428db9d671cd97a94)) +* tus upload with cloudflare proxy ([36af01d](https://github.com/filebrowser/filebrowser/commit/36af01daa6e04005ce3d18985eebaeef06f7393d)), closes [#2593](https://github.com/filebrowser/filebrowser/issues/2593) + + +### Refactorings + +* migrate frontend tooling to vite 4 ([#2645](https://github.com/filebrowser/filebrowser/issues/2645)) ([8838a09](https://github.com/filebrowser/filebrowser/commit/8838a09cf5104deac22b6143050588040c6825e6)) + + +### Build + +* bump go version to 1.21.0 ([#2672](https://github.com/filebrowser/filebrowser/issues/2672)) ([2c97573](https://github.com/filebrowser/filebrowser/commit/2c97573301a1b13179678fb7f9bd8316539ecdff)) +* bump node version to 18 ([#2671](https://github.com/filebrowser/filebrowser/issues/2671)) ([70eba7e](https://github.com/filebrowser/filebrowser/commit/70eba7ecc9d19545c0899ae40eb3897a7c48562f)) + + +### Performance improvements + +* **backend:** optimize subtitles detection performance ([#2637](https://github.com/filebrowser/filebrowser/issues/2637)) ([374bbd3](https://github.com/filebrowser/filebrowser/commit/374bbd3ec199fddbe491ab2b74e520a10a73e54b)) + ### [2.24.2](https://github.com/filebrowser/filebrowser/compare/v2.24.1...v2.24.2) (2023-08-08) diff --git a/README.md b/README.md index 7e39024d..23afecfc 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,12 @@ filebrowser provides a file managing interface within a specified directory and it can be used to upload, delete, preview, rename and edit your files. It allows the creation of multiple users and each user can have its own directory. It can be used as a standalone app. +## Demo + +url: https://demo.filebrowser.org/ + +credentials: `demo`/`demo` + ## Features Please refer to our docs at [https://filebrowser.org/features](https://filebrowser.org/features) diff --git a/cmd/root.go b/cmd/root.go index dc8b57e8..7ec4d441 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -64,6 +64,7 @@ func addServerFlags(flags *pflag.FlagSet) { flags.Uint32("socket-perm", 0666, "unix socket file permissions") //nolint:gomnd flags.StringP("baseurl", "b", "", "base url") flags.String("cache-dir", "", "file cache directory (disabled if empty)") + flags.String("token-expiration-time", "2h", "user session timeout") flags.Int("img-processors", 4, "image processors count") //nolint:gomnd flags.Bool("disable-thumbnails", false, "disable image thumbnails") flags.Bool("disable-preview-resize", false, "disable resize of image previews") @@ -261,6 +262,10 @@ func getRunParams(flags *pflag.FlagSet, st *storage.Storage) *settings.Server { _, disableExec := getParamB(flags, "disable-exec") server.EnableExec = !disableExec + if val, set := getParamB(flags, "token-expiration-time"); set { + server.TokenExpirationTime = val + } + return server } diff --git a/files/file.go b/files/file.go index 4fd00062..bb9690d4 100644 --- a/files/file.go +++ b/files/file.go @@ -7,6 +7,7 @@ import ( "crypto/sha512" "encoding/hex" "hash" + "image" "io" "io/fs" "log" @@ -51,6 +52,7 @@ type FileInfo struct { Checksums map[string]string `json:"checksums,omitempty"` Token string `json:"token,omitempty"` currentDir []os.FileInfo `json:"-"` + Resolution *ImageResolution `json:"resolution,omitempty"` } // FileOptions are the options when getting a file info. @@ -65,6 +67,11 @@ type FileOptions struct { Content bool } +type ImageResolution struct { + Width int `json:"width"` + Height int `json:"height"` +} + // NewFileInfo creates a File object from a path and a given user. This File // object will be automatically filled depending on if it is a directory // or a file. If it's a video file, it will also detect any subtitles. @@ -243,6 +250,12 @@ func (i *FileInfo) detectType(modify, saveContent, readHeader bool) error { return nil case strings.HasPrefix(mimetype, "image"): i.Type = "image" + resolution, err := calculateImageResolution(i.Fs, i.Path) + if err != nil { + log.Printf("Error calculating image resolution: %v", err) + } else { + i.Resolution = resolution + } return nil case strings.HasSuffix(mimetype, "pdf"): i.Type = "pdf" @@ -271,6 +284,28 @@ func (i *FileInfo) detectType(modify, saveContent, readHeader bool) error { return nil } +func calculateImageResolution(fs afero.Fs, filePath string) (*ImageResolution, error) { + file, err := fs.Open(filePath) + if err != nil { + return nil, err + } + defer func() { + if cErr := file.Close(); cErr != nil { + log.Printf("Failed to close file: %v", cErr) + } + }() + + config, _, err := image.DecodeConfig(file) + if err != nil { + return nil, err + } + + return &ImageResolution{ + Width: config.Width, + Height: config.Height, + }, nil +} + func (i *FileInfo) readFirstBytes() []byte { reader, err := i.Fs.Open(i.Path) if err != nil { @@ -401,6 +436,15 @@ func (i *FileInfo) readListing(checker rules.Checker, readHeader bool) error { currentDir: dir, } + if !file.IsDir && strings.HasPrefix(mime.TypeByExtension(file.Extension), "image/") { + resolution, err := calculateImageResolution(file.Fs, file.Path) + if err != nil { + log.Printf("Error calculating resolution for image %s: %v", file.Path, err) + } else { + file.Resolution = resolution + } + } + if file.IsDir { listing.NumDirs++ } else { diff --git a/frontend/.prettierignore b/frontend/.prettierignore new file mode 100644 index 00000000..780b4555 --- /dev/null +++ b/frontend/.prettierignore @@ -0,0 +1,2 @@ +# Ignore artifacts: +dist \ No newline at end of file diff --git a/frontend/src/api/tus.ts b/frontend/src/api/tus.ts index bdc234d5..71b7b5f2 100644 --- a/frontend/src/api/tus.ts +++ b/frontend/src/api/tus.ts @@ -1,11 +1,18 @@ import * as tus from "tus-js-client"; import { baseURL, tusEndpoint, tusSettings } from "@/utils/constants"; import { useAuthStore } from "@/stores/auth"; +import { useUploadStore } from "@/stores/upload"; import { removePrefix } from "@/api/utils"; import { fetchURL } from "./utils"; const RETRY_BASE_DELAY = 1000; const RETRY_MAX_DELAY = 20000; +const SPEED_UPDATE_INTERVAL = 1000; +const ALPHA = 0.2; +const ONE_MINUS_ALPHA = 1 - ALPHA; +const RECENT_SPEEDS_LIMIT = 5; +const MB_DIVISOR = 1024 * 1024; +const CURRENT_UPLOAD_LIST: CurrentUploadList = {}; export async function upload( filePath: string, @@ -40,19 +47,47 @@ export async function upload( "X-Auth": authStore.jwt, }, onError: function (error) { + if (CURRENT_UPLOAD_LIST[filePath].interval) { + clearInterval(CURRENT_UPLOAD_LIST[filePath].interval); + } + delete CURRENT_UPLOAD_LIST[filePath]; reject("Upload failed: " + error); }, onProgress: function (bytesUploaded) { - // Emulate ProgressEvent.loaded which is used by calling functions - // loaded is specified in bytes (https://developer.mozilla.org/en-US/docs/Web/API/ProgressEvent/loaded) + const fileData = CURRENT_UPLOAD_LIST[filePath]; + fileData.currentBytesUploaded = bytesUploaded; + + if (!fileData.hasStarted) { + fileData.hasStarted = true; + fileData.lastProgressTimestamp = Date.now(); + + fileData.interval = setInterval(() => { + calcProgress(filePath); + }, SPEED_UPDATE_INTERVAL); + } if (typeof onupload === "function") { onupload({ loaded: bytesUploaded }); } }, onSuccess: function () { + if (CURRENT_UPLOAD_LIST[filePath].interval) { + clearInterval(CURRENT_UPLOAD_LIST[filePath].interval); + } + delete CURRENT_UPLOAD_LIST[filePath]; resolve(); }, }); + CURRENT_UPLOAD_LIST[filePath] = { + upload: upload, + recentSpeeds: [], + initialBytesUploaded: 0, + currentBytesUploaded: 0, + currentAverageSpeed: 0, + lastProgressTimestamp: null, + sumOfRecentSpeeds: 0, + hasStarted: false, + interval: undefined, + }; upload.start(); }); } @@ -94,3 +129,85 @@ export async function useTus(content: ApiContent) { function isTusSupported() { return tus.isSupported === true; } + +function computeETA(state: ETAState, speed?: number) { + if (state.speedMbyte === 0) { + return Infinity; + } + const totalSize = state.sizes.reduce( + (acc: number, size: number) => acc + size, + 0 + ); + const uploadedSize = state.progress.reduce( + (acc: number, progress: Progress) => { + if (typeof progress === "number") { + return acc + progress; + } + return acc; + }, + 0 + ); + const remainingSize = totalSize - uploadedSize; + const speedBytesPerSecond = (speed ?? state.speedMbyte) * 1024 * 1024; + return remainingSize / speedBytesPerSecond; +} + +function computeGlobalSpeedAndETA() { + const uploadStore = useUploadStore(); + let totalSpeed = 0; + let totalCount = 0; + + for (const filePath in CURRENT_UPLOAD_LIST) { + totalSpeed += CURRENT_UPLOAD_LIST[filePath].currentAverageSpeed; + totalCount++; + } + + if (totalCount === 0) return { speed: 0, eta: Infinity }; + + const averageSpeed = totalSpeed / totalCount; + const averageETA = computeETA(uploadStore, averageSpeed); + + return { speed: averageSpeed, eta: averageETA }; +} + +function calcProgress(filePath: string) { + const uploadStore = useUploadStore(); + const fileData = CURRENT_UPLOAD_LIST[filePath]; + + const elapsedTime = + (Date.now() - (fileData.lastProgressTimestamp ?? 0)) / 1000; + const bytesSinceLastUpdate = + fileData.currentBytesUploaded - fileData.initialBytesUploaded; + const currentSpeed = bytesSinceLastUpdate / MB_DIVISOR / elapsedTime; + + if (fileData.recentSpeeds.length >= RECENT_SPEEDS_LIMIT) { + fileData.sumOfRecentSpeeds -= fileData.recentSpeeds.shift() ?? 0; + } + + fileData.recentSpeeds.push(currentSpeed); + fileData.sumOfRecentSpeeds += currentSpeed; + + const avgRecentSpeed = + fileData.sumOfRecentSpeeds / fileData.recentSpeeds.length; + fileData.currentAverageSpeed = + ALPHA * avgRecentSpeed + ONE_MINUS_ALPHA * fileData.currentAverageSpeed; + + const { speed, eta } = computeGlobalSpeedAndETA(); + uploadStore.setUploadSpeed(speed); + uploadStore.setETA(eta); + + fileData.initialBytesUploaded = fileData.currentBytesUploaded; + fileData.lastProgressTimestamp = Date.now(); +} + +export function abortAllUploads() { + for (const filePath in CURRENT_UPLOAD_LIST) { + if (CURRENT_UPLOAD_LIST[filePath].interval) { + clearInterval(CURRENT_UPLOAD_LIST[filePath].interval); + } + if (CURRENT_UPLOAD_LIST[filePath].upload) { + CURRENT_UPLOAD_LIST[filePath].upload.abort(true); + } + delete CURRENT_UPLOAD_LIST[filePath]; + } +} diff --git a/frontend/src/components/Search.vue b/frontend/src/components/Search.vue index 76513248..a93cd569 100644 --- a/frontend/src/components/Search.vue +++ b/frontend/src/components/Search.vue @@ -85,7 +85,7 @@ const boxes = { const layoutStore = useLayoutStore(); const fileStore = useFileStore(); -const { show } = storeToRefs(layoutStore); +const { currentPromptName } = storeToRefs(layoutStore); const prompt = ref(""); const active = ref(false); @@ -103,7 +103,7 @@ const { t } = useI18n(); const route = useRoute(); -watch(show, (newVal, oldVal) => { +watch(currentPromptName, (newVal, oldVal) => { active.value = newVal === "search"; if (oldVal === "search" && !active.value) { diff --git a/frontend/src/components/Shell.vue b/frontend/src/components/Shell.vue index 68f771e1..ebc6f62a 100644 --- a/frontend/src/components/Shell.vue +++ b/frontend/src/components/Shell.vue @@ -1,31 +1,46 @@ @@ -35,6 +50,8 @@ import { useFileStore } from "@/stores/file"; import { useLayoutStore } from "@/stores/layout"; import { commands } from "@/api"; +import { throttle } from "lodash"; +import { theme } from "@/utils/constants"; export default { name: "shell", @@ -54,9 +71,55 @@ export default { history: [], historyPos: 0, canInput: true, + shellDrag: false, + shellHeight: 25, + fontsize: parseFloat(getComputedStyle(document.documentElement).fontSize), }), + mounted() { + window.addEventListener("resize", this.resize); + }, + beforeDestroy() { + window.removeEventListener("resize", this.resize); + }, methods: { ...mapActions(useLayoutStore, ["toggleShell"]), + checkTheme() { + if (theme == "dark") { + return "rgba(255, 255, 255, 0.4)"; + } + return "rgba(127, 127, 127, 0.4)"; + }, + startDrag() { + document.addEventListener("pointermove", this.handleDrag); + this.shellDrag = true; + }, + stopDrag() { + document.removeEventListener("pointermove", this.handleDrag); + this.shellDrag = false; + }, + handleDrag: throttle(function (event) { + const top = window.innerHeight / this.fontsize - 4; + const userPos = (window.innerHeight - event.clientY) / this.fontsize; + const bottom = + 2.25 + + document.querySelector(".shell__divider").offsetHeight / this.fontsize; + + if (userPos <= top && userPos >= bottom) { + this.shellHeight = userPos.toFixed(2); + } + }, 32), + resize: throttle(function () { + const top = window.innerHeight / this.fontsize - 4; + const bottom = + 2.25 + + document.querySelector(".shell__divider").offsetHeight / this.fontsize; + + if (this.shellHeight > top) { + this.shellHeight = top; + } else if (this.shellHeight < bottom) { + this.shellHeight = bottom; + } + }, 32), scroll: function () { this.$refs.scrollable.scrollTop = this.$refs.scrollable.scrollHeight; }, diff --git a/frontend/src/components/Sidebar.vue b/frontend/src/components/Sidebar.vue index 044ad8bd..0f442056 100644 --- a/frontend/src/components/Sidebar.vue +++ b/frontend/src/components/Sidebar.vue @@ -144,9 +144,9 @@ export default { computed: { ...mapState(useAuthStore, ["user", "isLoggedIn"]), ...mapState(useFileStore, ["isFiles", "reload"]), - ...mapState(useLayoutStore, ["show"]), + ...mapState(useLayoutStore, ["currentPromptName"]), active() { - return this.show === "sidebar"; + return this.currentPromptName === "sidebar"; }, signup: () => signup, version: () => version, diff --git a/frontend/src/components/header/HeaderBar.vue b/frontend/src/components/header/HeaderBar.vue index c2c8a142..d15ec060 100644 --- a/frontend/src/components/header/HeaderBar.vue +++ b/frontend/src/components/header/HeaderBar.vue @@ -11,7 +11,10 @@ - @@ -37,6 +53,7 @@ import { mapActions, mapState, mapWritableState } from "pinia"; import { useFileStore } from "@/stores/file"; import { useLayoutStore } from "@/stores/layout"; +import { useAuthStore } from "@/stores/auth"; import FileList from "./FileList.vue"; import { files as api } from "@/api"; import buttons from "@/utils/buttons"; @@ -54,6 +71,7 @@ export default { inject: ["$showError"], computed: { ...mapState(useFileStore, ["req", "selected"]), + ...mapState(useAuthStore, ["user"]), ...mapWritableState(useFileStore, ["reload"]), }, methods: { diff --git a/frontend/src/components/prompts/Delete.vue b/frontend/src/components/prompts/Delete.vue index 096a334e..5b430530 100644 --- a/frontend/src/components/prompts/Delete.vue +++ b/frontend/src/components/prompts/Delete.vue @@ -48,9 +48,9 @@ export default { "selectedCount", "req", "selected", + "currentPrompt", ]), ...mapWritableState(useFileStore, ["reload"]), - ...mapState(useLayoutStore, ["showConfirm"]), }, methods: { ...mapActions(useLayoutStore, ["closeHovers"]), @@ -62,7 +62,7 @@ export default { await api.remove(this.$route.path); buttons.success("delete"); - this.showConfirm(); + this.currentPrompt?.confirm(); this.closeHovers(); return; } diff --git a/frontend/src/components/prompts/DeleteUser.vue b/frontend/src/components/prompts/DeleteUser.vue index e45dab8e..15539c69 100644 --- a/frontend/src/components/prompts/DeleteUser.vue +++ b/frontend/src/components/prompts/DeleteUser.vue @@ -17,7 +17,7 @@ diff --git a/frontend/src/components/prompts/FileList.vue b/frontend/src/components/prompts/FileList.vue index 05fe0dea..b276ce62 100644 --- a/frontend/src/components/prompts/FileList.vue +++ b/frontend/src/components/prompts/FileList.vue @@ -138,6 +138,17 @@ export default { this.selected = event.currentTarget.dataset.url; this.$emit("update:selected", this.selected); }, + createDir: async function () { + this.$store.commit("showHover", { + prompt: "newDir", + action: null, + confirm: null, + props: { + redirect: false, + base: this.current === this.$route.path ? null : this.current, + }, + }); + }, }, }; diff --git a/frontend/src/components/prompts/Info.vue b/frontend/src/components/prompts/Info.vue index 68a6cc9d..af4bdeb1 100644 --- a/frontend/src/components/prompts/Info.vue +++ b/frontend/src/components/prompts/Info.vue @@ -12,10 +12,17 @@

{{ $t("prompts.displayName") }} {{ name }}

+

{{ $t("prompts.size") }}: {{ humanSize }}

+ +
+ {{ $t("prompts.resolution") }}: + {{ resolution.width }} x {{ resolution.height }} +
+

{{ $t("prompts.lastModified") }}: {{ humanTime }}

@@ -146,6 +153,18 @@ export default { : this.req.items[this.selected[0]].isDir) ); }, + resolution: function() { + if (this.selectedCount === 1) { + const selectedItem = this.req.items[this.selected[0]]; + if (selectedItem && selectedItem.type === 'image') { + return selectedItem.resolution; + } + } + else if (this.req && this.req.type === 'image') { + return this.req.resolution; + } + return null; + }, }, methods: { ...mapActions(useLayoutStore, ["closeHovers"]), diff --git a/frontend/src/components/prompts/Move.vue b/frontend/src/components/prompts/Move.vue index 452b4753..a2ff367e 100644 --- a/frontend/src/components/prompts/Move.vue +++ b/frontend/src/components/prompts/Move.vue @@ -5,30 +5,50 @@
- +
-
- + - + {{ $t("buttons.move") }} + +
@@ -37,6 +57,7 @@ import { mapActions, mapState } from "pinia"; import { useFileStore } from "@/stores/file"; import { useLayoutStore } from "@/stores/layout"; +import { useAuthStore } from "@/stores/auth"; import FileList from "./FileList.vue"; import { files as api } from "@/api"; import buttons from "@/utils/buttons"; @@ -52,7 +73,10 @@ export default { }; }, inject: ["$showError"], - computed: mapState(useFileStore, ["req", "selected"]), + computed: { + ...mapState(useFileStore, ["req", "selected"]), + ...mapState(useAuthStore, ["user"]), + }, methods: { ...mapActions(useLayoutStore, ["showHover", "closeHovers"]), move: async function (event) { diff --git a/frontend/src/components/prompts/NewDir.vue b/frontend/src/components/prompts/NewDir.vue index 4510bc27..1eb6d481 100644 --- a/frontend/src/components/prompts/NewDir.vue +++ b/frontend/src/components/prompts/NewDir.vue @@ -51,6 +51,14 @@ import { useI18n } from "vue-i18n"; const $showError = inject("$showError")!; +const props = defineProps({ + base: String, + redirect: { + type: Boolean, + default: true, + }, +}); + const fileStore = useFileStore(); const layoutStore = useLayoutStore(); @@ -65,7 +73,10 @@ const submit = async (event: Event) => { if (name.value === "") return; // Build the path of the new directory. - let uri = fileStore.isFiles ? route.path + "/" : "/"; + let uri: string; + if (props.base) uri = props.base; + else if (fileStore.isFiles) uri = route.path + "/"; + else uri = "/"; if (!fileStore.isListing) { uri = url.removeLastDir(uri) + "/"; @@ -76,7 +87,12 @@ const submit = async (event: Event) => { try { await api.post(uri); - router.push({ path: `${uri}` }); + if (props.redirect) { + router.push({ path: uri }); + } else if (!props.base) { + const res = await api.fetch(url.removeLastDir(uri) + "/"); + fileStore.updateRequest(res); + } } catch (e) { if (e instanceof Error) { $showError(e); diff --git a/frontend/src/components/prompts/Prompts.vue b/frontend/src/components/prompts/Prompts.vue index 844272b9..95e945c5 100644 --- a/frontend/src/components/prompts/Prompts.vue +++ b/frontend/src/components/prompts/Prompts.vue @@ -29,7 +29,7 @@ import Upload from "./Upload.vue"; const layoutStore = useLayoutStore(); -const { show } = storeToRefs(layoutStore); +const { currentPromptName } = storeToRefs(layoutStore); const closeModal = ref<() => Promise>(); @@ -51,7 +51,7 @@ const components = new Map([ ["deleteUser", DeleteUser], ]); -watch(show, (newValue) => { +watch(currentPromptName, (newValue) => { if (closeModal.value) { closeModal.value(); closeModal.value = undefined; @@ -62,12 +62,6 @@ watch(show, (newValue) => { const { open, close } = useModal({ component: BaseModal, - attrs: { - // title: "Hello World!", - // onConfirm() { - // console.log("onConfirm"); - // }, - }, slots: { default: modal, }, @@ -78,7 +72,7 @@ watch(show, (newValue) => { }); window.addEventListener("keydown", (event) => { - if (!layoutStore.show) return; + if (!layoutStore.currentPrompt) return; if (event.key === "Escape") { event.stopImmediatePropagation(); diff --git a/frontend/src/components/prompts/Replace.vue b/frontend/src/components/prompts/Replace.vue index 03e8b912..3a83ac9e 100644 --- a/frontend/src/components/prompts/Replace.vue +++ b/frontend/src/components/prompts/Replace.vue @@ -20,7 +20,7 @@