5963791d85
* Use --force-message in ts/protobuf * Remove some now unnecessary type assertions in deck-options/lib * Satisfy formatter
202 lines
6.6 KiB
TypeScript
202 lines
6.6 KiB
TypeScript
// Copyright: Ankitects Pty Ltd and contributors
|
|
// License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
|
|
|
import { isEqual } from "lodash-es";
|
|
import { Readable, readable } from "svelte/store";
|
|
|
|
import * as tr from "../lib/ftl";
|
|
import { Notetypes, notetypes } from "../lib/proto";
|
|
|
|
function nullToNegativeOne(list: (number | null)[]): number[] {
|
|
return list.map((val) => val ?? -1);
|
|
}
|
|
|
|
/// Public only for tests.
|
|
export function negativeOneToNull(list: number[]): (number | null)[] {
|
|
return list.map((val) => (val === -1 ? null : val));
|
|
}
|
|
|
|
/// Wrapper for the protobuf message to make it more ergonomic.
|
|
export class ChangeNotetypeInfoWrapper {
|
|
fields: (number | null)[];
|
|
templates?: (number | null)[];
|
|
oldNotetypeName: string;
|
|
readonly info: Notetypes.ChangeNotetypeInfo;
|
|
|
|
constructor(info: Notetypes.ChangeNotetypeInfo) {
|
|
this.info = info;
|
|
const templates = info.input?.newTemplates ?? [];
|
|
if (templates.length > 0) {
|
|
this.templates = negativeOneToNull(templates);
|
|
}
|
|
this.fields = negativeOneToNull(info.input?.newFields ?? []);
|
|
this.oldNotetypeName = info.oldNotetypeName;
|
|
}
|
|
|
|
/// A list with an entry for each field/template in the new notetype, with
|
|
/// the values pointing back to indexes in the original notetype.
|
|
mapForContext(ctx: MapContext): (number | null)[] {
|
|
return ctx == MapContext.Template ? this.templates ?? [] : this.fields;
|
|
}
|
|
|
|
/// Return index of old fields/templates, with null values mapped to "Nothing"
|
|
/// at the end.
|
|
getOldIndex(ctx: MapContext, newIdx: number): number {
|
|
const map = this.mapForContext(ctx);
|
|
const val = map[newIdx];
|
|
return val ?? this.getOldNamesIncludingNothing(ctx).length - 1;
|
|
}
|
|
|
|
/// Return all the old names, with "Nothing" at the end.
|
|
getOldNamesIncludingNothing(ctx: MapContext): string[] {
|
|
return [...this.getOldNames(ctx), tr.changeNotetypeNothing()];
|
|
}
|
|
|
|
/// Old names without "Nothing" at the end.
|
|
getOldNames(ctx: MapContext): string[] {
|
|
return ctx == MapContext.Template
|
|
? this.info.oldTemplateNames
|
|
: this.info.oldFieldNames;
|
|
}
|
|
|
|
getOldNotetypeName(): string {
|
|
return this.info.oldNotetypeName;
|
|
}
|
|
|
|
getNewName(ctx: MapContext, idx: number): string {
|
|
return (
|
|
ctx == MapContext.Template
|
|
? this.info.newTemplateNames
|
|
: this.info.newFieldNames
|
|
)[idx];
|
|
}
|
|
|
|
unusedItems(ctx: MapContext): string[] {
|
|
const usedEntries = new Set(this.mapForContext(ctx).filter((v) => v !== null));
|
|
const oldNames = this.getOldNames(ctx);
|
|
const unusedIdxs = [...Array(oldNames.length).keys()].filter(
|
|
(idx) => !usedEntries.has(idx),
|
|
);
|
|
const unusedNames = unusedIdxs.map((idx) => oldNames[idx]);
|
|
return unusedNames;
|
|
}
|
|
|
|
unchanged(): boolean {
|
|
return (
|
|
this.input().newNotetypeId === this.input().oldNotetypeId &&
|
|
isEqual(this.fields, [...Array(this.fields.length).keys()]) &&
|
|
isEqual(this.templates, [...Array(this.templates?.length ?? 0).keys()])
|
|
);
|
|
}
|
|
|
|
input(): Notetypes.ChangeNotetypeRequest {
|
|
return this.info.input as Notetypes.ChangeNotetypeRequest;
|
|
}
|
|
|
|
/// Pack changes back into input message for saving.
|
|
intoInput(): Notetypes.ChangeNotetypeRequest {
|
|
const input = this.info.input as Notetypes.ChangeNotetypeRequest;
|
|
input.newFields = nullToNegativeOne(this.fields);
|
|
if (this.templates) {
|
|
input.newTemplates = nullToNegativeOne(this.templates);
|
|
}
|
|
|
|
return input;
|
|
}
|
|
}
|
|
|
|
export interface NotetypeListEntry {
|
|
idx: number;
|
|
name: string;
|
|
current: boolean;
|
|
}
|
|
|
|
export enum MapContext {
|
|
Field,
|
|
Template,
|
|
}
|
|
|
|
export class ChangeNotetypeState {
|
|
readonly info: Readable<ChangeNotetypeInfoWrapper>;
|
|
readonly notetypes: Readable<NotetypeListEntry[]>;
|
|
|
|
private info_: ChangeNotetypeInfoWrapper;
|
|
private infoSetter!: (val: ChangeNotetypeInfoWrapper) => void;
|
|
private notetypeNames: Notetypes.NotetypeNames;
|
|
private notetypesSetter!: (val: NotetypeListEntry[]) => void;
|
|
|
|
constructor(
|
|
notetypes: Notetypes.NotetypeNames,
|
|
info: Notetypes.ChangeNotetypeInfo,
|
|
) {
|
|
this.info_ = new ChangeNotetypeInfoWrapper(info);
|
|
this.info = readable(this.info_, (set) => {
|
|
this.infoSetter = set;
|
|
});
|
|
this.notetypeNames = notetypes;
|
|
this.notetypes = readable(this.buildNotetypeList(), (set) => {
|
|
this.notetypesSetter = set;
|
|
return;
|
|
});
|
|
}
|
|
|
|
async setTargetNotetypeIndex(idx: number): Promise<void> {
|
|
this.info_.input().newNotetypeId = this.notetypeNames.entries[idx].id!;
|
|
this.notetypesSetter(this.buildNotetypeList());
|
|
const { oldNotetypeId, newNotetypeId } = this.info_.input();
|
|
const newInfo = await notetypes.getChangeNotetypeInfo(
|
|
Notetypes.GetChangeNotetypeInfoRequest.create({
|
|
oldNotetypeId,
|
|
newNotetypeId,
|
|
}),
|
|
);
|
|
|
|
this.info_ = new ChangeNotetypeInfoWrapper(newInfo);
|
|
this.info_.unusedItems(MapContext.Field);
|
|
this.infoSetter(this.info_);
|
|
}
|
|
|
|
setOldIndex(ctx: MapContext, newIdx: number, oldIdx: number): void {
|
|
const list = this.info_.mapForContext(ctx);
|
|
const oldNames = this.info_.getOldNames(ctx);
|
|
const realOldIdx = oldIdx < oldNames.length ? oldIdx : null;
|
|
const allowDupes = ctx == MapContext.Field;
|
|
|
|
// remove any existing references?
|
|
if (!allowDupes && realOldIdx !== null) {
|
|
for (let i = 0; i < list.length; i++) {
|
|
if (list[i] === realOldIdx) {
|
|
list[i] = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
list[newIdx] = realOldIdx;
|
|
this.infoSetter(this.info_);
|
|
}
|
|
|
|
dataForSaving(): Notetypes.ChangeNotetypeRequest {
|
|
return this.info_.intoInput();
|
|
}
|
|
|
|
async save(): Promise<void> {
|
|
if (this.info_.unchanged()) {
|
|
alert("No changes to save");
|
|
return;
|
|
}
|
|
await notetypes.changeNotetype(this.dataForSaving());
|
|
}
|
|
|
|
private buildNotetypeList(): NotetypeListEntry[] {
|
|
const currentId = this.info_.input().newNotetypeId;
|
|
return this.notetypeNames.entries.map(
|
|
(entry, idx) =>
|
|
({
|
|
idx,
|
|
name: entry.name,
|
|
current: entry.id === currentId,
|
|
} as NotetypeListEntry),
|
|
);
|
|
}
|
|
}
|