traxxx-web/pages/scene/edit/+Page.vue

493 lines
9.5 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.toSorted((tagA, tagB) => tagA.name.localeCompare(tagB.name)),
},
{
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>