Added delete option to scene edits.
This commit is contained in:
@@ -119,12 +119,22 @@
|
||||
|
||||
.button-cancel {
|
||||
background: none;
|
||||
color: var(--glass);
|
||||
color: var(--error);
|
||||
font-weight: normal;
|
||||
box-shadow: none;
|
||||
|
||||
.icon {
|
||||
fill: var(--error);
|
||||
}
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
color: var(--error);
|
||||
color: var(--text-light);
|
||||
background: var(--error);
|
||||
cursor: pointer;
|
||||
|
||||
.icon {
|
||||
fill: var(--text-light);
|
||||
}
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
|
||||
@@ -378,6 +378,8 @@ async function reviewRevision(revision, isApproved) {
|
||||
await post(`/revisions/${domain}/${revision.id}/reviews`, {
|
||||
isApproved,
|
||||
feedback: feedbacks.value[revision.id],
|
||||
}, {
|
||||
appendErrorMessage: true,
|
||||
});
|
||||
|
||||
const updatedRevision = await get(`/revisions/${domain}/${revision.id}`, {
|
||||
|
||||
@@ -28,6 +28,10 @@ export async function onBeforeRender(pageContext) {
|
||||
restriction: pageContext.restriction,
|
||||
});
|
||||
|
||||
if (!scene) {
|
||||
throw render(404, `Cannot find scene '${pageContext.routeParams.sceneId}'.`);
|
||||
}
|
||||
|
||||
const [campaigns, tagIds] = await Promise.all([
|
||||
getRandomCampaigns([
|
||||
{
|
||||
@@ -44,10 +48,6 @@ export async function onBeforeRender(pageContext) {
|
||||
], 'tags', true),
|
||||
]);
|
||||
|
||||
if (!scene) {
|
||||
throw render(404, `Cannot find scene '${pageContext.routeParams.sceneId}'.`);
|
||||
}
|
||||
|
||||
return {
|
||||
pageContext: {
|
||||
title: getTitle(scene),
|
||||
|
||||
@@ -73,7 +73,7 @@
|
||||
v-if="item.note"
|
||||
v-tooltip="item.note"
|
||||
icon="info2"
|
||||
class="item-note"
|
||||
class="item-note noselect"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -81,6 +81,7 @@
|
||||
<Icon
|
||||
v-if="!item.forced"
|
||||
icon="pencil5"
|
||||
class="noselect"
|
||||
:class="{ active: editing.has(item.key) }"
|
||||
@click="toggleField(item)"
|
||||
/>
|
||||
@@ -134,6 +135,15 @@
|
||||
:disabled="!editing.has(item.key)"
|
||||
/>
|
||||
|
||||
<Checkbox
|
||||
v-if="item.type === 'checkbox'"
|
||||
:label="item.checkboxLabel"
|
||||
:checked="edits[item.key]"
|
||||
:disabled="!editing.has(item.key)"
|
||||
class="checkbox delete"
|
||||
@change="(checked) => setDelete(checked)"
|
||||
/>
|
||||
|
||||
<div
|
||||
v-if="item.type === 'date'"
|
||||
class="date"
|
||||
@@ -210,9 +220,10 @@
|
||||
<div class="editor-actions">
|
||||
<Checkbox
|
||||
v-if="user.role !== 'user'"
|
||||
v-tooltip="isApplyDisabled && editing.has('delete') ? 'Delete must be approved by an admin' : null"
|
||||
label="Approve and apply immediately"
|
||||
:checked="apply"
|
||||
:disabled="editing.size === 0"
|
||||
:disabled="isApplyDisabled"
|
||||
@change="(checked) => apply = checked"
|
||||
/>
|
||||
|
||||
@@ -241,10 +252,7 @@ import EditTags from '#/components/edit/tags.vue';
|
||||
import EditMovies from '#/components/edit/movies.vue';
|
||||
import Checkbox from '#/components/form/checkbox.vue';
|
||||
|
||||
import {
|
||||
// get,
|
||||
post,
|
||||
} from '#/src/api.js';
|
||||
import { post } from '#/src/api.js';
|
||||
|
||||
const pageContext = inject('pageContext');
|
||||
|
||||
@@ -310,12 +318,20 @@ const fields = computed(() => [
|
||||
},
|
||||
...(user.role === 'user'
|
||||
? []
|
||||
: [{
|
||||
key: 'comment',
|
||||
type: 'text',
|
||||
placeholder: 'Do NOT use this field to summarize and clarify your revision. This field is for permanent notes and comments regarding the scene or database entry itself.',
|
||||
value: scene.value.comment,
|
||||
}]),
|
||||
: [
|
||||
{
|
||||
key: 'comment',
|
||||
type: 'text',
|
||||
placeholder: 'Do NOT use this field to summarize and clarify your revision. This field is for permanent notes and comments regarding the scene or database entry itself.',
|
||||
value: scene.value.comment,
|
||||
},
|
||||
{
|
||||
key: 'delete',
|
||||
type: 'checkbox',
|
||||
checkboxLabel: 'Remove this scene from the database',
|
||||
value: false,
|
||||
},
|
||||
]),
|
||||
]);
|
||||
|
||||
function simplifyArray(field) {
|
||||
@@ -332,6 +348,9 @@ const comment = ref(null);
|
||||
const apply = ref(user.role !== 'user');
|
||||
const submitted = ref(false);
|
||||
|
||||
const userCanDelete = user.abilities.some((ability) => ability.subject === 'scene' && ability.action === 'delete');
|
||||
const isApplyDisabled = computed(() => editing.value.size === 0 || (edits.value.delete && !userCanDelete));
|
||||
|
||||
const keyMap = {
|
||||
date: {
|
||||
date: 'date',
|
||||
@@ -359,6 +378,14 @@ function setDuration(unit, event) {
|
||||
edits.value.duration[timeUnits.indexOf(unit)] = Number(event.target.value);
|
||||
}
|
||||
|
||||
function setDelete(checked) {
|
||||
edits.value.delete = checked;
|
||||
|
||||
if (!userCanDelete) {
|
||||
apply.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function submit() {
|
||||
try {
|
||||
await post('/revisions/scenes', {
|
||||
@@ -417,6 +444,10 @@ async function submit() {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: .25rem 1rem;
|
||||
|
||||
.value.disabled {
|
||||
color: var(--glass);
|
||||
}
|
||||
}
|
||||
|
||||
.key {
|
||||
@@ -488,7 +519,7 @@ async function submit() {
|
||||
}
|
||||
}
|
||||
|
||||
.item-note{
|
||||
.item-note {
|
||||
fill: var(--glass);
|
||||
padding: .5rem .75rem;
|
||||
cursor: help;
|
||||
@@ -518,6 +549,25 @@ async function submit() {
|
||||
}
|
||||
}
|
||||
|
||||
.checkbox.delete {
|
||||
display: inline-flex;
|
||||
gap: 1rem;
|
||||
align-items: center;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.value.disabled .delete {
|
||||
:deep(.check-checkbox) + .check {
|
||||
background: var(--glass-weak-30);
|
||||
}
|
||||
}
|
||||
|
||||
.value:not(.disabled) .delete {
|
||||
:deep(.check-checkbox:checked) + .check {
|
||||
background: var(--error);
|
||||
}
|
||||
}
|
||||
|
||||
.editor-actions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -540,6 +590,27 @@ async function submit() {
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.delete-title {
|
||||
display: block;
|
||||
margin-top: .5rem;
|
||||
max-width: 25rem;
|
||||
}
|
||||
|
||||
.dialog-body {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.dialog-section {
|
||||
margin-bottom: 1rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.dialog-actions {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
@media(--small) {
|
||||
.row {
|
||||
flex-direction: column;
|
||||
|
||||
@@ -817,7 +817,18 @@ async function applySceneMoviesDelta(sceneId, delta, trx) {
|
||||
}
|
||||
}
|
||||
|
||||
async function applySceneRevision(revisionIds) {
|
||||
async function applySceneDeleteDelta(sceneId, _delta, trx, reqUser) {
|
||||
if (!reqUser.abilities.some((ability) => ability.subject === 'scene' && ability.action === 'delete')) {
|
||||
throw new HttpError('You are not privileged to delete scenes', 400);
|
||||
}
|
||||
|
||||
await knexOwner('releases')
|
||||
.where('id', sceneId)
|
||||
.delete()
|
||||
.transacting(trx);
|
||||
}
|
||||
|
||||
async function applySceneRevision(revisionIds, reqUser) {
|
||||
const revisions = await knexOwner('scenes_revisions')
|
||||
.whereIn('id', revisionIds)
|
||||
.whereNull('applied_at'); // should not re-apply revision that was already applied
|
||||
@@ -827,6 +838,10 @@ async function applySceneRevision(revisionIds) {
|
||||
|
||||
await knexOwner.transaction(async (trx) => {
|
||||
await Promise.all(revision.deltas.map(async (delta) => {
|
||||
if (delta.key === 'delete') {
|
||||
return applySceneDeleteDelta(revision.scene_id, delta, trx, reqUser);
|
||||
}
|
||||
|
||||
if ([
|
||||
'title',
|
||||
'description',
|
||||
@@ -858,11 +873,13 @@ async function applySceneRevision(revisionIds) {
|
||||
|
||||
await knexOwner('scenes_revisions')
|
||||
.where('id', revision.id)
|
||||
.update('applied_at', knex.fn.now());
|
||||
.update('applied_at', knexOwner.fn.now())
|
||||
.transacting(trx);
|
||||
|
||||
// await trx.commit();
|
||||
}).catch(async (error) => {
|
||||
logger.error(`Failed to apply revision ${revision.id} on scene ${revision.scene_id}: ${error.message}`);
|
||||
throw error;
|
||||
});
|
||||
}, Promise.resolve());
|
||||
}
|
||||
@@ -892,7 +909,19 @@ export async function reviewSceneRevision(revisionId, isApproved, { feedback },
|
||||
}
|
||||
|
||||
if (isApproved) {
|
||||
await applySceneRevision([revisionId]);
|
||||
try {
|
||||
await applySceneRevision([revisionId], reqUser);
|
||||
} catch (error) {
|
||||
await knexOwner('scenes_revisions')
|
||||
.where('id', revisionId)
|
||||
.update({
|
||||
approved: null,
|
||||
reviewed_at: null,
|
||||
reviewed_by: null,
|
||||
});
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -946,6 +975,10 @@ export async function createSceneRevision(sceneId, { edits, comment, apply }, re
|
||||
}).filter(Boolean));
|
||||
|
||||
const deltas = Object.entries(edits).map(([key, value]) => {
|
||||
if (key === 'delete') {
|
||||
return { key: 'delete' };
|
||||
}
|
||||
|
||||
if (baseScene[key] === value || typeof value === 'undefined') {
|
||||
return null;
|
||||
}
|
||||
@@ -984,6 +1017,6 @@ export async function createSceneRevision(sceneId, { edits, comment, apply }, re
|
||||
|
||||
if (['admin', 'editor'].includes(reqUser.role) && apply) {
|
||||
// don't keep the editor waiting for the revision to apply
|
||||
reviewSceneRevision(revisionEntry.id, true, {}, reqUser);
|
||||
reviewSceneRevision(revisionEntry.id, true, {}, reqUser).catch(() => {});
|
||||
}
|
||||
}
|
||||
|
||||
648028
src/tools/movies.json
648028
src/tools/movies.json
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user