Added actors admin panel with bulk merge. Fixed merge failing if source and target actor have conflicting network profiles.
This commit is contained in:
@@ -384,7 +384,7 @@
|
||||
|
||||
<Merge
|
||||
v-if="showMergeDialog"
|
||||
:actor="actor"
|
||||
:actors="[actor]"
|
||||
@close="showMergeDialog = false"
|
||||
/>
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<Dialog
|
||||
:title="`Merge '${actor.name}'`"
|
||||
:title="`Merge ${actors.length === 1 ? `'${actors[0].name}'` : `${actors.length} actors`}`"
|
||||
@close="emit('close')"
|
||||
>
|
||||
<div
|
||||
@@ -12,9 +12,9 @@
|
||||
<ul class="options">
|
||||
<li class="option">
|
||||
<a
|
||||
:href="`/actor/${actor?.id}/${actor?.slug}`"
|
||||
href=""
|
||||
class="link"
|
||||
>Reload #{{ actor.id }} {{ actor.name }} ({{ actor.entity.name }})</a>
|
||||
>Reload page</a>
|
||||
</li>
|
||||
|
||||
<li class="option">
|
||||
@@ -30,7 +30,18 @@
|
||||
v-else
|
||||
class="dialog-body"
|
||||
>
|
||||
<strong class="source">#{{ actor.id }} {{ actor.name }}<span v-if="actor.entity"> ({{ actor.entity.name }})</span></strong>
|
||||
<ul class="actors nolist">
|
||||
<li
|
||||
v-for="actor in actors"
|
||||
:key="`actor-${actor.id}`"
|
||||
class="actor"
|
||||
>
|
||||
<span class="source">
|
||||
<strong class="source-name">{{ actor.name }}<template v-if="actor.entity"> ({{ actor.entity.name }})</template></strong>
|
||||
<span class="source-id">#{{ actor.id }}</span>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<span class="path">merging into</span>
|
||||
|
||||
@@ -88,9 +99,10 @@
|
||||
|
||||
<button
|
||||
v-else
|
||||
v-tooltip="sourceTargetConflict && 'Cannot merge actor profile into itself'"
|
||||
type="submit"
|
||||
class="button button-primary"
|
||||
:disabled="!targetActor"
|
||||
:disabled="!targetActor || sourceTargetConflict"
|
||||
@click="merge"
|
||||
>Merge</button>
|
||||
</div>
|
||||
@@ -99,7 +111,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
|
||||
import Dialog from '#/components/dialog/dialog.vue';
|
||||
import Ellipsis from '#/components/loading/ellipsis.vue';
|
||||
@@ -107,9 +119,9 @@ import Ellipsis from '#/components/loading/ellipsis.vue';
|
||||
import { get, post } from '#/src/api.js';
|
||||
|
||||
const props = defineProps({
|
||||
actor: {
|
||||
type: Object,
|
||||
default: null,
|
||||
actors: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
});
|
||||
|
||||
@@ -122,9 +134,11 @@ const actorResults = ref([]);
|
||||
const submitted = ref(false);
|
||||
const merged = ref(false);
|
||||
|
||||
const sourceTargetConflict = computed(() => props.actors.some((actor) => actor.id === targetActor.value?.id));
|
||||
|
||||
async function searchActors() {
|
||||
const res = await get('/actors', {
|
||||
q: actorQuery.value.charAt(0) === '#' ? actorQuery.value : `${actorQuery.value}*`, // return partial matches
|
||||
q: actorQuery.value, // return partial matches
|
||||
limit: 10,
|
||||
global: true,
|
||||
});
|
||||
@@ -132,12 +146,24 @@ async function searchActors() {
|
||||
actorResults.value = res.actors;
|
||||
}
|
||||
|
||||
function getActorNames() {
|
||||
if (props.actors.length > 1) {
|
||||
return `${props.actors.length} actors`;
|
||||
}
|
||||
|
||||
if (props.actors[0].entity) {
|
||||
return `${props.actors[0].name} (${props.actors[0].entity.name})`;
|
||||
}
|
||||
|
||||
return props.actors[0].name;
|
||||
}
|
||||
|
||||
async function merge() {
|
||||
submitted.value = true;
|
||||
|
||||
await post(`/actors/${targetActor.value.id}/merge/${props.actor.id}`, null, {
|
||||
successFeedback: `Merged ${props.actor.entity ? `${props.actor.name} (${props.actor.entity.name})` : props.actor.name} into ${targetActor.value.name}`,
|
||||
errorFeedback: `Failed to merge ${props.actor.entity ? `${props.actor.name} (${props.actor.entity.name})` : props.actor.name} into ${targetActor.value.name}`,
|
||||
await post(`/actors/${targetActor.value.id}/merge/${props.actors.map((actor) => actor.id).join(',')}`, null, {
|
||||
successFeedback: `Merged ${getActorNames()} into ${targetActor.value.name}`,
|
||||
errorFeedback: `Failed to merge ${getActorNames()} into ${targetActor.value.name}`,
|
||||
appendErrorMessage: true,
|
||||
});
|
||||
|
||||
@@ -167,6 +193,7 @@ onMounted(() => {
|
||||
box-sizing: border-box;
|
||||
padding: 1rem;
|
||||
gap: 1rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.dialog-actions {
|
||||
@@ -175,6 +202,15 @@ onMounted(() => {
|
||||
}
|
||||
}
|
||||
|
||||
.actors {
|
||||
max-height: 15rem;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.actor {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.input {
|
||||
width: 100%;
|
||||
}
|
||||
@@ -199,11 +235,20 @@ onMounted(() => {
|
||||
}
|
||||
}
|
||||
|
||||
.source-id,
|
||||
.target-id {
|
||||
font-family: monospace;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.source {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.source-name {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.results {
|
||||
padding: .25rem 0;
|
||||
}
|
||||
|
||||
@@ -128,6 +128,10 @@ const favorited = ref(props.actor.stashes.some((actorStash) => actorStash.id ===
|
||||
:deep(.bookmarks) .icon:not(.favorited):not(:hover) {
|
||||
fill: var(--text-light);
|
||||
}
|
||||
|
||||
.menu {
|
||||
fill: var(--text-light);
|
||||
}
|
||||
}
|
||||
|
||||
&.unstashed {
|
||||
@@ -135,6 +139,14 @@ const favorited = ref(props.actor.stashes.some((actorStash) => actorStash.id ===
|
||||
}
|
||||
}
|
||||
|
||||
.menu {
|
||||
padding: .5rem .75rem;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
fill: var(--highlight-strong-20);
|
||||
}
|
||||
|
||||
.label {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
@@ -2,6 +2,14 @@
|
||||
<div class="page">
|
||||
<nav class="nav">
|
||||
<ul class="nav-items nolist">
|
||||
<li class="nav-item">
|
||||
<a
|
||||
href="/admin/actors"
|
||||
class="nav-link nolink"
|
||||
:class="{ active: pageContext.routeParams.section === 'actors' }"
|
||||
>Actors</a>
|
||||
</li>
|
||||
|
||||
<li class="nav-item">
|
||||
<a
|
||||
href="/admin/revisions/scenes"
|
||||
|
||||
Reference in New Issue
Block a user