Added elaborate template switching.
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
<Teleport to="#container">
|
||||
<div
|
||||
class="dialog-container"
|
||||
@click="emit('close')"
|
||||
@click="close"
|
||||
>
|
||||
<div
|
||||
class="dialog"
|
||||
@@ -14,11 +14,11 @@
|
||||
<Icon
|
||||
icon="cross2"
|
||||
class="dialog-close"
|
||||
@click="emit('close')"
|
||||
@click="close"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<slot />
|
||||
<slot @event="({ type, data }) => emit('event', { type, data })" />
|
||||
</div>
|
||||
</div>
|
||||
</Teleport>
|
||||
@@ -27,14 +27,24 @@
|
||||
<script setup>
|
||||
import { onMounted } from 'vue';
|
||||
|
||||
defineProps({
|
||||
const props = defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
confirmClose: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['open', 'close']);
|
||||
const emit = defineEmits(['open', 'close', 'event']);
|
||||
|
||||
function close() {
|
||||
if (!props.confirmClose || confirm('You have unchanged changes, are you sure you want to close the dialog?')) { // eslint-disable-line no-restricted-globals, no-alert
|
||||
emit('close');
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => emit('open'));
|
||||
</script>
|
||||
|
||||
@@ -1,7 +1,30 @@
|
||||
<template>
|
||||
<Dialog title="Edit summary template">
|
||||
<Dialog
|
||||
title="Edit summary template"
|
||||
:confirm-close="hasChanged"
|
||||
>
|
||||
<div class="dialog-body">
|
||||
<ul class="templates nolist">
|
||||
<li
|
||||
v-for="storedTemplate in templates"
|
||||
:key="`template-${storedTemplate.id}`"
|
||||
class="template-key"
|
||||
:class="{ selected: selectedTemplate === storedTemplate.id }"
|
||||
@click="selectTemplate(storedTemplate.id)"
|
||||
>{{ storedTemplate.name }}</li>
|
||||
|
||||
<li
|
||||
class="template-key add"
|
||||
@click="add"
|
||||
>
|
||||
<Icon
|
||||
icon="plus3"
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<textarea
|
||||
ref="input"
|
||||
v-model="template"
|
||||
height="3"
|
||||
class="input edit"
|
||||
@@ -26,39 +49,54 @@
|
||||
<button
|
||||
class="button"
|
||||
@click="reset"
|
||||
>Reset</button>
|
||||
>Default</button>
|
||||
</div>
|
||||
|
||||
<div class="actions save">
|
||||
<!--
|
||||
<form
|
||||
class="actions save"
|
||||
@submit.prevent="save"
|
||||
>
|
||||
<Icon
|
||||
v-if="selectedTemplate"
|
||||
icon="bin"
|
||||
class="remove"
|
||||
@click="remove"
|
||||
/>
|
||||
|
||||
<input
|
||||
v-model="templateName"
|
||||
class="input"
|
||||
placeholder="Name"
|
||||
required
|
||||
>
|
||||
-->
|
||||
|
||||
<button
|
||||
class="button"
|
||||
@click="save"
|
||||
>Save</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { ref, inject } from 'vue';
|
||||
import { parse } from 'yaml';
|
||||
import Cookies from 'js-cookie';
|
||||
|
||||
// import slugify from '#/utils/slugify.js';
|
||||
import events from '#/src/events.js';
|
||||
import { get, post, del } from '#/src/api.js';
|
||||
import processSummaryTemplate from '#/utils/process-summary-template.js';
|
||||
|
||||
import Dialog from '#/components/dialog/dialog.vue';
|
||||
|
||||
import defaultTemplate from '#/assets/summary.yaml?raw'; // eslint-disable-line import/no-unresolved
|
||||
|
||||
const emit = defineEmits(['event']);
|
||||
|
||||
const pageContext = inject('pageContext');
|
||||
|
||||
const cookies = Cookies.withConverter({
|
||||
write: (value) => value,
|
||||
});
|
||||
@@ -68,20 +106,50 @@ const props = defineProps({
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
selected: {
|
||||
type: Number,
|
||||
default: null,
|
||||
},
|
||||
});
|
||||
|
||||
const storedTemplate = cookies.get('summary');
|
||||
const template = ref(storedTemplate ? JSON.parse(storedTemplate)?.custom : defaultTemplate);
|
||||
const templates = ref(pageContext.assets.templates);
|
||||
const selectedTemplate = ref(props.selected || templates.value.at(0)?.id || null);
|
||||
|
||||
const initialTemplate = templates.value.find((storedTemplate) => storedTemplate.id === selectedTemplate.value) || null;
|
||||
|
||||
const template = ref(initialTemplate?.template || defaultTemplate);
|
||||
const hasError = ref(false);
|
||||
const hasChanged = ref(false);
|
||||
const input = ref(null);
|
||||
|
||||
const templateName = ref(initialTemplate?.name || `custom_${Date.now()}`);
|
||||
|
||||
function getSummary() {
|
||||
return processSummaryTemplate(props.release, parse(template.value));
|
||||
return processSummaryTemplate(template.value, props.release);
|
||||
}
|
||||
|
||||
const summary = ref(getSummary());
|
||||
|
||||
function selectTemplate(templateId) {
|
||||
selectedTemplate.value = templateId;
|
||||
|
||||
const nextTemplate = templates.value.find((storedTemplate) => storedTemplate.id === templateId);
|
||||
|
||||
template.value = nextTemplate.template;
|
||||
templateName.value = nextTemplate.name;
|
||||
summary.value = getSummary();
|
||||
|
||||
emit('event', {
|
||||
type: 'select',
|
||||
data: templateId,
|
||||
});
|
||||
|
||||
cookies.set('selectedTemplate', String(templateId));
|
||||
}
|
||||
|
||||
function update() {
|
||||
hasError.value = false;
|
||||
hasChanged.value = true;
|
||||
|
||||
try {
|
||||
summary.value = getSummary();
|
||||
@@ -90,24 +158,52 @@ function update() {
|
||||
}
|
||||
}
|
||||
|
||||
function save() {
|
||||
async function save() {
|
||||
try {
|
||||
parse(template.value);
|
||||
|
||||
cookies.set('summary', JSON.stringify({ custom: template.value }), { expires: 400 }); // 100 years from now
|
||||
hasChanged.value = false;
|
||||
|
||||
events.emit('feedback', {
|
||||
type: 'success',
|
||||
message: 'Saved summary template',
|
||||
const createdTemplate = await post('/templates', {
|
||||
name: templateName.value,
|
||||
template: template.value,
|
||||
successFeedback: `Saved summary template '${templateName.value}'`,
|
||||
errorFeedback: `Failed to save summary template '${templateName.value}'`,
|
||||
});
|
||||
|
||||
templates.value = await get(`/users/${pageContext.user.id}/templates`);
|
||||
|
||||
selectTemplate(createdTemplate.id);
|
||||
} catch (error) {
|
||||
events.emit('feedback', {
|
||||
type: 'error',
|
||||
message: `Failed to save summary template: ${error.message}`,
|
||||
message: `Failed to save summary template '${templateName.value}': ${error.message}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function add() {
|
||||
selectedTemplate.value = null;
|
||||
template.value = '';
|
||||
templateName.value = `custom_${Date.now()}`;
|
||||
summary.value = '';
|
||||
|
||||
input.value.focus();
|
||||
}
|
||||
|
||||
async function remove() {
|
||||
if (confirm(`Are you sure you want to delete summary template ${templateName.value}?`)) { // eslint-disable-line no-restricted-globals, no-alert
|
||||
await del(`/templates/${selectedTemplate.value}`, {
|
||||
undoFeedback: `Deleted summary template '${templateName.value}'`,
|
||||
errorFeedback: `Failed to remove summary template '${templateName.value}'`,
|
||||
});
|
||||
|
||||
templates.value = await get(`/users/${pageContext.user.id}/templates`);
|
||||
|
||||
selectTemplate(templates.value.at(-1)?.id);
|
||||
}
|
||||
}
|
||||
|
||||
function copy() {
|
||||
navigator.clipboard.writeText(summary.value);
|
||||
|
||||
@@ -118,7 +214,7 @@ function copy() {
|
||||
}
|
||||
|
||||
function reset() {
|
||||
if (confirm('Are you sure you want to reset the summary template? Your custom template will be discarded.')) { // eslint-disable-line no-restricted-globals, no-alert
|
||||
if (confirm('Are you sure you want to reset the summary template to the default? Your custom template will be discarded.')) { // eslint-disable-line no-restricted-globals, no-alert
|
||||
template.value = defaultTemplate;
|
||||
|
||||
update();
|
||||
@@ -176,11 +272,59 @@ function reset() {
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
|
||||
.button:not(:last-child) {
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
&.save {
|
||||
flex-grow: 1;
|
||||
justify-content: flex-end;
|
||||
|
||||
.input {
|
||||
margin-right: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
height: 100%;
|
||||
padding: 0 1rem;
|
||||
cursor: pointer;
|
||||
fill: var(--glass);
|
||||
|
||||
&.remove:hover {
|
||||
fill: var(--error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.templates {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: .5rem;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.template-key {
|
||||
padding: .25rem .5rem;
|
||||
border-radius: .25rem;
|
||||
cursor: pointer;
|
||||
|
||||
.icon {
|
||||
fill: var(--glass);
|
||||
}
|
||||
|
||||
&.selected {
|
||||
background: var(--primary);
|
||||
color: var(--text-light);
|
||||
}
|
||||
|
||||
&:hover:not(.selected) {
|
||||
color: var(--primary);
|
||||
|
||||
.icon {
|
||||
fill: var(--primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -141,9 +141,9 @@ const props = defineProps({
|
||||
const pageContext = inject('pageContext');
|
||||
const user = pageContext.user;
|
||||
const pageStash = pageContext.pageProps.stash;
|
||||
const currentStash = pageStash || user?.primaryStash;
|
||||
const currentStash = pageStash || pageContext.assets.primaryStash;
|
||||
|
||||
const favorited = ref(props.scene.stashes.some((sceneStash) => sceneStash.id === currentStash.id));
|
||||
const favorited = ref(props.scene.stashes.some((sceneStash) => sceneStash.id === currentStash?.id));
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
<template #popper>
|
||||
<StashMenu
|
||||
:user="user"
|
||||
:stashes="stashes"
|
||||
:item-stashes="itemStashes"
|
||||
@stash="(stash) => stashItem(stash)"
|
||||
@unstash="(stash) => unstashItem(stash)"
|
||||
@@ -53,10 +53,10 @@
|
||||
|
||||
<template v-else>
|
||||
<Icon
|
||||
v-if="itemStashes.some((itemStash) => itemStash.id === user.primaryStash.id)"
|
||||
v-if="itemStashes.some((itemStash) => itemStash.id === primaryStash.id)"
|
||||
icon="heart7"
|
||||
class="heart favorited noselect"
|
||||
@click.native.stop="unstashItem(user.primaryStash)"
|
||||
@click.native.stop="unstashItem(primaryStash)"
|
||||
@contextmenu.prevent="toggleShowStashes(true)"
|
||||
/>
|
||||
|
||||
@@ -64,14 +64,14 @@
|
||||
v-else
|
||||
icon="heart8"
|
||||
class="heart noselect"
|
||||
@click.native.stop="stashItem(user.primaryStash)"
|
||||
@click.native.stop="stashItem(primaryStash)"
|
||||
@contextmenu.prevent="toggleShowStashes(true)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template #popper>
|
||||
<StashMenu
|
||||
:user="user"
|
||||
:stashes="stashes"
|
||||
:item-stashes="itemStashes"
|
||||
@stash="(stash) => stashItem(stash)"
|
||||
@unstash="(stash) => unstashItem(stash)"
|
||||
@@ -117,8 +117,10 @@ const emit = defineEmits(['stashed', 'unstashed']);
|
||||
|
||||
const pageContext = inject('pageContext');
|
||||
const pageStash = pageContext.pageProps.stash;
|
||||
const user = pageContext.user;
|
||||
|
||||
const user = ref(pageContext.user);
|
||||
const stashes = ref(pageContext.assets?.stashes);
|
||||
const primaryStash = pageContext.assets?.primaryStash;
|
||||
const itemStashes = ref(props.item.stashes);
|
||||
const hasSecondaryStash = computed(() => itemStashes.value.some((itemStash) => !itemStash.isPrimary));
|
||||
|
||||
@@ -195,9 +197,7 @@ function toggleShowStashes(state) {
|
||||
}
|
||||
|
||||
async function reloadStashes(newStash) {
|
||||
const profile = await get(`/users/${user.value.id}`);
|
||||
|
||||
user.value = profile;
|
||||
stashes.value = await get(`/users/${user.id}/stashes`);
|
||||
|
||||
await stashItem(newStash);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<ul class="stash-menu nolist noselect">
|
||||
<li
|
||||
v-for="userStash in user.stashes"
|
||||
v-for="userStash in stashes"
|
||||
:key="`stash-${userStash.id}`"
|
||||
class="menu-item"
|
||||
>
|
||||
@@ -30,9 +30,9 @@
|
||||
import Checkbox from '#/components/form/checkbox.vue';
|
||||
|
||||
defineProps({
|
||||
user: {
|
||||
type: Object,
|
||||
default: null,
|
||||
stashes: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
itemStashes: {
|
||||
type: Array,
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
</a>
|
||||
|
||||
<Icon
|
||||
v-if="!stash.public"
|
||||
v-if="!stash.isPublic"
|
||||
v-tooltip="'This stash is private'"
|
||||
icon="eye-blocked"
|
||||
class="private noselect"
|
||||
@@ -31,7 +31,7 @@
|
||||
<template #popper>
|
||||
<ul class="stash-menu nolist">
|
||||
<li
|
||||
v-if="stash.public"
|
||||
v-if="stash.isPublic"
|
||||
class="menu-item"
|
||||
@click="setPublic(false)"
|
||||
>
|
||||
@@ -158,7 +158,7 @@ async function setPublic(isPublic) {
|
||||
|
||||
done.value = false;
|
||||
|
||||
await patch(`/stashes/${props.stash.id}`, { public: isPublic }, {
|
||||
await patch(`/stashes/${props.stash.id}`, { isPublic }, {
|
||||
undoFeedback: !isPublic && `Stash '${props.stash.name}' set to private`,
|
||||
successFeedback: isPublic && `Stash '${props.stash.name}' set to public`,
|
||||
errorFeedback: 'Failed to update stash',
|
||||
|
||||
Reference in New Issue
Block a user