Added new actor dialog to admin panel, removed create mode from actor edit page.

This commit is contained in:
2026-06-17 01:54:23 +02:00
parent 0af5b8d84c
commit 2595fcc365
7 changed files with 208 additions and 49 deletions

View File

@@ -25,7 +25,8 @@
.button {
display: inline-flex;
flex-shrink: 0;
align-items: stretch;
/* align-items: stretch; */
align-items: center;
box-sizing: border-box;
padding: .5rem;
border: none;

175
components/actors/add.vue Normal file
View File

@@ -0,0 +1,175 @@
<template>
<Dialog
title="Add actor"
@close="emit('close')"
>
<div
v-if="isSubmitted && newActor"
class="dialog-body"
>
<strong>Added '{{ newActor.name }}' (#{{ newActor.id }})</strong>
<ul
v-if="isSubmitted && newActor"
class="dialog-body options"
>
<li class="option">
<a
:href="`/actor/edit/${newActor.id}/${newActor.slug}`"
target="_blank"
class="link"
>Edit bio</a>
</li>
<li class="option">
<a
:href="`/actor/${newActor.id}/${newActor.slug}`"
target="_blank"
class="link"
>Go to actor</a>
</li>
<li class="option">
<span
class="link"
@click="newActor = null; isSubmitted = false;"
>Add another actor</span>
</li>
</ul>
</div>
<form
v-else
class="dialog-body"
@submit.prevent="addActor"
>
<input
ref="nameInput"
v-model="name"
class="input"
placeholder="Name"
required
>
<select
v-model="gender"
placeholder="Gender"
class="input"
>
<option value="female">Female</option>
<option value="male">Male</option>
<option value="transsexual">Transsexual</option>
<option value="other">Other</option>
</select>
<Checkbox
label="Allow global match"
:checked="allowGlobalMatch"
@change="(isChecked) => allowGlobalMatch = isChecked"
/>
<div class="dialog-actions">
<Ellipsis v-if="isSubmitting" />
<button
v-else
class="button button-primary"
>Add actor</button>
</div>
</form>
</Dialog>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import Dialog from '#/components/dialog/dialog.vue';
import Ellipsis from '#/components/loading/ellipsis.vue';
import Checkbox from '#/components/form/checkbox.vue';
import { post } from '#/src/api.js';
import events from '#/src/events.js';
const emit = defineEmits(['close']);
const nameInput = ref(null);
const isSubmitting = ref(false);
const isSubmitted = ref(false);
const name = ref(null);
const gender = ref('female');
const allowGlobalMatch = ref(true);
const newActor = ref(null);
onMounted(() => {
nameInput.value.focus();
});
async function addActor() {
isSubmitting.value = true;
try {
const { actor } = await post('/actors', {
actor: {
name: name.value,
gender: gender.value,
allowGlobalMatch: allowGlobalMatch.value,
},
}, {
successFeedback: 'Actor has been added.',
appendErrorMessage: true,
});
name.value = null;
gender.value = null;
allowGlobalMatch.value = true;
newActor.value = actor;
} catch (error) {
events.emit('feedback', {
type: 'error',
message: error.message,
});
}
isSubmitting.value = false;
isSubmitted.value = true;
}
</script>
<style scoped>
.dialog-body {
width: 20rem;
max-width: 100%;
box-sizing: border-box;
padding: 1rem;
gap: 1.25rem;
}
.load-container {
width: 100%;
display: inline-flex;
justify-content: center;
align-items: center;
padding: 0;
height: 2rem;
}
.options {
padding: 0 1.5rem;
margin: 0;
}
.option {
cursor: pointer;
}
.dialog-actions {
margin-top: .5rem;
}
.button-primary {
width: 100%;
}
</style>

View File

@@ -373,8 +373,6 @@ import {
post,
} from '#/src/api.js';
import events from '#/src/events.js';
const pageContext = inject('pageContext');
const user = pageContext.user;
@@ -642,48 +640,7 @@ const groupMap = {
weight: 'size',
};
async function submitCreate() {
submitting.value = true;
try {
const newActor = {
actor: Object.fromEntries(Array.from(['name', 'gender']).flatMap((key) => {
if (!edits.value[key]) {
throw new Error(`Missing ${key}`);
}
if (edits.value[key] && typeof edits.value[key] === 'object' && !Array.isArray(edits.value[key])) {
return Object.entries(edits.value[key]).map(([valueKey, value]) => [keyMap[key]?.[valueKey] || valueKey, value]);
}
return [[key, edits.value[key]]];
})),
comment: comment.value,
};
const { actor: createdActor } = await post('/actors', newActor, {
successFeedback: 'Actor has been added.',
appendErrorMessage: true,
});
actor.value = createdActor;
} catch (error) {
events.emit('feedback', {
type: 'error',
message: error.message,
});
}
submitting.value = false;
submitted.value = true;
}
async function submit() {
if (!actor.value) {
submitCreate();
return;
}
try {
submitting.value = true;

View File

@@ -2,7 +2,7 @@
// import { redirect } from 'vike/abort'; /* eslint-disable-line import/extensions */
import { match } from 'path-to-regexp';
const path = '/actor/(edit|new)/:actorId?/:actorSlug?';
const path = '/actor/edit/:actorId/:actorSlug?';
const urlMatch = match(path, { decode: decodeURIComponent });
export default (pageContext) => {

View File

@@ -21,6 +21,11 @@
:disabled="selectedActors.size === 0"
@click="showMergeDialog = true"
><Icon icon="make-group" />Merge</button>
<button
class="button"
@click="showAddDialog = true"
><Icon icon="plus3" />Add actor</button>
</div>
</div>
@@ -84,6 +89,17 @@
@change="(isChecked) => selectActors(actor, isChecked, index)"
/>
<a
:href="`/actor/edit/${actor.id}/${actor.slug}`"
target="_blank"
>
<Icon
v-tooltip="'Edit bio'"
icon="pencil5"
class="actor-action action-merge"
/>
</a>
<Icon
v-tooltip="'Merge'"
icon="make-group"
@@ -104,11 +120,16 @@
</tbody>
</table>
<Merge
<MergeActors
v-if="showMergeDialog"
:actors="activeActor ? [activeActor] : actors.filter((actor) => selectedActors.has(actor.id))"
@close="showMergeDialog = false; activeActor = null;"
/>
<AddActor
v-if="showAddDialog"
@close="showAddDialog = false"
/>
</Admin>
</template>
@@ -117,7 +138,8 @@ import { ref, inject, onMounted } from 'vue';
import Admin from '#/components/admin/admin.vue';
import Checkbox from '#/components/form/checkbox.vue';
import Merge from '#/components/actors/merge.vue';
import MergeActors from '#/components/actors/merge.vue';
import AddActor from '#/components/actors/add.vue';
import getPath from '#/src/get-path.js';
import navigate from '#/src/navigate.js';
@@ -133,6 +155,7 @@ const actorQuery = ref(urlParsed.search.q || null);
const lastSelectedIndex = ref(null);
const holdingShift = ref(false);
const showMergeDialog = ref(false);
const showAddDialog = ref(false);
function selectActors(selectedActor, isChecked, index) {
const [start, end] = holdingShift.value
@@ -155,7 +178,7 @@ function selectActors(selectedActor, isChecked, index) {
}
async function searchActors() {
navigate('/admin/actors', { q: actorQuery.value }, { redirect: true });
navigate('/admin/actors', { q: actorQuery.value || undefined }, { redirect: true });
}
onMounted(() => {
@@ -192,7 +215,9 @@ onMounted(() => {
.header-actions {
display: flex;
align-items: center;
justify-content: flex-end;
gap: .5rem;
flex-grow: 1;
}
.actors-header tr {

View File

@@ -12,7 +12,7 @@ export default async function onBeforeRender(pageContext) {
query: pageContext.urlParsed.search.q,
}, {
limit: 100,
// order: pageContext.urlParsed.search.order?.split('.') || ['likes', 'desc'],
order: pageContext.urlParsed.search.order?.split('.') || ['likes', 'desc'],
}, pageContext.user);
return {

View File

@@ -548,6 +548,7 @@ function curateActorEntry(actor, context) {
slug: slugify(actor.name),
entry_id: nanoid(), // allows for manual creation of multiple actors with the same name
gender: actor.gender,
allow_global_match: actor.allowGlobalMatch,
comment: context?.comment,
};
}