Added delete option to scene edits.
This commit is contained in:
@@ -119,12 +119,22 @@
|
|||||||
|
|
||||||
.button-cancel {
|
.button-cancel {
|
||||||
background: none;
|
background: none;
|
||||||
color: var(--glass);
|
color: var(--error);
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
|
box-shadow: none;
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
fill: var(--error);
|
||||||
|
}
|
||||||
|
|
||||||
&:hover:not(:disabled) {
|
&:hover:not(:disabled) {
|
||||||
color: var(--error);
|
color: var(--text-light);
|
||||||
|
background: var(--error);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
fill: var(--text-light);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&:disabled {
|
&:disabled {
|
||||||
|
|||||||
@@ -378,6 +378,8 @@ async function reviewRevision(revision, isApproved) {
|
|||||||
await post(`/revisions/${domain}/${revision.id}/reviews`, {
|
await post(`/revisions/${domain}/${revision.id}/reviews`, {
|
||||||
isApproved,
|
isApproved,
|
||||||
feedback: feedbacks.value[revision.id],
|
feedback: feedbacks.value[revision.id],
|
||||||
|
}, {
|
||||||
|
appendErrorMessage: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const updatedRevision = await get(`/revisions/${domain}/${revision.id}`, {
|
const updatedRevision = await get(`/revisions/${domain}/${revision.id}`, {
|
||||||
|
|||||||
@@ -28,6 +28,10 @@ export async function onBeforeRender(pageContext) {
|
|||||||
restriction: pageContext.restriction,
|
restriction: pageContext.restriction,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!scene) {
|
||||||
|
throw render(404, `Cannot find scene '${pageContext.routeParams.sceneId}'.`);
|
||||||
|
}
|
||||||
|
|
||||||
const [campaigns, tagIds] = await Promise.all([
|
const [campaigns, tagIds] = await Promise.all([
|
||||||
getRandomCampaigns([
|
getRandomCampaigns([
|
||||||
{
|
{
|
||||||
@@ -44,10 +48,6 @@ export async function onBeforeRender(pageContext) {
|
|||||||
], 'tags', true),
|
], 'tags', true),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (!scene) {
|
|
||||||
throw render(404, `Cannot find scene '${pageContext.routeParams.sceneId}'.`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
pageContext: {
|
pageContext: {
|
||||||
title: getTitle(scene),
|
title: getTitle(scene),
|
||||||
|
|||||||
@@ -73,7 +73,7 @@
|
|||||||
v-if="item.note"
|
v-if="item.note"
|
||||||
v-tooltip="item.note"
|
v-tooltip="item.note"
|
||||||
icon="info2"
|
icon="info2"
|
||||||
class="item-note"
|
class="item-note noselect"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -81,6 +81,7 @@
|
|||||||
<Icon
|
<Icon
|
||||||
v-if="!item.forced"
|
v-if="!item.forced"
|
||||||
icon="pencil5"
|
icon="pencil5"
|
||||||
|
class="noselect"
|
||||||
:class="{ active: editing.has(item.key) }"
|
:class="{ active: editing.has(item.key) }"
|
||||||
@click="toggleField(item)"
|
@click="toggleField(item)"
|
||||||
/>
|
/>
|
||||||
@@ -134,6 +135,15 @@
|
|||||||
:disabled="!editing.has(item.key)"
|
: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
|
<div
|
||||||
v-if="item.type === 'date'"
|
v-if="item.type === 'date'"
|
||||||
class="date"
|
class="date"
|
||||||
@@ -210,9 +220,10 @@
|
|||||||
<div class="editor-actions">
|
<div class="editor-actions">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
v-if="user.role !== 'user'"
|
v-if="user.role !== 'user'"
|
||||||
|
v-tooltip="isApplyDisabled && editing.has('delete') ? 'Delete must be approved by an admin' : null"
|
||||||
label="Approve and apply immediately"
|
label="Approve and apply immediately"
|
||||||
:checked="apply"
|
:checked="apply"
|
||||||
:disabled="editing.size === 0"
|
:disabled="isApplyDisabled"
|
||||||
@change="(checked) => apply = checked"
|
@change="(checked) => apply = checked"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -241,10 +252,7 @@ import EditTags from '#/components/edit/tags.vue';
|
|||||||
import EditMovies from '#/components/edit/movies.vue';
|
import EditMovies from '#/components/edit/movies.vue';
|
||||||
import Checkbox from '#/components/form/checkbox.vue';
|
import Checkbox from '#/components/form/checkbox.vue';
|
||||||
|
|
||||||
import {
|
import { post } from '#/src/api.js';
|
||||||
// get,
|
|
||||||
post,
|
|
||||||
} from '#/src/api.js';
|
|
||||||
|
|
||||||
const pageContext = inject('pageContext');
|
const pageContext = inject('pageContext');
|
||||||
|
|
||||||
@@ -310,12 +318,20 @@ const fields = computed(() => [
|
|||||||
},
|
},
|
||||||
...(user.role === 'user'
|
...(user.role === 'user'
|
||||||
? []
|
? []
|
||||||
: [{
|
: [
|
||||||
key: 'comment',
|
{
|
||||||
type: 'text',
|
key: 'comment',
|
||||||
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.',
|
type: 'text',
|
||||||
value: scene.value.comment,
|
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) {
|
function simplifyArray(field) {
|
||||||
@@ -332,6 +348,9 @@ const comment = ref(null);
|
|||||||
const apply = ref(user.role !== 'user');
|
const apply = ref(user.role !== 'user');
|
||||||
const submitted = ref(false);
|
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 = {
|
const keyMap = {
|
||||||
date: {
|
date: {
|
||||||
date: 'date',
|
date: 'date',
|
||||||
@@ -359,6 +378,14 @@ function setDuration(unit, event) {
|
|||||||
edits.value.duration[timeUnits.indexOf(unit)] = Number(event.target.value);
|
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() {
|
async function submit() {
|
||||||
try {
|
try {
|
||||||
await post('/revisions/scenes', {
|
await post('/revisions/scenes', {
|
||||||
@@ -417,6 +444,10 @@ async function submit() {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: .25rem 1rem;
|
padding: .25rem 1rem;
|
||||||
|
|
||||||
|
.value.disabled {
|
||||||
|
color: var(--glass);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.key {
|
.key {
|
||||||
@@ -488,7 +519,7 @@ async function submit() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-note{
|
.item-note {
|
||||||
fill: var(--glass);
|
fill: var(--glass);
|
||||||
padding: .5rem .75rem;
|
padding: .5rem .75rem;
|
||||||
cursor: help;
|
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 {
|
.editor-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -540,6 +590,27 @@ async function submit() {
|
|||||||
line-height: 1.5;
|
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) {
|
@media(--small) {
|
||||||
.row {
|
.row {
|
||||||
flex-direction: column;
|
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')
|
const revisions = await knexOwner('scenes_revisions')
|
||||||
.whereIn('id', revisionIds)
|
.whereIn('id', revisionIds)
|
||||||
.whereNull('applied_at'); // should not re-apply revision that was already applied
|
.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 knexOwner.transaction(async (trx) => {
|
||||||
await Promise.all(revision.deltas.map(async (delta) => {
|
await Promise.all(revision.deltas.map(async (delta) => {
|
||||||
|
if (delta.key === 'delete') {
|
||||||
|
return applySceneDeleteDelta(revision.scene_id, delta, trx, reqUser);
|
||||||
|
}
|
||||||
|
|
||||||
if ([
|
if ([
|
||||||
'title',
|
'title',
|
||||||
'description',
|
'description',
|
||||||
@@ -858,11 +873,13 @@ async function applySceneRevision(revisionIds) {
|
|||||||
|
|
||||||
await knexOwner('scenes_revisions')
|
await knexOwner('scenes_revisions')
|
||||||
.where('id', revision.id)
|
.where('id', revision.id)
|
||||||
.update('applied_at', knex.fn.now());
|
.update('applied_at', knexOwner.fn.now())
|
||||||
|
.transacting(trx);
|
||||||
|
|
||||||
// await trx.commit();
|
// await trx.commit();
|
||||||
}).catch(async (error) => {
|
}).catch(async (error) => {
|
||||||
logger.error(`Failed to apply revision ${revision.id} on scene ${revision.scene_id}: ${error.message}`);
|
logger.error(`Failed to apply revision ${revision.id} on scene ${revision.scene_id}: ${error.message}`);
|
||||||
|
throw error;
|
||||||
});
|
});
|
||||||
}, Promise.resolve());
|
}, Promise.resolve());
|
||||||
}
|
}
|
||||||
@@ -892,7 +909,19 @@ export async function reviewSceneRevision(revisionId, isApproved, { feedback },
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isApproved) {
|
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));
|
}).filter(Boolean));
|
||||||
|
|
||||||
const deltas = Object.entries(edits).map(([key, value]) => {
|
const deltas = Object.entries(edits).map(([key, value]) => {
|
||||||
|
if (key === 'delete') {
|
||||||
|
return { key: 'delete' };
|
||||||
|
}
|
||||||
|
|
||||||
if (baseScene[key] === value || typeof value === 'undefined') {
|
if (baseScene[key] === value || typeof value === 'undefined') {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -984,6 +1017,6 @@ export async function createSceneRevision(sceneId, { edits, comment, apply }, re
|
|||||||
|
|
||||||
if (['admin', 'editor'].includes(reqUser.role) && apply) {
|
if (['admin', 'editor'].includes(reqUser.role) && apply) {
|
||||||
// don't keep the editor waiting for the revision to 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