Update batch style to drawer

This commit is contained in:
rijkvanzanten
2020-11-05 15:39:07 -05:00
parent a432640ecb
commit 8dbfd34862
6 changed files with 224 additions and 44 deletions

View File

@@ -2,10 +2,15 @@ import express from 'express';
import asyncHandler from 'express-async-handler';
import collectionExists from '../middleware/collection-exists';
import { ItemsService, MetaService } from '../services';
import { RouteNotFoundException, ForbiddenException } from '../exceptions';
import {
RouteNotFoundException,
ForbiddenException,
FailedValidationException,
} from '../exceptions';
import { respond } from '../middleware/respond';
import { InvalidPayloadException } from '../exceptions';
import { PrimaryKey } from '../types';
import Joi from 'joi';
const router = express.Router();
@@ -92,7 +97,35 @@ router.patch(
return next();
}
const primaryKeys = await service.update(req.body);
if (Array.isArray(req.body)) {
const primaryKeys = await service.update(req.body);
try {
const result = await service.readByKey(primaryKeys, req.sanitizedQuery);
res.locals.payload = { data: result || null };
} catch (error) {
if (error instanceof ForbiddenException) {
return next();
}
throw error;
}
return next();
}
const updateSchema = Joi.object({
keys: Joi.array().items(Joi.alternatives(Joi.string(), Joi.number())).required(),
data: Joi.object().required().unknown(),
});
const { error } = updateSchema.validate(req.body);
if (error) {
throw new FailedValidationException(error.details[0]);
}
const primaryKeys = await service.update(req.body.data, req.body.keys);
try {
const result = await service.readByKey(primaryKeys, req.sanitizedQuery);

View File

@@ -64,9 +64,10 @@ export default defineComponent({
type: Boolean,
default: false,
},
// Note: can be null when the form is used in batch mode
primaryKey: {
type: [String, Number],
required: true,
default: null,
},
disabled: {
type: Boolean,

View File

@@ -96,7 +96,7 @@
<v-dialog
v-model="confirmArchive"
@dsc="confirmArchive = false"
@esc="confirmArchive = false"
v-if="selection.length > 0 && currentCollection.meta && currentCollection.meta.archive_field"
>
<template #activator="{ on }">
@@ -130,10 +130,10 @@
rounded
icon
class="action-batch"
v-if="selection.length > 1"
:to="batchLink"
v-tooltip.bottom="batchEditAllowed ? $t('edit') : $t('not_allowed')"
:disabled="batchEditAllowed === false"
@click="batchEditActive = true"
v-if="selection.length > 1"
v-tooltip.bottom="batchEditAllowed ? $t('edit') : $t('not_allowed')"
>
<v-icon name="edit" outline />
</v-button>
@@ -203,6 +203,13 @@
</template>
</component>
<drawer-batch
:primary-keys="selection"
:active.sync="batchEditActive"
:collection="collection"
@refresh="refresh"
/>
<template #sidebar>
<sidebar-detail icon="info_outline" :title="$t('information')" close>
<div
@@ -255,6 +262,7 @@ import BookmarkEdit from '@/views/private/components/bookmark-edit';
import router from '@/router';
import marked from 'marked';
import { usePermissionsStore, useUserStore } from '@/stores';
import DrawerBatch from '@/views/private/components/drawer-batch';
type Item = {
[field: string]: any;
@@ -270,6 +278,7 @@ export default defineComponent({
SearchInput,
BookmarkAdd,
BookmarkEdit,
DrawerBatch,
},
props: {
collection: {
@@ -291,7 +300,7 @@ export default defineComponent({
const { selection } = useSelection();
const { info: currentCollection } = useCollection(collection);
const { addNewLink, batchLink, currentCollectionLink } = useLinks();
const { addNewLink, currentCollectionLink } = useLinks();
const { breadcrumb } = useBreadcrumb();
const {
@@ -319,7 +328,8 @@ export default defineComponent({
archive,
archiving,
error: deleteError,
} = useBatchDelete();
batchEditActive,
} = useBatch();
const {
bookmarkDialogActive,
@@ -344,7 +354,7 @@ export default defineComponent({
return {
addNewLink,
batchDelete,
batchLink,
batchEditActive,
confirmDelete,
currentCollection,
@@ -381,8 +391,13 @@ export default defineComponent({
bookmarkIsMine,
bookmarkSaving,
clearLocalSave,
refresh,
};
function refresh() {
layoutRef.value?.refresh?.();
}
function useBreadcrumb() {
const breadcrumb = computed(() => [
{
@@ -406,16 +421,18 @@ export default defineComponent({
return { selection };
}
function useBatchDelete() {
function useBatch() {
const confirmDelete = ref(false);
const deleting = ref(false);
const batchEditActive = ref(false);
const confirmArchive = ref(false);
const archiving = ref(false);
const error = ref<any>();
return { confirmDelete, deleting, batchDelete, confirmArchive, archiving, archive, error };
return { batchEditActive, confirmDelete, deleting, batchDelete, confirmArchive, archiving, archive, error };
async function batchDelete() {
deleting.value = true;
@@ -464,16 +481,11 @@ export default defineComponent({
return `/collections/${props.collection}/+`;
});
const batchLink = computed<string>(() => {
const batchPrimaryKeys = selection.value.join();
return `/collections/${props.collection}/${batchPrimaryKeys}`;
});
const currentCollectionLink = computed<string>(() => {
return `/collections/${props.collection}`;
});
return { addNewLink, batchLink, currentCollectionLink };
return { addNewLink, currentCollectionLink };
}
function useBookmarks() {

View File

@@ -10,12 +10,7 @@
</h1>
</template>
<template
#title
v-else-if="
isNew === false && isBatch === false && collectionInfo.meta && collectionInfo.meta.display_template
"
>
<template #title v-else-if="isNew === false && collectionInfo.meta && collectionInfo.meta.display_template">
<v-skeleton-loader class="title-loader" type="text" v-if="loading" />
<h1 class="type-title" v-else>
@@ -162,7 +157,6 @@
:loading="loading"
:initial-values="item"
:fields="fields"
:batch-mode="isBatch"
:primary-key="primaryKey || '+'"
:validation-errors="validationErrors"
v-model="edits"
@@ -186,24 +180,14 @@
<div class="page-description" v-html="marked($t('page_help_collections_item'))" />
</sidebar-detail>
<revisions-drawer-detail
v-if="
collectionInfo.meta &&
collectionInfo.meta.singleton === false &&
isBatch === false &&
isNew === false
"
v-if="collectionInfo.meta && collectionInfo.meta.singleton === false && isNew === false"
:collection="collection"
:primary-key="primaryKey"
ref="revisionsDrawerDetail"
@revert="refresh"
/>
<comments-sidebar-detail
v-if="
collectionInfo.meta &&
collectionInfo.meta.singleton === false &&
isBatch === false &&
isNew === false
"
v-if="collectionInfo.meta && collectionInfo.meta.singleton === false && isNew === false"
:collection="collection"
:primary-key="primaryKey"
/>
@@ -287,7 +271,6 @@ export default defineComponent({
archiving,
isArchived,
saveAsCopy,
isBatch,
refresh,
validationErrors,
} = useItem(collection, primaryKey);
@@ -326,11 +309,6 @@ export default defineComponent({
});
const title = computed(() => {
if (isBatch.value) {
const itemCount = props.primaryKey.split(',').length;
return i18n.t('editing_in_batch', { count: itemCount });
}
return isNew.value
? i18n.t('creating_in', { collection: collectionInfo.value?.name })
: i18n.t('editing_in', { collection: collectionInfo.value?.name });
@@ -380,7 +358,6 @@ export default defineComponent({
saveAndStay,
saveAndAddNew,
saveAsCopyAndNavigate,
isBatch,
templateValues,
archiveTooltip,
breadcrumb,

View File

@@ -0,0 +1,153 @@
<template>
<v-drawer
v-model="_active"
:title="$t('editing_in_batch', { count: primaryKeys.length })"
persistent
@cancel="cancel"
>
<template #actions>
<v-button @click="save" icon rounded :loading="saving" v-tooltip.bottom="$t('save')">
<v-icon name="check" />
</v-button>
</template>
<div class="drawer-batch-content">
<v-form :collection="collection" v-model="_edits" batch-mode primary-key="+" />
</div>
</v-drawer>
</template>
<script lang="ts">
import { defineComponent, ref, computed, PropType, watch, toRefs } from '@vue/composition-api';
import api from '@/api';
import useCollection from '@/composables/use-collection';
import { useFieldsStore, useRelationsStore } from '@/stores';
import i18n from '@/lang';
import { Relation, Field } from '@/types';
export default defineComponent({
model: {
prop: 'edits',
},
props: {
active: {
type: Boolean,
default: false,
},
collection: {
type: String,
required: true,
},
edits: {
type: Object as PropType<Record<string, any>>,
default: undefined,
},
primaryKeys: {
type: Array as PropType<(number | string)[]>,
required: true,
},
},
setup(props, { emit }) {
const fieldsStore = useFieldsStore();
const relationsStore = useRelationsStore();
const { _edits } = useEdits();
const { _active } = useActiveState();
const { save, cancel, saving } = useActions();
const { collection } = toRefs(props);
const { info: collectionInfo } = useCollection(collection);
return {
_active,
_edits,
save,
saving,
cancel,
};
function useEdits() {
const localEdits = ref<Record<string, any>>({});
const _edits = computed<Record<string, any>>({
get() {
if (props.edits !== undefined) {
return {
...props.edits,
...localEdits.value,
};
}
return localEdits.value;
},
set(newEdits) {
localEdits.value = newEdits;
},
});
return { _edits };
}
function useActiveState() {
const localActive = ref(false);
const _active = computed({
get() {
return props.active === undefined ? localActive.value : props.active;
},
set(newActive: boolean) {
localActive.value = newActive;
emit('update:active', newActive);
},
});
return { _active };
}
function useActions() {
const saving = ref(false);
const error = ref<any>(null);
return { save, cancel, saving };
async function save() {
saving.value = true;
try {
await api.patch(`/items/${props.collection}`, {
keys: props.primaryKeys,
data: _edits.value,
});
emit('refresh');
_active.value = false;
_edits.value = {};
} catch (err) {
error.value = err;
} finally {
saving.value = false;
}
}
function cancel() {
_active.value = false;
_edits.value = {};
}
}
},
});
</script>
<style lang="scss" scoped>
.v-divider {
margin: 52px 0;
}
.drawer-batch-content {
padding: var(--content-padding);
padding-bottom: var(--content-padding-bottom);
}
</style>

View File

@@ -0,0 +1,4 @@
import DrawerBatch from './drawer-batch.vue';
export { DrawerBatch };
export default DrawerBatch;