feat: add new folder button to move/create dialogs (#2667)

---------

Co-authored-by: Oleg Lobanov <oleg@lobanov.me>
This commit is contained in:
ArthurMousatov 2023-08-26 18:28:58 -04:00 committed by GitHub
parent 374bbd3ec1
commit 5994224468
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 199 additions and 129 deletions

View File

@ -90,10 +90,10 @@ export default {
};
},
watch: {
show(val, old) {
this.active = val === "search";
currentPrompt(val, old) {
this.active = val?.prompt === "search";
if (old === "search" && !this.active) {
if (old?.prompt === "search" && !this.active) {
if (this.reload) {
this.setReload(true);
}
@ -116,8 +116,8 @@ export default {
},
},
computed: {
...mapState(["user", "show"]),
...mapGetters(["isListing"]),
...mapState(["user"]),
...mapGetters(["isListing", "currentPrompt"]),
boxes() {
return boxes;
},

View File

@ -133,9 +133,9 @@ export default {
},
computed: {
...mapState(["user"]),
...mapGetters(["isLogged"]),
...mapGetters(["isLogged", "currentPrompt"]),
active() {
return this.$store.state.show === "sidebar";
return this.currentPrompt?.prompt === "sidebar";
},
signup: () => signup,
version: () => version,

View File

@ -11,7 +11,7 @@
<slot />
<div id="dropdown" :class="{ active: this.$store.state.show === 'more' }">
<div id="dropdown" :class="{ active: this.currentPromptName === 'more' }">
<slot name="actions" />
</div>
@ -25,7 +25,7 @@
<div
class="overlay"
v-show="this.$store.state.show == 'more'"
v-show="this.currentPromptName == 'more'"
@click="$store.commit('closeHovers')"
/>
</header>
@ -35,6 +35,7 @@
import { logoURL } from "@/utils/constants";
import Action from "@/components/header/Action.vue";
import { mapGetters } from "vuex";
export default {
name: "header-bar",
@ -52,6 +53,9 @@ export default {
this.$store.commit("showHover", "sidebar");
},
},
computed: {
...mapGetters(["currentPromptName"]),
},
};
</script>

View File

@ -6,26 +6,43 @@
<div class="card-content">
<p>{{ $t("prompts.copyMessage") }}</p>
<file-list @update:selected="(val) => (dest = val)"></file-list>
<file-list ref="fileList" @update:selected="(val) => (dest = val)">
</file-list>
</div>
<div class="card-action">
<button
class="button button--flat button--grey"
@click="$store.commit('closeHovers')"
:aria-label="$t('buttons.cancel')"
:title="$t('buttons.cancel')"
>
{{ $t("buttons.cancel") }}
</button>
<button
class="button button--flat"
@click="copy"
:aria-label="$t('buttons.copy')"
:title="$t('buttons.copy')"
>
{{ $t("buttons.copy") }}
</button>
<div
class="card-action"
style="display: flex; align-items: center; justify-content: space-between;"
>
<template v-if="user.perm.create">
<button
class="button button--flat"
@click="$refs.fileList.createDir()"
:aria-label="$t('sidebar.newFolder')"
:title="$t('sidebar.newFolder')"
style="justify-self: left;"
>
<span>{{ $t("sidebar.newFolder") }}</span>
</button>
</template>
<div>
<button
class="button button--flat button--grey"
@click="$store.commit('closeHovers')"
:aria-label="$t('buttons.cancel')"
:title="$t('buttons.cancel')"
>
{{ $t("buttons.cancel") }}
</button>
<button
class="button button--flat"
@click="copy"
:aria-label="$t('buttons.copy')"
:title="$t('buttons.copy')"
>
{{ $t("buttons.copy") }}
</button>
</div>
</div>
</div>
</template>
@ -46,7 +63,7 @@ export default {
dest: null,
};
},
computed: mapState(["req", "selected"]),
computed: mapState(["req", "selected", "user"]),
methods: {
copy: async function (event) {
event.preventDefault();

View File

@ -37,8 +37,8 @@ import buttons from "@/utils/buttons";
export default {
name: "delete",
computed: {
...mapGetters(["isListing", "selectedCount"]),
...mapState(["req", "selected", "showConfirm"]),
...mapGetters(["isListing", "selectedCount", "currentPrompt"]),
...mapState(["req", "selected"]),
},
methods: {
...mapMutations(["closeHovers"]),
@ -50,7 +50,7 @@ export default {
await api.remove(this.$route.path);
buttons.success("delete");
this.showConfirm();
this.currentPrompt?.confirm();
this.closeHovers();
return;
}

View File

@ -11,7 +11,7 @@
v-for="(ext, format) in formats"
:key="format"
class="button button--block"
@click="showConfirm(format)"
@click="currentPrompt.confirm(format)"
v-focus
>
{{ ext }}
@ -21,7 +21,7 @@
</template>
<script>
import { mapState } from "vuex";
import { mapGetters } from "vuex";
export default {
name: "download",
@ -38,6 +38,8 @@ export default {
},
};
},
computed: mapState(["showConfirm"]),
computed: {
...mapGetters(["currentPrompt"]),
},
};
</script>

View File

@ -133,6 +133,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,
},
});
},
},
};
</script>

