Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Revamp entity dialog #471

Merged
merged 7 commits into from
Nov 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 41 additions & 13 deletions src/entity/command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,15 @@ import IronVaultPlugin from "../index";
import { Oracle, OracleRollableRow, RollContext } from "../model/oracle";
import { Roll, RollWrapper } from "../model/rolls";
import { CustomSuggestModal } from "../utils/suggest";
import { EntityModal, EntityModalResults } from "./modal";
import { EntityModal } from "./modal";
import { NewEntityModal } from "./new-modal";
import {
ENTITIES,
EntityAttributeFieldSpec,
EntityDescriptor,
EntityResults,
EntitySpec,
NewEntityModalResults,
} from "./specs";

type OraclePromptOption =
Expand Down Expand Up @@ -81,10 +83,10 @@ export async function promptOracleRow(
}

export async function generateEntity(
app: App,
plugin: IronVaultPlugin,
dataContext: CampaignDataContext,
entityDesc: EntityDescriptor<EntitySpec>,
): Promise<EntityModalResults<EntitySpec>> {
): Promise<NewEntityModalResults<EntitySpec>> {
const rollContext = dataContext.oracleRoller;
const attributes = Object.entries(entityDesc.spec)
.filter(
Expand All @@ -103,17 +105,31 @@ export async function generateEntity(
if (!oracle) {
throw new NoSuchOracleError(spec.id, `missing entity oracle for ${key}`);
}
const roll = await promptOracleRow(app, oracle, rollContext, true);
const roll = await promptOracleRow(plugin.app, oracle, rollContext, true);
initialEntity[key] = [new RollWrapper(oracle, rollContext, roll)];
}
return EntityModal.create({
app,
app: plugin.app,
entityDesc,
rollContext,
initialEntity,
});
}

export async function generateEntityNewModal(
plugin: IronVaultPlugin,
dataContext: CampaignDataContext,
entityDesc: EntityDescriptor<EntitySpec>,
): Promise<NewEntityModalResults<EntitySpec>> {
const rollContext = dataContext.oracleRoller;
return NewEntityModal.create({
plugin,
entityDesc,
rollContext,
initialEntity: {},
});
}

export async function generateEntityCommand(
plugin: IronVaultPlugin,
editor: Editor,
Expand Down Expand Up @@ -161,19 +177,29 @@ export async function generateEntityCommand(
entityDesc = selectedEntityDescriptor;
}

let results: EntityModalResults<EntitySpec>;
let results: NewEntityModalResults<EntitySpec>;
try {
results = await generateEntity(plugin.app, campaignContext, entityDesc);
if (plugin.settings.useOldRoller) {
results = await generateEntity(plugin, campaignContext, entityDesc);
} else {
results = await generateEntityNewModal(
plugin,
campaignContext,
entityDesc,
);
}
} catch (e) {
new Notice(String(e));
throw e;
if (e) {
new Notice(String(e));
throw e;
} else {
return;
}
}

const { entity, createFile } = results;
const entityName = results.name ?? `New ${entityDesc.label}`;

const entityName = entityDesc.nameGen
? entityDesc.nameGen(entity)
: `New ${entityDesc.label}`;
let oracleGroupTitle: string;
if (createFile) {
const fileName = results.fileName;
Expand Down Expand Up @@ -202,7 +228,9 @@ export async function generateEntityCommand(
{
spec: spec,
label: spec.name ?? rolls[0].oracle.name,
rolls: rolls.map((roll) => roll.simpleResult).join(", "),
rolls: rolls
.map((roll) => roll.activeRollWrapper().simpleResult)
.join(", "),
},
];
}),
Expand Down
95 changes: 20 additions & 75 deletions src/entity/modal.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { matchDataswornLink } from "datastore/parsers/datasworn/id";
import {
App,
ButtonComponent,
Expand All @@ -14,67 +13,20 @@ import { FolderTextSuggest } from "utils/ui/settings/folder";
import { Oracle, RollContext } from "../model/oracle";
import { RollWrapper } from "../model/rolls";
import {
AttributeMechanism,
EntityAttributeFieldSpec,
EntityDescriptor,
EntityFieldSpec,
EntityResults,
EntitySpec,
evaluateAttribute,
hasAllProperties,
isEntityAttributeSpec,
NewEntityModalResults,
} from "./specs";

const SAFE_SNAKECASE_RESULT = /^[a-z0-9\s]+$/i;
// [Rocky World](id:starforged/collections/oracles/planets/rocky)

function evaluateAttribute(
spec: EntityAttributeFieldSpec,
roll: RollWrapper[],
): string {
if (roll.length != 1) {
throw new Error(`unexpected number of rolls for attribute: ${roll.length}`);
}
const rawResult = roll[0].simpleResult;
switch (spec.definesAttribute.mechanism) {
case AttributeMechanism.Snakecase:
if (!rawResult.match(SAFE_SNAKECASE_RESULT))
throw new Error(
`attribute value did not have snakecase-compatible result: ${rawResult}`,
);
return rawResult.replaceAll(/\s+/g, "_").toLowerCase();
case AttributeMechanism.ParseId: {
const match = matchDataswornLink(rawResult);
if (!match) throw new Error(`no id link found: ${rawResult}`);
const parts = match.id.split("/");
if (parts.length < 2) throw new Error(`no / separator in ${rawResult}`);
return parts.last()!;
}
}
}

/** Check that all properties in `reqs` are present in `inst`.
* @param reqs all properties on this object must be present on `inst`
* @param inst the object to test against reqs
* @returns true if `inst` matches `reqs`
*/
function hasAllProperties<K extends string>(
reqs: Partial<Record<K, string>>,
inst: Partial<Record<K, string>>,
): boolean {
return (Object.entries(reqs) as [K, string][])
.map(([reqKey, reqValue]) => inst[reqKey] === reqValue)
.reduce((acc, cond) => acc && cond);
}

export type EntityModalResults<T extends EntitySpec> = {
createFile: boolean;
fileName: string;
targetFolder: string;
entity: EntityResults<T>;
};

export class EntityModal<T extends EntitySpec> extends Modal {
public accepted: boolean = false;
public readonly results: EntityModalResults<T>;
public readonly results: NewEntityModalResults<T>;

static create<T extends EntitySpec>({
app,
Expand All @@ -86,7 +38,7 @@ export class EntityModal<T extends EntitySpec> extends Modal {
entityDesc: EntityDescriptor<T>;
rollContext: RollContext;
initialEntity: Partial<EntityResults<T>>;
}): Promise<EntityModalResults<T>> {
}): Promise<NewEntityModalResults<T>> {
return new Promise((onAccept, onCancel) => {
let modal;
try {
Expand All @@ -111,21 +63,11 @@ export class EntityModal<T extends EntitySpec> extends Modal {
public readonly entityDesc: EntityDescriptor<T>,
public readonly initialEntity: Partial<EntityResults<T>>,
public readonly rollContext: RollContext,
public readonly onAccept: (results: EntityModalResults<T>) => void,
public readonly onAccept: (results: NewEntityModalResults<T>) => void,
public readonly onCancel: () => void,
) {
super(app);
this.results = {
createFile: false,
fileName: "",
targetFolder: "",
entity: Object.fromEntries(
Object.entries(entityDesc.spec).map(([key]) => [
key,
initialEntity[key] ?? [],
]),
) as Record<keyof T, RollWrapper[]>,
};
this.results = new NewEntityModalResults(entityDesc, initialEntity);
}

async onOpen(): Promise<void> {
Expand Down Expand Up @@ -173,10 +115,9 @@ export class EntityModal<T extends EntitySpec> extends Modal {

const appendRoll = async (key: keyof T): Promise<void> => {
const { table } = settings[key];
this.results.entity[key];

const setting = [
...this.results.entity[key],
...this.results.entityProxy[key],
new RollWrapper(
table,
this.rollContext,
Expand All @@ -193,17 +134,18 @@ export class EntityModal<T extends EntitySpec> extends Modal {

const updateSetting = (key: keyof T, values: RollWrapper[]) => {
const { setting } = settings[key];
this.results.entity[key] = values;
this.results.entityProxy[key] = values;
if (values.length > 0) {
setting.setName(renderRolls(this.results.entity[key]));
setting.setName(renderRolls(this.results.entityProxy[key]));
} else {
setting.setName("");
}

const newEntityName =
this.entityDesc.nameGen && this.entityDesc.nameGen(this.results.entity);
if (newEntityName !== undefined) {
fileNameInput.setValue(newEntityName);
this.results.name =
this.entityDesc.nameGen &&
this.entityDesc.nameGen(this.results.entityProxy);
if (this.results.name !== undefined) {
fileNameInput.setValue(this.results.name);
fileNameInput.onChanged();
}
};
Expand Down Expand Up @@ -233,12 +175,15 @@ export class EntityModal<T extends EntitySpec> extends Modal {
throw new Error("missing table " + id);
}
const setting = new Setting(contentEl)
.setName(renderRolls(this.results.entity[key]))
.setName(renderRolls(this.results.entityProxy[key]))
.setDesc(spec.name ?? table.name);

setting.descEl.ariaLabel = `(id: ${table.id})`;

attributeValues[key] = evaluateAttribute(spec, this.results.entity[key]);
attributeValues[key] = evaluateAttribute(
spec,
this.results.entityProxy[key],
);

settings[key] = { setting, table };
}
Expand Down
Loading
Loading