493 lines
9.4 KiB
Vue
493 lines
9.4 KiB
Vue
<template>
|
|
<div class="editor">
|
|
<p
|
|
v-if="submitted"
|
|
class="submitted"
|
|
>
|
|
<template v-if="apply">Your revision has been submitted. Thank you for your contribution!</template>
|
|
<template v-else>Your revision has been submitted for review. Thank you for your contribution!</template>
|
|
|
|
<ul>
|
|
<li>
|
|
<a
|
|
:href="`/scene/${scene.id}/${scene.slug}`"
|
|
class="link"
|
|
>Return to scene</a>
|
|
</li>
|
|
|
|
<li>
|
|
<a
|
|
:href="`/scene/edit/${scene.id}`"
|
|
class="link"
|
|
>Make another edit</a>
|
|
</li>
|
|
|
|
<li>
|
|
<a
|
|
:href="`/scene/revisions/${scene.id}/${scene.slug}`"
|
|
class="link"
|
|
>Go to scene revisions</a>
|
|
</li>
|
|
|
|
<li>
|
|
<a
|
|
:href="`/user/${user.username}/revisions`"
|
|
class="link"
|
|
>Go to user revisions</a>
|
|
</li>
|
|
|
|
<li v-if="user.role !== 'user'">
|
|
<a
|
|
href="/admin/revisions"
|
|
class="link"
|
|
>Go to revisions admin</a>
|
|
</li>
|
|
</ul>
|
|
</p>
|
|
|
|
<form
|
|
v-else
|
|
@submit.prevent
|
|
>
|
|
<div class="editor-header">
|
|
<h2 class="heading ellipsis">Edit scene #{{ scene.id }}</h2>
|
|
|
|
<a
|
|
:href="`/scene/${scene.id}/${scene.slug}`"
|
|
target="_blank"
|
|
class="link noshrink"
|
|
>Go to scene</a>
|
|
</div>
|
|
|
|
<ul class="nolist">
|
|
<li
|
|
v-for="item in fields"
|
|
:key="`item-${item.key}`"
|
|
class="row"
|
|
>
|
|
<div class="item-header">
|
|
<div class="key">{{ item.label || item.key }}</div>
|
|
|
|
<div class="item-actions">
|
|
<Icon
|
|
v-if="!item.forced"
|
|
icon="pencil5"
|
|
:class="{ active: editing.has(item.key) }"
|
|
@click="toggleField(item)"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div
|
|
class="value"
|
|
:class="{ disabled: !editing.has(item.key) }"
|
|
>
|
|
<EditActors
|
|
v-if="item.type === 'actors'"
|
|
:scene="scene"
|
|
:item="item"
|
|
:edits="edits"
|
|
:editing="editing"
|
|
@actors="(actors) => { edits.actors = actors; }"
|
|
/>
|
|
|
|
<EditTags
|
|
v-if="item.type === 'tags'"
|
|
:scene="scene"
|
|
:item="item"
|
|
:edits="edits"
|
|
:editing="editing"
|
|
@tags="(tags) => { edits.tags = tags; }"
|
|
/>
|
|
|
|
<EditMovies
|
|
v-if="item.type === 'movies'"
|
|
:scene="scene"
|
|
:item="item"
|
|
:edits="edits"
|
|
:editing="editing"
|
|
@movies="(movies) => { edits.movies = movies; }"
|
|
/>
|
|
|
|
<input
|
|
v-if="item.type === 'string'"
|
|
:value="edits[item.key] || item.value"
|
|
class="string input"
|
|
:disabled="!editing.has(item.key)"
|
|
@input="setValue(item, $event)"
|
|
>
|
|
|
|
<textarea
|
|
v-if="item.type === 'text'"
|
|
:value="edits[item.key] || item.value"
|
|
:placeholder="item.placeholder"
|
|
rows="3"
|
|
class="text input"
|
|
:disabled="!editing.has(item.key)"
|
|
@input="setValue(item, $event)"
|
|
/>
|
|
|
|
<input
|
|
v-if="item.type === 'date'"
|
|
type="datetime-local"
|
|
:value="edits[item.key] || item.value"
|
|
class="date input"
|
|
:disabled="!editing.has(item.key)"
|
|
@input="setValue(item, $event)"
|
|
>
|
|
|
|
<div
|
|
v-if="item.type === 'duration'"
|
|
class="duration"
|
|
>
|
|
<input
|
|
type="number"
|
|
class="input"
|
|
:value="edits[item.key]?.[0] ?? item.value[0]"
|
|
min="0"
|
|
max="100"
|
|
:disabled="!editing.has(item.key)"
|
|
@change="setDuration('h', $event)"
|
|
>H
|
|
|
|
<input
|
|
type="number"
|
|
class="input"
|
|
:value="edits[item.key]?.[1] ?? item.value[1]"
|
|
min="0"
|
|
max="59"
|
|
:disabled="!editing.has(item.key)"
|
|
@change="setDuration('m', $event)"
|
|
>M
|
|
|
|
<input
|
|
type="number"
|
|
class="input"
|
|
:value="edits[item.key]?.[2] ?? item.value[2]"
|
|
min="0"
|
|
max="59"
|
|
:disabled="!editing.has(item.key)"
|
|
@change="setDuration('s', $event)"
|
|
>S
|
|
</div>
|
|
</div>
|
|
</li>
|
|
</ul>
|
|
|
|
<div class="editor-footer">
|
|
<div class="comment">
|
|
<textarea
|
|
v-model="comment"
|
|
rows="3"
|
|
placeholder="Please provide verifiable information supporting your edits."
|
|
class="text input"
|
|
/>
|
|
</div>
|
|
|
|
<div class="editor-actions">
|
|
<Checkbox
|
|
v-if="user.role !== 'user'"
|
|
label="Approve and apply immediately"
|
|
:checked="apply"
|
|
:disabled="editing.size === 0"
|
|
@change="(checked) => apply = checked"
|
|
/>
|
|
|
|
<!-- we don't want the return key to submit the form -->
|
|
<button
|
|
class="button button-primary"
|
|
type="button"
|
|
:disabled="editing.size === 0"
|
|
@click="submit"
|
|
>
|
|
<template v-if="apply">Submit</template>
|
|
<template v-else>Submit for review</template>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, computed, inject } from 'vue';
|
|
import { format } from 'date-fns';
|
|
|
|
import EditActors from '#/components/edit/actors.vue';
|
|
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';
|
|
|
|
const pageContext = inject('pageContext');
|
|
|
|
const user = pageContext.user;
|
|
const scene = ref(pageContext.pageProps.scene);
|
|
|
|
const fields = computed(() => [
|
|
{
|
|
key: 'actors',
|
|
type: 'actors',
|
|
value: scene.value.actors,
|
|
},
|
|
{
|
|
key: 'tags',
|
|
type: 'tags',
|
|
value: scene.value.tags,
|
|
},
|
|
{
|
|
key: 'movies',
|
|
type: 'movies',
|
|
value: scene.value.movies,
|
|
},
|
|
{
|
|
key: 'title',
|
|
type: 'string',
|
|
value: scene.value.title,
|
|
},
|
|
{
|
|
key: 'description',
|
|
type: 'text',
|
|
value: scene.value.description,
|
|
},
|
|
{
|
|
key: 'date',
|
|
type: 'date',
|
|
value: scene.value.date
|
|
? format(scene.value.date, 'yyyy-MM-dd hh:mm')
|
|
: null,
|
|
},
|
|
{
|
|
key: 'duration',
|
|
type: 'duration',
|
|
value: [Math.floor(scene.value.duration / 3600), Math.floor((scene.value.duration % 3600) / 60), scene.value.duration % 60],
|
|
},
|
|
{
|
|
key: 'productionDate',
|
|
label: 'production date',
|
|
type: 'date',
|
|
value: scene.value.productionDate
|
|
? format(scene.value.productionDate, 'yyyy-MM-dd hh:mm')
|
|
: null,
|
|
},
|
|
...(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,
|
|
}]),
|
|
]);
|
|
|
|
const editing = ref(new Set());
|
|
const edits = ref({});
|
|
const comment = ref(null);
|
|
const apply = ref(user.role !== 'user');
|
|
const submitted = ref(false);
|
|
|
|
function toggleField(item) {
|
|
if (editing.value.has(item.key)) {
|
|
editing.value.delete(item.key);
|
|
delete edits.value[item.key];
|
|
|
|
return;
|
|
}
|
|
|
|
editing.value.add(item.key);
|
|
|
|
if (Array.isArray(item.value)) {
|
|
edits.value[item.key] = item.value.map((value) => value.hash || value.id);
|
|
return;
|
|
}
|
|
|
|
edits.value[item.key] = item.value;
|
|
}
|
|
|
|
function setValue(item, event) {
|
|
edits.value[item.key] = event.target.value;
|
|
}
|
|
|
|
const timeUnits = ['h', 'm', 's'];
|
|
|
|
function setDuration(unit, event) {
|
|
edits.value.duration[timeUnits.indexOf(unit)] = Number(event.target.value);
|
|
}
|
|
|
|
async function submit() {
|
|
try {
|
|
await post('/revisions', {
|
|
sceneId: scene.value.id,
|
|
edits: {
|
|
...edits.value,
|
|
duration: edits.value.duration
|
|
? (((edits.value.duration[0] || 0) * 3600) + ((edits.value.duration[1] || 0) * 60) + (edits.value.duration[2] || 0)) || null
|
|
: undefined,
|
|
},
|
|
comment: comment.value,
|
|
apply: apply.value,
|
|
}, {
|
|
successFeedback: 'Your revision has been submitted for approval.',
|
|
appendErrorMessage: true,
|
|
});
|
|
|
|
editing.value = new Set();
|
|
edits.value = {};
|
|
comment.value = null;
|
|
|
|
submitted.value = true;
|
|
|
|
// scene.value = await get(`/scenes/${scene.value.id}`);
|
|
} catch (error) {
|
|
// do nothing
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.editor {
|
|
flex-grow: 1;
|
|
background: var(--background-dark-10);
|
|
}
|
|
|
|
.editor-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: 1rem;
|
|
}
|
|
|
|
.heading {
|
|
margin: 0;
|
|
}
|
|
|
|
.row {
|
|
display: flex;
|
|
align-items: center;
|
|
padding: .25rem 1rem;
|
|
}
|
|
|
|
.key {
|
|
width: 8rem;
|
|
text-transform: capitalize;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.input {
|
|
background: var(--background);
|
|
}
|
|
|
|
.item-header {
|
|
display: flex;
|
|
align-items: center;
|
|
}
|
|
|
|
.value {
|
|
flex-grow: 1;
|
|
|
|
.input {
|
|
width: 100%;
|
|
|
|
&:disabled {
|
|
color: var(--glass-strong-10);
|
|
background: none;
|
|
border: solid 1px var(--glass-weak-30);
|
|
}
|
|
}
|
|
|
|
.duration {
|
|
.input {
|
|
width: 5rem;
|
|
margin-right: .25rem;
|
|
|
|
&:not(:first-child) {
|
|
margin-left: .75rem;
|
|
}
|
|
}
|
|
}
|
|
|
|
&.disabled {
|
|
pointer-events: none;
|
|
}
|
|
}
|
|
|
|
.item-actions {
|
|
.icon {
|
|
padding: .25rem 1rem;
|
|
fill: var(--glass);
|
|
overflow: hidden;
|
|
|
|
&:hover {
|
|
cursor: pointer;
|
|
fill: var(--text);
|
|
}
|
|
|
|
&.active {
|
|
fill: var(--primary);
|
|
}
|
|
}
|
|
}
|
|
|
|
.editor-footer {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
gap: 1rem;
|
|
padding: 1rem 1rem 0 1rem;
|
|
border-top: solid 1px var(--primary-light-30);
|
|
margin: 1rem 0;
|
|
}
|
|
|
|
.comment {
|
|
width: 100%;
|
|
flex-shrink: 0;
|
|
|
|
.input {
|
|
width: 100%;
|
|
resize: vertical;
|
|
}
|
|
}
|
|
|
|
.editor-actions {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
gap: 1.5rem;
|
|
margin: .5rem 0;
|
|
|
|
.button {
|
|
padding: .5rem 1rem;
|
|
font-size: 1.1rem;
|
|
}
|
|
}
|
|
|
|
.submitted {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
padding: 1rem;
|
|
font-weight: bold;
|
|
line-height: 1.5;
|
|
}
|
|
|
|
@media(--small) {
|
|
.row {
|
|
flex-direction: column;
|
|
align-items: stretch;
|
|
margin-bottom: .25rem;
|
|
}
|
|
|
|
.item-header {
|
|
margin-bottom: .25rem;
|
|
}
|
|
|
|
.key {
|
|
flex-grow: 1;
|
|
}
|
|
}
|
|
</style>
|