View File

@ -5,27 +5,44 @@
</div>
<div class="card-content">
<file-list @update:selected="(val) => (dest = val)"></file-list>
<file-list ref="fileList" @update:selected="(val) => (dest = val)">
</file-list>
</div>
<div class="card-action">
<button
class="button button--flat button--grey"
@click="$store.commit('closeHovers')"
:aria-label="$t('buttons.cancel')"
:title="$t('buttons.cancel')"
>
{{ $t("buttons.cancel") }}
</button>
<button
class="button button--flat"
@click="move"
:disabled="$route.path === dest"
:aria-label="$t('buttons.move')"
:title="$t('buttons.move')"
>
{{ $t("buttons.move") }}
</button>
<div
class="card-action"
style="display: flex; align-items: center; justify-content: space-between;"
>
<template v-if="user.perm.create">
<button
class="button button--flat"
@click="$refs.fileList.createDir()"
:aria-label="$t('sidebar.newFolder')"
:title="$t('sidebar.newFolder')"
style="justify-self: left;"
>
<span>{{ $t("sidebar.newFolder") }}</span>
</button>
</template>
<div>
<button
class="button button--flat button--grey"
@click="$store.commit('closeHovers')"
:aria-label="$t('buttons.cancel')"
:title="$t('buttons.cancel')"
>
{{ $t("buttons.cancel") }}
</button>
<button
class="button button--flat"
@click="move"
:disabled="$route.path === dest"
:aria-label="$t('buttons.move')"
:title="$t('buttons.move')"
>
{{ $t("buttons.move") }}
</button>
</div>
</div>
</div>
</template>
@ -46,7 +63,7 @@ export default {
dest: null,
};
},
computed: mapState(["req", "selected"]),
computed: mapState(["req", "selected", "user"]),
methods: {
move: async function (event) {
event.preventDefault();

View File

@ -43,6 +43,16 @@ import url from "@/utils/url";
export default {
name: "new-dir",
props: {
redirect: {
type: Boolean,
default: true,
},
base: {
type: [String, null],
default: null,
},
},
data: function () {
return {
name: "",
@ -57,7 +67,11 @@ export default {
if (this.new === "") return;
// Build the path of the new directory.
let uri = this.isFiles ? this.$route.path + "/" : "/";
let uri;
if (this.base) uri = this.base;
else if (this.isFiles) uri = this.$route.path + "/";
else uri = "/";
if (!this.isListing) {
uri = url.removeLastDir(uri) + "/";
@ -65,10 +79,14 @@ export default {
uri += encodeURIComponent(this.name) + "/";
uri = uri.replace("//", "/");
try {
await api.post(uri);
this.$router.push({ path: uri });
if (this.redirect) {
this.$router.push({ path: uri });
} else if (!this.base) {
const res = await api.fetch(url.removeLastDir(uri) + "/");
this.$store.commit("updateRequest", res);
}
} catch (e) {
this.$showError(e);
}

View File

@ -1,6 +1,12 @@
<template>
<div>
<component ref="currentComponent" :is="currentComponent"></component>
<component
v-if="showOverlay"
:ref="currentPromptName"
:is="currentPromptName"
v-bind="currentPrompt.props"
>
</component>
<div v-show="showOverlay" @click="resetPrompts" class="overlay"></div>
</div>
</template>
@ -20,7 +26,8 @@ import ReplaceRename from "./ReplaceRename.vue";
import Share from "./Share.vue";
import Upload from "./Upload.vue";
import ShareDelete from "./ShareDelete.vue";
import { mapState } from "vuex";
import Sidebar from "../Sidebar.vue";
import { mapGetters, mapState } from "vuex";
import buttons from "@/utils/buttons";
export default {
@ -40,6 +47,7 @@ export default {
ReplaceRename,
Upload,
ShareDelete,
Sidebar
},
data: function () {
return {
@ -52,7 +60,7 @@ export default {
},
created() {
window.addEventListener("keydown", (event) => {
if (this.show == null) return;
if (this.currentPrompt == null) return;
let prompt = this.$refs.currentComponent;
@ -64,7 +72,7 @@ export default {
// Enter
if (event.keyCode == 13) {
switch (this.show) {
switch (this.currentPrompt.prompt) {
case "delete":
prompt.submit();
break;
@ -82,31 +90,13 @@ export default {
});
},
computed: {
...mapState(["show", "plugins"]),
currentComponent: function () {
const matched =
[
"info",
"help",
"delete",
"rename",
"move",
"copy",
"newFile",
"newDir",
"download",
"replace",
"replace-rename",
"share",
"upload",
"share-delete",
].indexOf(this.show) >= 0;
return (matched && this.show) || null;
},
...mapState(["plugins"]),
...mapGetters(["currentPrompt", "currentPromptName"]),
showOverlay: function () {
return (
this.show !== null && this.show !== "search" && this.show !== "more"
this.currentPrompt !== null &&
this.currentPrompt.prompt !== "search" &&
this.currentPrompt.prompt !== "more"
);
},
},

View File

@ -19,7 +19,7 @@
</button>
<button
class="button button--flat button--blue"
@click="showAction"
@click="currentPrompt.action"
:aria-label="$t('buttons.continue')"
:title="$t('buttons.continue')"
>
@ -27,7 +27,7 @@
</button>
<button
class="button button--flat button--red"
@click="showConfirm"
@click="currentPrompt.confirm"
:aria-label="$t('buttons.replace')"
:title="$t('buttons.replace')"
>
@ -38,10 +38,10 @@
</template>
<script>
import { mapState } from "vuex";
import { mapGetters } from "vuex";
export default {
name: "replace",
computed: mapState(["showConfirm", "showAction"]),
computed: mapGetters(["currentPrompt"]),
};
</script>

View File

@ -19,7 +19,7 @@
</button>
<button
class="button button--flat button--blue"
@click="(event) => showConfirm(event, 'rename')"
@click="(event) => currentPrompt.confirm(event, 'rename')"
:aria-label="$t('buttons.rename')"
:title="$t('buttons.rename')"
>
@ -27,7 +27,7 @@
</button>
<button
class="button button--flat button--red"
@click="(event) => showConfirm(event, 'overwrite')"
@click="(event) => currentPrompt.confirm(event, 'overwrite')"
:aria-label="$t('buttons.replace')"
:title="$t('buttons.replace')"
>
@ -38,10 +38,10 @@
</template>
<script>
import { mapState } from "vuex";
import { mapGetters } from "vuex";
export default {
name: "replace-rename",
computed: mapState(["showConfirm"]),
computed: mapGetters(["currentPrompt"]),
};
</script>

View File

@ -25,16 +25,16 @@
</template>
<script>
import { mapState } from "vuex";
import { mapGetters } from "vuex";
export default {
name: "share-delete",
computed: {
...mapState(["showConfirm"]),
...mapGetters(["currentPrompt"]),
},
methods: {
submit: function () {
this.showConfirm();
this.currentPrompt?.confirm();
},
},
};

View File

@ -43,6 +43,14 @@ const getters = {
return files.sort((a, b) => a.progress - b.progress);
},
currentPrompt: (state) => {
return state.prompts.length > 0
? state.prompts[state.prompts.length - 1]
: null;
},
currentPromptName: (_, getters) => {
return getters.currentPrompt?.prompt;
},
};
export default getters;

View File

@ -20,10 +20,8 @@ const state = {
reload: false,
selected: [],
multiple: false,
show: null,
prompts: [],
showShell: false,
showConfirm: null,
showAction: null,
};
export default new Vuex.Store({

View File

@ -3,30 +3,34 @@ import moment from "moment";
const mutations = {
closeHovers: (state) => {
state.show = null;
state.showConfirm = null;
state.showAction = null;
state.prompts.pop();
},
toggleShell: (state) => {
state.showShell = !state.showShell;
},
showHover: (state, value) => {
if (typeof value !== "object") {
state.show = value;
state.prompts.push({
prompt: value,
confirm: null,
action: null,
props: null,
});
return;
}
state.show = value.prompt;
state.showConfirm = value.confirm;
if (value.action !== undefined) {
state.showAction = value.action;
}
state.prompts.push({
prompt: value.prompt, // Should not be null
confirm: value?.confirm,
action: value?.action,
props: value?.props,
});
},
showError: (state) => {
state.show = "error";
state.prompts.push("error");
},
showSuccess: (state) => {
state.show = "success";
state.prompts.push("success");
},
setLoading: (state, value) => {
state.loading = value;
@ -74,8 +78,15 @@ const mutations = {
}
},
updateRequest: (state, value) => {
const selectedItems = state.selected.map((i) => state.req.items[i]);
state.oldReq = state.req;
state.req = value;
state.selected = [];
if (!state.req?.items) return;
state.selected = state.req.items
.filter((item) => selectedItems.some((rItem) => rItem.url === item.url))
.map((item) => item.index);
},
updateClipboard: (state, value) => {
state.clipboard.key = value.key;

View File

@ -50,7 +50,7 @@ export default {
};
},
computed: {
...mapState(["req", "reload", "loading", "show"]),
...mapState(["req", "reload", "loading"]),
currentView() {
if (this.req.type == undefined) {
return null;

View File

@ -30,7 +30,7 @@ export default {
UploadFiles,
},
computed: {
...mapGetters(["isLogged", "progress"]),
...mapGetters(["isLogged", "progress", "currentPrompt"]),
...mapState(["user"]),
isExecEnabled: () => enableExec,
},
@ -38,7 +38,7 @@ export default {
$route: function () {
this.$store.commit("resetSelected");
this.$store.commit("multiple", false);
if (this.$store.state.show !== "success")
if (this.currentPrompt?.prompt !== "success")
this.$store.commit("closeHovers");
},
},

View File

@ -298,16 +298,8 @@ export default {
};
},
computed: {
...mapState([
"req",
"selected",
"user",
"show",
"multiple",
"selected",
"loading",
]),
...mapGetters(["selectedCount"]),
...mapState(["req", "selected", "user", "multiple", "selected", "loading"]),
...mapGetters(["selectedCount", "currentPrompt"]),
nameSorted() {
return this.req.sorting.by === "name";
},
@ -444,7 +436,7 @@ export default {
},
keyEvent(event) {
// No prompts are shown
if (this.show !== null) {
if (this.currentPrompt !== null) {
return;
}

View File

@ -143,7 +143,7 @@
</template>
<script>
import { mapState } from "vuex";
import { mapGetters, mapState } from "vuex";
import { files as api } from "@/api";
import { resizePreview } from "@/utils/constants";
import url from "@/utils/url";
@ -177,7 +177,8 @@ export default {
};
},
computed: {
...mapState(["req", "user", "oldReq", "jwt", "loading", "show"]),
...mapState(["req", "user", "oldReq", "jwt", "loading"]),
...mapGetters(["currentPrompt"]),
hasPrevious() {
return this.previousLink !== "";
},
@ -195,7 +196,7 @@ export default {
return api.getDownloadURL(this.req, true);
},
showMore() {
return this.$store.state.show === "more";
return this.currentPrompt?.prompt === "more";
},
isResizeEnabled() {
return resizePreview;
@ -247,7 +248,7 @@ export default {
this.$router.replace({ path: this.nextLink });
},
key(event) {
if (this.show !== null) {
if (this.currentPrompt !== null) {
return;
}

View File

@ -37,7 +37,7 @@
</form>
</div>
<div v-if="$store.state.show === 'deleteUser'" class="card floating">
<div v-if="this.currentPromptName === 'deleteUser'" class="card floating">
<div class="card-content">
<p>Are you sure you want to delete this user?</p>
</div>
@ -61,7 +61,7 @@
</template>
<script>
import { mapState, mapMutations } from "vuex";
import { mapState, mapMutations, mapGetters } from "vuex";
import { users as api, settings } from "@/api";
import UserForm from "@/components/settings/UserForm.vue";
import Errors from "@/views/Errors.vue";
@ -89,6 +89,7 @@ export default {
return this.$route.path === "/settings/users/new";
},
...mapState(["loading"]),
...mapGetters(["currentPrompt", "currentPromptName"]),
},
watch: {
$route: "fetchData",