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

---------

Co-authored-by: Oleg Lobanov <oleg@lobanov.me>
pull/2673/head
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: { watch: {
show(val, old) { currentPrompt(val, old) {
this.active = val === "search"; this.active = val?.prompt === "search";
if (old === "search" && !this.active) { if (old?.prompt === "search" && !this.active) {
if (this.reload) { if (this.reload) {
this.setReload(true); this.setReload(true);
} }
@ -116,8 +116,8 @@ export default {
}, },
}, },
computed: { computed: {
...mapState(["user", "show"]), ...mapState(["user"]),
...mapGetters(["isListing"]), ...mapGetters(["isListing", "currentPrompt"]),
boxes() { boxes() {
return boxes; return boxes;
}, },

View File

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

View File

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

View File

@ -6,10 +6,26 @@
<div class="card-content"> <div class="card-content">
<p>{{ $t("prompts.copyMessage") }}</p> <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>
<div class="card-action"> <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 <button
class="button button--flat button--grey" class="button button--flat button--grey"
@click="$store.commit('closeHovers')" @click="$store.commit('closeHovers')"
@ -28,6 +44,7 @@
</button> </button>
</div> </div>
</div> </div>
</div>
</template> </template>
<script> <script>
@ -46,7 +63,7 @@ export default {
dest: null, dest: null,
}; };
}, },
computed: mapState(["req", "selected"]), computed: mapState(["req", "selected", "user"]),
methods: { methods: {
copy: async function (event) { copy: async function (event) {
event.preventDefault(); event.preventDefault();

View File

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

View File

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

View File

@ -133,6 +133,17 @@ export default {
this.selected = event.currentTarget.dataset.url; this.selected = event.currentTarget.dataset.url;
this.$emit("update:selected", this.selected); 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> </script>

View File

@ -5,10 +5,26 @@
</div> </div>
<div class="card-content"> <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>
<div class="card-action"> <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 <button
class="button button--flat button--grey" class="button button--flat button--grey"
@click="$store.commit('closeHovers')" @click="$store.commit('closeHovers')"
@ -28,6 +44,7 @@
</button> </button>
</div> </div>
</div> </div>
</div>
</template> </template>
<script> <script>
@ -46,7 +63,7 @@ export default {
dest: null, dest: null,
}; };
}, },
computed: mapState(["req", "selected"]), computed: mapState(["req", "selected", "user"]),
methods: { methods: {
move: async function (event) { move: async function (event) {
event.preventDefault(); event.preventDefault();

View File

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

View File

@ -1,6 +1,12 @@
<template> <template>
<div> <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 v-show="showOverlay" @click="resetPrompts" class="overlay"></div>
</div> </div>
</template> </template>
@ -20,7 +26,8 @@ import ReplaceRename from "./ReplaceRename.vue";
import Share from "./Share.vue"; import Share from "./Share.vue";
import Upload from "./Upload.vue"; import Upload from "./Upload.vue";
import ShareDelete from "./ShareDelete.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"; import buttons from "@/utils/buttons";
export default { export default {
@ -40,6 +47,7 @@ export default {
ReplaceRename, ReplaceRename,
Upload, Upload,
ShareDelete, ShareDelete,
Sidebar
}, },
data: function () { data: function () {
return { return {
@ -52,7 +60,7 @@ export default {
}, },
created() { created() {
window.addEventListener("keydown", (event) => { window.addEventListener("keydown", (event) => {
if (this.show == null) return; if (this.currentPrompt == null) return;
let prompt = this.$refs.currentComponent; let prompt = this.$refs.currentComponent;
@ -64,7 +72,7 @@ export default {
// Enter // Enter
if (event.keyCode == 13) { if (event.keyCode == 13) {
switch (this.show) { switch (this.currentPrompt.prompt) {
case "delete": case "delete":
prompt.submit(); prompt.submit();
break; break;
@ -82,31 +90,13 @@ export default {
}); });
}, },
computed: { computed: {
...mapState(["show", "plugins"]), ...mapState(["plugins"]),
currentComponent: function () { ...mapGetters(["currentPrompt", "currentPromptName"]),
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;
},
showOverlay: function () { showOverlay: function () {
return ( 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>
<button <button
class="button button--flat button--blue" class="button button--flat button--blue"
@click="showAction" @click="currentPrompt.action"
:aria-label="$t('buttons.continue')" :aria-label="$t('buttons.continue')"
:title="$t('buttons.continue')" :title="$t('buttons.continue')"
> >
@ -27,7 +27,7 @@
</button> </button>
<button <button
class="button button--flat button--red" class="button button--flat button--red"
@click="showConfirm" @click="currentPrompt.confirm"
:aria-label="$t('buttons.replace')" :aria-label="$t('buttons.replace')"
:title="$t('buttons.replace')" :title="$t('buttons.replace')"
> >
@ -38,10 +38,10 @@
</template> </template>
<script> <script>
import { mapState } from "vuex"; import { mapGetters } from "vuex";
export default { export default {
name: "replace", name: "replace",
computed: mapState(["showConfirm", "showAction"]), computed: mapGetters(["currentPrompt"]),
}; };
</script> </script>

View File

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

View File

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

View File

@ -43,6 +43,14 @@ const getters = {
return files.sort((a, b) => a.progress - b.progress); 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; export default getters;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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