Add dynamic text compiling and dynamic children list rendering on DOM.
This commit is contained in:
parent
b1229f81f6
commit
6f5566326e
|
|
@ -51,7 +51,7 @@ export const characterTable = table("character", {
|
||||||
people: text().notNull(),
|
people: text().notNull(),
|
||||||
level: int().notNull().default(1),
|
level: int().notNull().default(1),
|
||||||
variables: text({ mode: 'json' }).notNull().default('{"health": 0,"mana": 0,"spells": [],"items": [],"exhaustion": 0,"sickness": [],"poisons": []}'),
|
variables: text({ mode: 'json' }).notNull().default('{"health": 0,"mana": 0,"spells": [],"items": [],"exhaustion": 0,"sickness": [],"poisons": []}'),
|
||||||
aspect: int(),
|
aspect: text().notNull(),
|
||||||
public_notes: text(),
|
public_notes: text(),
|
||||||
private_notes: text(),
|
private_notes: text(),
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ definePageMeta({
|
||||||
const job = ref<string>('');
|
const job = ref<string>('');
|
||||||
|
|
||||||
const payload = reactive<Record<string, any>>({
|
const payload = reactive<Record<string, any>>({
|
||||||
data: JSON.stringify({ username: "Peaceultime", id: 1, timestamp: Date.now() }),
|
data: JSON.stringify({ username: "Peaceultime", id: 1, userId: 1, timestamp: Date.now() }),
|
||||||
to: 'clem31470@gmail.com',
|
to: 'clem31470@gmail.com',
|
||||||
});
|
});
|
||||||
const data = ref(), status = ref<'idle' | 'pending' | 'success' | 'error'>('idle'), success = ref(false), error = ref<Error | null>();
|
const data = ref(), status = ref<'idle' | 'pending' | 'success' | 'error'>('idle'), success = ref(false), error = ref<Error | null>();
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ export type Character = {
|
||||||
name: string; //Free text
|
name: string; //Free text
|
||||||
people?: string; //People ID
|
people?: string; //People ID
|
||||||
level: number;
|
level: number;
|
||||||
aspect?: number;
|
aspect?: string; //Aspect ID
|
||||||
notes?: { public?: string, private?: string }; //Free text
|
notes?: { public?: string, private?: string }; //Free text
|
||||||
|
|
||||||
training: Record<MainStat, Partial<Record<TrainingLevel, number>>>;
|
training: Record<MainStat, Partial<Record<TrainingLevel, number>>>;
|
||||||
|
|
@ -68,8 +68,8 @@ type ItemState = {
|
||||||
export type CharacterConfig = {
|
export type CharacterConfig = {
|
||||||
peoples: Record<string, RaceConfig>;
|
peoples: Record<string, RaceConfig>;
|
||||||
training: Record<MainStat, Record<TrainingLevel, FeatureID[]>>;
|
training: Record<MainStat, Record<TrainingLevel, FeatureID[]>>;
|
||||||
spells: SpellConfig[];
|
spells: Record<string, SpellConfig>;
|
||||||
aspects: AspectConfig[];
|
aspects: Record<string, AspectConfig>;
|
||||||
features: Record<FeatureID, Feature>;
|
features: Record<FeatureID, Feature>;
|
||||||
enchantments: Record<string, EnchantementConfig>; //TODO
|
enchantments: Record<string, EnchantementConfig>; //TODO
|
||||||
items: Record<string, ItemConfig>;
|
items: Record<string, ItemConfig>;
|
||||||
|
|
@ -141,6 +141,7 @@ export type RaceConfig = {
|
||||||
options: Record<Level, FeatureID[]>;
|
options: Record<Level, FeatureID[]>;
|
||||||
};
|
};
|
||||||
export type AspectConfig = {
|
export type AspectConfig = {
|
||||||
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
description: string; //TODO -> TextID
|
description: string; //TODO -> TextID
|
||||||
stat: MainStat | 'special';
|
stat: MainStat | 'special';
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,7 @@ CREATE TABLE `campaign_logs` (
|
||||||
`type` text,
|
`type` text,
|
||||||
`details` text NOT NULL,
|
`details` text NOT NULL,
|
||||||
PRIMARY KEY(`id`, `from`, `timestamp`),
|
PRIMARY KEY(`id`, `from`, `timestamp`),
|
||||||
FOREIGN KEY (`id`) REFERENCES `campaign`(`id`) ON UPDATE cascade ON DELETE cascade,
|
FOREIGN KEY (`id`) REFERENCES `campaign`(`id`) ON UPDATE cascade ON DELETE cascade
|
||||||
FOREIGN KEY (`from`)
|
|
||||||
);
|
);
|
||||||
--> statement-breakpoint
|
--> statement-breakpoint
|
||||||
ALTER TABLE `campaign` ADD `status` text DEFAULT 'PREPARING';--> statement-breakpoint
|
ALTER TABLE `campaign` ADD `status` text DEFAULT 'PREPARING';--> statement-breakpoint
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,7 @@ CREATE TABLE `__new_campaign_logs` (
|
||||||
`type` text,
|
`type` text,
|
||||||
`details` text NOT NULL,
|
`details` text NOT NULL,
|
||||||
PRIMARY KEY(`id`, `from`, `timestamp`),
|
PRIMARY KEY(`id`, `from`, `timestamp`),
|
||||||
FOREIGN KEY (`id`) REFERENCES `campaign`(`id`) ON UPDATE cascade ON DELETE cascade,
|
FOREIGN KEY (`id`) REFERENCES `campaign`(`id`) ON UPDATE cascade ON DELETE cascade
|
||||||
FOREIGN KEY (`from`)
|
|
||||||
);
|
);
|
||||||
--> statement-breakpoint
|
--> statement-breakpoint
|
||||||
INSERT INTO `__new_campaign_logs`("id", "from", "timestamp", "type", "details") SELECT "id", "from", "timestamp", "type", "details" FROM `campaign_logs`;--> statement-breakpoint
|
INSERT INTO `__new_campaign_logs`("id", "from", "timestamp", "type", "details") SELECT "id", "from", "timestamp", "type", "details" FROM `campaign_logs`;--> statement-breakpoint
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,7 @@ CREATE TABLE `__new_campaign_logs` (
|
||||||
`type` text,
|
`type` text,
|
||||||
`details` text NOT NULL,
|
`details` text NOT NULL,
|
||||||
PRIMARY KEY(`id`, `target`, `timestamp`),
|
PRIMARY KEY(`id`, `target`, `timestamp`),
|
||||||
FOREIGN KEY (`id`) REFERENCES `campaign`(`id`) ON UPDATE cascade ON DELETE cascade,
|
FOREIGN KEY (`id`) REFERENCES `campaign`(`id`) ON UPDATE cascade ON DELETE cascade
|
||||||
FOREIGN KEY (`target`)
|
|
||||||
);
|
);
|
||||||
--> statement-breakpoint
|
--> statement-breakpoint
|
||||||
INSERT INTO `__new_campaign_logs`("id", "target", "timestamp", "type", "details") SELECT "id", "target", "timestamp", "type", "details" FROM `campaign_logs`;--> statement-breakpoint
|
INSERT INTO `__new_campaign_logs`("id", "target", "timestamp", "type", "details") SELECT "id", "target", "timestamp", "type", "details" FROM `campaign_logs`;--> statement-breakpoint
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
PRAGMA foreign_keys=OFF;--> statement-breakpoint
|
||||||
|
CREATE TABLE `__new_character` (
|
||||||
|
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||||
|
`name` text NOT NULL,
|
||||||
|
`owner` integer NOT NULL,
|
||||||
|
`people` text NOT NULL,
|
||||||
|
`level` integer DEFAULT 1 NOT NULL,
|
||||||
|
`variables` text DEFAULT '{"health": 0,"mana": 0,"spells": [],"items": [],"exhaustion": 0,"sickness": [],"poisons": []}' NOT NULL,
|
||||||
|
`aspect` text NOT NULL,
|
||||||
|
`public_notes` text,
|
||||||
|
`private_notes` text,
|
||||||
|
`visibility` text DEFAULT 'private' NOT NULL,
|
||||||
|
`thumbnail` blob,
|
||||||
|
FOREIGN KEY (`owner`) REFERENCES `users`(`id`) ON UPDATE cascade ON DELETE cascade
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
INSERT INTO `__new_character`("id", "name", "owner", "people", "level", "variables", "aspect", "public_notes", "private_notes", "visibility", "thumbnail") SELECT "id", "name", "owner", "people", "level", "variables", "aspect", "public_notes", "private_notes", "visibility", "thumbnail" FROM `character`;--> statement-breakpoint
|
||||||
|
DROP TABLE `character`;--> statement-breakpoint
|
||||||
|
ALTER TABLE `__new_character` RENAME TO `character`;--> statement-breakpoint
|
||||||
|
PRAGMA foreign_keys=ON;
|
||||||
|
|
@ -0,0 +1,995 @@
|
||||||
|
{
|
||||||
|
"version": "6",
|
||||||
|
"dialect": "sqlite",
|
||||||
|
"id": "fdee27cd-0188-4e54-bc2c-a96a375e83a1",
|
||||||
|
"prevId": "4ef0ea2b-0c07-438c-901f-0ef64bb5f749",
|
||||||
|
"tables": {
|
||||||
|
"campaign_characters": {
|
||||||
|
"name": "campaign_characters",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"character": {
|
||||||
|
"name": "character",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"campaign_characters_id_campaign_id_fk": {
|
||||||
|
"name": "campaign_characters_id_campaign_id_fk",
|
||||||
|
"tableFrom": "campaign_characters",
|
||||||
|
"tableTo": "campaign",
|
||||||
|
"columnsFrom": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "cascade"
|
||||||
|
},
|
||||||
|
"campaign_characters_character_character_id_fk": {
|
||||||
|
"name": "campaign_characters_character_character_id_fk",
|
||||||
|
"tableFrom": "campaign_characters",
|
||||||
|
"tableTo": "character",
|
||||||
|
"columnsFrom": [
|
||||||
|
"character"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "cascade"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {
|
||||||
|
"campaign_characters_id_character_pk": {
|
||||||
|
"columns": [
|
||||||
|
"id",
|
||||||
|
"character"
|
||||||
|
],
|
||||||
|
"name": "campaign_characters_id_character_pk"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"campaign_logs": {
|
||||||
|
"name": "campaign_logs",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"target": {
|
||||||
|
"name": "target",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"timestamp": {
|
||||||
|
"name": "timestamp",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"name": "type",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"details": {
|
||||||
|
"name": "details",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"campaign_logs_id_campaign_id_fk": {
|
||||||
|
"name": "campaign_logs_id_campaign_id_fk",
|
||||||
|
"tableFrom": "campaign_logs",
|
||||||
|
"tableTo": "campaign",
|
||||||
|
"columnsFrom": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "cascade"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {
|
||||||
|
"campaign_logs_id_target_timestamp_pk": {
|
||||||
|
"columns": [
|
||||||
|
"id",
|
||||||
|
"target",
|
||||||
|
"timestamp"
|
||||||
|
],
|
||||||
|
"name": "campaign_logs_id_target_timestamp_pk"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"campaign_members": {
|
||||||
|
"name": "campaign_members",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"name": "user",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"campaign_members_id_campaign_id_fk": {
|
||||||
|
"name": "campaign_members_id_campaign_id_fk",
|
||||||
|
"tableFrom": "campaign_members",
|
||||||
|
"tableTo": "campaign",
|
||||||
|
"columnsFrom": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "cascade"
|
||||||
|
},
|
||||||
|
"campaign_members_user_users_id_fk": {
|
||||||
|
"name": "campaign_members_user_users_id_fk",
|
||||||
|
"tableFrom": "campaign_members",
|
||||||
|
"tableTo": "users",
|
||||||
|
"columnsFrom": [
|
||||||
|
"user"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "cascade"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {
|
||||||
|
"campaign_members_id_user_pk": {
|
||||||
|
"columns": [
|
||||||
|
"id",
|
||||||
|
"user"
|
||||||
|
],
|
||||||
|
"name": "campaign_members_id_user_pk"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"campaign": {
|
||||||
|
"name": "campaign",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": true
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"owner": {
|
||||||
|
"name": "owner",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"link": {
|
||||||
|
"name": "link",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"name": "status",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "'PREPARING'"
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"name": "settings",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "'{}'"
|
||||||
|
},
|
||||||
|
"inventory": {
|
||||||
|
"name": "inventory",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "'[]'"
|
||||||
|
},
|
||||||
|
"money": {
|
||||||
|
"name": "money",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": 0
|
||||||
|
},
|
||||||
|
"public_notes": {
|
||||||
|
"name": "public_notes",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "''"
|
||||||
|
},
|
||||||
|
"dm_notes": {
|
||||||
|
"name": "dm_notes",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "''"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"campaign_owner_users_id_fk": {
|
||||||
|
"name": "campaign_owner_users_id_fk",
|
||||||
|
"tableFrom": "campaign",
|
||||||
|
"tableTo": "users",
|
||||||
|
"columnsFrom": [
|
||||||
|
"owner"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "cascade"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"character_abilities": {
|
||||||
|
"name": "character_abilities",
|
||||||
|
"columns": {
|
||||||
|
"character": {
|
||||||
|
"name": "character",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"ability": {
|
||||||
|
"name": "ability",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"name": "value",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": 0
|
||||||
|
},
|
||||||
|
"max": {
|
||||||
|
"name": "max",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"character_abilities_character_character_id_fk": {
|
||||||
|
"name": "character_abilities_character_character_id_fk",
|
||||||
|
"tableFrom": "character_abilities",
|
||||||
|
"tableTo": "character",
|
||||||
|
"columnsFrom": [
|
||||||
|
"character"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "cascade"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {
|
||||||
|
"character_abilities_character_ability_pk": {
|
||||||
|
"columns": [
|
||||||
|
"character",
|
||||||
|
"ability"
|
||||||
|
],
|
||||||
|
"name": "character_abilities_character_ability_pk"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"character_choices": {
|
||||||
|
"name": "character_choices",
|
||||||
|
"columns": {
|
||||||
|
"character": {
|
||||||
|
"name": "character",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"choice": {
|
||||||
|
"name": "choice",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"character_choices_character_character_id_fk": {
|
||||||
|
"name": "character_choices_character_character_id_fk",
|
||||||
|
"tableFrom": "character_choices",
|
||||||
|
"tableTo": "character",
|
||||||
|
"columnsFrom": [
|
||||||
|
"character"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "cascade"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {
|
||||||
|
"character_choices_character_id_choice_pk": {
|
||||||
|
"columns": [
|
||||||
|
"character",
|
||||||
|
"id",
|
||||||
|
"choice"
|
||||||
|
],
|
||||||
|
"name": "character_choices_character_id_choice_pk"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"character_leveling": {
|
||||||
|
"name": "character_leveling",
|
||||||
|
"columns": {
|
||||||
|
"character": {
|
||||||
|
"name": "character",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"level": {
|
||||||
|
"name": "level",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"choice": {
|
||||||
|
"name": "choice",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"character_leveling_character_character_id_fk": {
|
||||||
|
"name": "character_leveling_character_character_id_fk",
|
||||||
|
"tableFrom": "character_leveling",
|
||||||
|
"tableTo": "character",
|
||||||
|
"columnsFrom": [
|
||||||
|
"character"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "cascade"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {
|
||||||
|
"character_leveling_character_level_pk": {
|
||||||
|
"columns": [
|
||||||
|
"character",
|
||||||
|
"level"
|
||||||
|
],
|
||||||
|
"name": "character_leveling_character_level_pk"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"character": {
|
||||||
|
"name": "character",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": true
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"owner": {
|
||||||
|
"name": "owner",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"people": {
|
||||||
|
"name": "people",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"level": {
|
||||||
|
"name": "level",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": 1
|
||||||
|
},
|
||||||
|
"variables": {
|
||||||
|
"name": "variables",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "'{\"health\": 0,\"mana\": 0,\"spells\": [],\"items\": [],\"exhaustion\": 0,\"sickness\": [],\"poisons\": []}'"
|
||||||
|
},
|
||||||
|
"aspect": {
|
||||||
|
"name": "aspect",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"public_notes": {
|
||||||
|
"name": "public_notes",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"private_notes": {
|
||||||
|
"name": "private_notes",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"visibility": {
|
||||||
|
"name": "visibility",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "'private'"
|
||||||
|
},
|
||||||
|
"thumbnail": {
|
||||||
|
"name": "thumbnail",
|
||||||
|
"type": "blob",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"character_owner_users_id_fk": {
|
||||||
|
"name": "character_owner_users_id_fk",
|
||||||
|
"tableFrom": "character",
|
||||||
|
"tableTo": "users",
|
||||||
|
"columnsFrom": [
|
||||||
|
"owner"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "cascade"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"character_training": {
|
||||||
|
"name": "character_training",
|
||||||
|
"columns": {
|
||||||
|
"character": {
|
||||||
|
"name": "character",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"stat": {
|
||||||
|
"name": "stat",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"level": {
|
||||||
|
"name": "level",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"choice": {
|
||||||
|
"name": "choice",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"character_training_character_character_id_fk": {
|
||||||
|
"name": "character_training_character_character_id_fk",
|
||||||
|
"tableFrom": "character_training",
|
||||||
|
"tableTo": "character",
|
||||||
|
"columnsFrom": [
|
||||||
|
"character"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "cascade"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {
|
||||||
|
"character_training_character_stat_level_pk": {
|
||||||
|
"columns": [
|
||||||
|
"character",
|
||||||
|
"stat",
|
||||||
|
"level"
|
||||||
|
],
|
||||||
|
"name": "character_training_character_stat_level_pk"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"email_validation": {
|
||||||
|
"name": "email_validation",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"timestamp": {
|
||||||
|
"name": "timestamp",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"project_content": {
|
||||||
|
"name": "project_content",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"content": {
|
||||||
|
"name": "content",
|
||||||
|
"type": "blob",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"project_files": {
|
||||||
|
"name": "project_files",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"path": {
|
||||||
|
"name": "path",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"owner": {
|
||||||
|
"name": "owner",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"name": "title",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"name": "type",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"navigable": {
|
||||||
|
"name": "navigable",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"private": {
|
||||||
|
"name": "private",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"order": {
|
||||||
|
"name": "order",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"timestamp": {
|
||||||
|
"name": "timestamp",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"project_files_path_unique": {
|
||||||
|
"name": "project_files_path_unique",
|
||||||
|
"columns": [
|
||||||
|
"path"
|
||||||
|
],
|
||||||
|
"isUnique": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {
|
||||||
|
"project_files_owner_users_id_fk": {
|
||||||
|
"name": "project_files_owner_users_id_fk",
|
||||||
|
"tableFrom": "project_files",
|
||||||
|
"tableTo": "users",
|
||||||
|
"columnsFrom": [
|
||||||
|
"owner"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "cascade"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"user_permissions": {
|
||||||
|
"name": "user_permissions",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"permission": {
|
||||||
|
"name": "permission",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"user_permissions_id_users_id_fk": {
|
||||||
|
"name": "user_permissions_id_users_id_fk",
|
||||||
|
"tableFrom": "user_permissions",
|
||||||
|
"tableTo": "users",
|
||||||
|
"columnsFrom": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "cascade"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {
|
||||||
|
"user_permissions_id_permission_pk": {
|
||||||
|
"columns": [
|
||||||
|
"id",
|
||||||
|
"permission"
|
||||||
|
],
|
||||||
|
"name": "user_permissions_id_permission_pk"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"user_sessions": {
|
||||||
|
"name": "user_sessions",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"timestamp": {
|
||||||
|
"name": "timestamp",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"user_sessions_user_id_users_id_fk": {
|
||||||
|
"name": "user_sessions_user_id_users_id_fk",
|
||||||
|
"tableFrom": "user_sessions",
|
||||||
|
"tableTo": "users",
|
||||||
|
"columnsFrom": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "cascade"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {
|
||||||
|
"user_sessions_id_user_id_pk": {
|
||||||
|
"columns": [
|
||||||
|
"id",
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"name": "user_sessions_id_user_id_pk"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"users_data": {
|
||||||
|
"name": "users_data",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"signin": {
|
||||||
|
"name": "signin",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"lastTimestamp": {
|
||||||
|
"name": "lastTimestamp",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"users_data_id_users_id_fk": {
|
||||||
|
"name": "users_data_id_users_id_fk",
|
||||||
|
"tableFrom": "users_data",
|
||||||
|
"tableTo": "users",
|
||||||
|
"columnsFrom": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "cascade"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"users": {
|
||||||
|
"name": "users",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": true
|
||||||
|
},
|
||||||
|
"username": {
|
||||||
|
"name": "username",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"name": "email",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"hash": {
|
||||||
|
"name": "hash",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"state": {
|
||||||
|
"name": "state",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"users_username_unique": {
|
||||||
|
"name": "users_username_unique",
|
||||||
|
"columns": [
|
||||||
|
"username"
|
||||||
|
],
|
||||||
|
"isUnique": true
|
||||||
|
},
|
||||||
|
"users_email_unique": {
|
||||||
|
"name": "users_email_unique",
|
||||||
|
"columns": [
|
||||||
|
"email"
|
||||||
|
],
|
||||||
|
"isUnique": true
|
||||||
|
},
|
||||||
|
"users_hash_unique": {
|
||||||
|
"name": "users_hash_unique",
|
||||||
|
"columns": [
|
||||||
|
"hash"
|
||||||
|
],
|
||||||
|
"isUnique": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"views": {},
|
||||||
|
"enums": {},
|
||||||
|
"_meta": {
|
||||||
|
"schemas": {},
|
||||||
|
"tables": {},
|
||||||
|
"columns": {}
|
||||||
|
},
|
||||||
|
"internal": {
|
||||||
|
"indexes": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -176,6 +176,13 @@
|
||||||
"when": 1763479527696,
|
"when": 1763479527696,
|
||||||
"tag": "0024_secret_arclight",
|
"tag": "0024_secret_arclight",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 25,
|
||||||
|
"version": "6",
|
||||||
|
"when": 1764763792974,
|
||||||
|
"tag": "0025_majestic_grim_reaper",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -3,7 +3,6 @@ import { eq, or } from 'drizzle-orm';
|
||||||
import { z } from 'zod/v4';
|
import { z } from 'zod/v4';
|
||||||
import useDatabase from '~/composables/useDatabase';
|
import useDatabase from '~/composables/useDatabase';
|
||||||
import { usersTable } from '~/db/schema';
|
import { usersTable } from '~/db/schema';
|
||||||
import sendMail from '~/../server/tasks/mail';
|
|
||||||
|
|
||||||
const schema = z.object({
|
const schema = z.object({
|
||||||
profile: z.string(),
|
profile: z.string(),
|
||||||
|
|
@ -33,7 +32,7 @@ export default defineEventHandler(async (e) => {
|
||||||
id, timestamp,
|
id, timestamp,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
await sendMail({
|
await runTask('mail', {
|
||||||
payload: {
|
payload: {
|
||||||
type: 'mail',
|
type: 'mail',
|
||||||
data: {
|
data: {
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ import { usersDataTable, usersTable } from '~/db/schema';
|
||||||
import { schema } from '~/schemas/registration';
|
import { schema } from '~/schemas/registration';
|
||||||
import { checkSession, logSession } from '~/../server/utils/user';
|
import { checkSession, logSession } from '~/../server/utils/user';
|
||||||
import type { UserSession, UserSessionRequired } from '~/types/auth';
|
import type { UserSession, UserSessionRequired } from '~/types/auth';
|
||||||
import sendMail from '~/../server/tasks/mail';
|
|
||||||
import type { $ZodIssue } from 'zod/v4/core';
|
import type { $ZodIssue } from 'zod/v4/core';
|
||||||
|
|
||||||
interface SuccessHandler
|
interface SuccessHandler
|
||||||
|
|
@ -75,7 +74,7 @@ export default defineEventHandler(async (e): Promise<Return> => {
|
||||||
|
|
||||||
logSession(e, await setUserSession(e, { user: { id: id.id, username: body.data.username, email: body.data.email, state: 0, signin: new Date(), permissions: [], lastTimestamp: new Date() } }) as UserSessionRequired);
|
logSession(e, await setUserSession(e, { user: { id: id.id, username: body.data.username, email: body.data.email, state: 0, signin: new Date(), permissions: [], lastTimestamp: new Date() } }) as UserSessionRequired);
|
||||||
|
|
||||||
await sendMail({
|
await runTask('mail', {
|
||||||
payload: {
|
payload: {
|
||||||
type: 'mail',
|
type: 'mail',
|
||||||
to: [body.data.email],
|
to: [body.data.email],
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ export default defineEventHandler(async (e) => {
|
||||||
|
|
||||||
const db = useDatabase();
|
const db = useDatabase();
|
||||||
|
|
||||||
const campaign = db.select({ id: campaignTable.id }).from(campaignMembersTable).innerJoin(campaignTable, eq(campaignMembersTable.id, campaignTable.id)).where(and(eq(campaignMembersTable.id, campaign_id), or(eq(campaignMembersTable.user, session.user.id), eq(campaignTable.owner, session.user.id)))).get();
|
const campaign = db.select({ id: campaignTable.id }).from(campaignTable).leftJoin(campaignMembersTable, eq(campaignTable.id, campaignMembersTable.id)).where(and(eq(campaignTable.id, campaign_id), or(eq(campaignMembersTable.user, session.user.id), eq(campaignTable.owner, session.user.id)))).get();
|
||||||
if(!campaign || campaign.id !== campaign_id)
|
if(!campaign || campaign.id !== campaign_id)
|
||||||
return setResponseStatus(e, 404);
|
return setResponseStatus(e, 404);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import { and, eq, notExists } from 'drizzle-orm';
|
import { and, eq, or } from 'drizzle-orm';
|
||||||
import useDatabase from '~/composables/useDatabase';
|
import useDatabase from '~/composables/useDatabase';
|
||||||
import { campaignCharactersTable, campaignMembersTable, campaignTable, characterTable } from '~/db/schema';
|
import { campaignCharactersTable, campaignMembersTable, campaignTable, characterTable } from '~/db/schema';
|
||||||
import { CharacterVariablesValidation } from '#shared/character.util';
|
|
||||||
|
|
||||||
export default defineEventHandler(async (e) => {
|
export default defineEventHandler(async (e) => {
|
||||||
const _id = getRouterParam(e, "id");
|
const _id = getRouterParam(e, "id");
|
||||||
|
|
@ -32,7 +31,7 @@ export default defineEventHandler(async (e) => {
|
||||||
if(!character || character.id !== id)
|
if(!character || character.id !== id)
|
||||||
return setResponseStatus(e, 403);
|
return setResponseStatus(e, 403);
|
||||||
|
|
||||||
const campaign = db.select({ id: campaignMembersTable.id }).from(campaignMembersTable).where(and(eq(campaignMembersTable.id, campaign_id), eq(campaignMembersTable.user, session.user.id))).get();
|
const campaign = db.select({ id: campaignTable.id }).from(campaignTable).leftJoin(campaignMembersTable, eq(campaignTable.id, campaignMembersTable.id)).where(and(eq(campaignTable.id, campaign_id), or(eq(campaignTable.owner, session.user.id), eq(campaignMembersTable.user, session.user.id)))).get();
|
||||||
if(!campaign || campaign.id !== campaign_id)
|
if(!campaign || campaign.id !== campaign_id)
|
||||||
return setResponseStatus(e, 404);
|
return setResponseStatus(e, 404);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ import { hash } from "bun";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
import useDatabase from "~/composables/useDatabase";
|
import useDatabase from "~/composables/useDatabase";
|
||||||
import { usersTable } from "~/db/schema";
|
import { usersTable } from "~/db/schema";
|
||||||
import sendMail from '~/../server/tasks/mail';
|
|
||||||
|
|
||||||
export default defineEventHandler(async (e) => {
|
export default defineEventHandler(async (e) => {
|
||||||
const session = await getUserSession(e);
|
const session = await getUserSession(e);
|
||||||
|
|
@ -57,7 +56,7 @@ export default defineEventHandler(async (e) => {
|
||||||
id: emailId, timestamp,
|
id: emailId, timestamp,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
await sendMail({
|
await runTask('mail', {
|
||||||
payload: {
|
payload: {
|
||||||
type: 'mail',
|
type: 'mail',
|
||||||
to: [data.email],
|
to: [data.email],
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@ export default defineTask({
|
||||||
throw new Error(`Données inconnues`);
|
throw new Error(`Données inconnues`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const mailPayload = payload.data as MailPayload;
|
const mailPayload = payload as MailPayload;
|
||||||
const template = templates[mailPayload.template];
|
const template = templates[mailPayload.template];
|
||||||
|
|
||||||
console.log(mailPayload);
|
console.log(mailPayload);
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -3,7 +3,7 @@ import { z } from "zod/v4";
|
||||||
import characterConfig from '#shared/character-config.json';
|
import characterConfig from '#shared/character-config.json';
|
||||||
import proses, { preview } from "#shared/proses";
|
import proses, { preview } from "#shared/proses";
|
||||||
import { button, buttongroup, checkbox, floater, foldable, input, loading, multiselect, numberpicker, select, tabgroup, Toaster, toggle } from "#shared/components.util";
|
import { button, buttongroup, checkbox, floater, foldable, input, loading, multiselect, numberpicker, select, tabgroup, Toaster, toggle } from "#shared/components.util";
|
||||||
import { div, dom, icon, span, text } from "#shared/dom.util";
|
import { div, dom, icon, span, text, type DOMList } from "#shared/dom.util";
|
||||||
import { followermenu, fullblocker, tooltip } from "#shared/floating.util";
|
import { followermenu, fullblocker, tooltip } from "#shared/floating.util";
|
||||||
import { clamp } from "#shared/general.util";
|
import { clamp } from "#shared/general.util";
|
||||||
import markdown from "#shared/markdown.util";
|
import markdown from "#shared/markdown.util";
|
||||||
|
|
@ -51,91 +51,97 @@ export const defaultCharacter: Character = {
|
||||||
owner: -1,
|
owner: -1,
|
||||||
visibility: "private",
|
visibility: "private",
|
||||||
};
|
};
|
||||||
const defaultCompiledCharacter: (character: Character) => CompiledCharacter = (character: Character) => ({
|
const defaultCompiledCharacter: (character: Character) => CompiledCharacter = (character: Character) => {
|
||||||
id: character.id,
|
const compiled = {
|
||||||
owner: character.owner,
|
id: character.id,
|
||||||
username: character.username,
|
owner: character.owner,
|
||||||
name: character.name,
|
username: character.username,
|
||||||
health: 0,
|
name: character.name,
|
||||||
mana: 0,
|
health: 0,
|
||||||
race: character.people!,
|
mana: 0,
|
||||||
modifier: MAIN_STATS.reduce((p, v) => { p[v] = 0; return p; }, {} as Record<MainStat, number>),
|
race: character.people!,
|
||||||
level: character.level,
|
modifier: MAIN_STATS.reduce((p, v) => { p[v] = 0; return p; }, {} as Record<MainStat, number>),
|
||||||
variables: character.variables,
|
level: character.level,
|
||||||
action: 0,
|
variables: character.variables,
|
||||||
reaction: 0,
|
action: 0,
|
||||||
exhaust: 0,
|
reaction: 0,
|
||||||
itempower: 0,
|
exhaust: 0,
|
||||||
features: {
|
itempower: 0,
|
||||||
action: [],
|
features: {
|
||||||
reaction: [],
|
action: [],
|
||||||
freeaction: [],
|
reaction: [],
|
||||||
passive: [],
|
freeaction: [],
|
||||||
},
|
passive: [],
|
||||||
abilities: {
|
},
|
||||||
athletics: 0,
|
abilities: {
|
||||||
acrobatics: 0,
|
athletics: 0,
|
||||||
intimidation: 0,
|
acrobatics: 0,
|
||||||
sleightofhand: 0,
|
intimidation: 0,
|
||||||
stealth: 0,
|
sleightofhand: 0,
|
||||||
survival: 0,
|
stealth: 0,
|
||||||
investigation: 0,
|
survival: 0,
|
||||||
history: 0,
|
investigation: 0,
|
||||||
religion: 0,
|
history: 0,
|
||||||
arcana: 0,
|
religion: 0,
|
||||||
understanding: 0,
|
arcana: 0,
|
||||||
perception: 0,
|
understanding: 0,
|
||||||
performance: 0,
|
perception: 0,
|
||||||
medecine: 0,
|
performance: 0,
|
||||||
persuasion: 0,
|
medecine: 0,
|
||||||
animalhandling: 0,
|
persuasion: 0,
|
||||||
deception: 0
|
animalhandling: 0,
|
||||||
},
|
deception: 0
|
||||||
spellslots: 0,
|
},
|
||||||
artslots: 0,
|
spellslots: 0,
|
||||||
spellranks: {
|
artslots: 0,
|
||||||
instinct: 0,
|
spellranks: {
|
||||||
knowledge: 0,
|
instinct: 0 as 0 | 1 | 2 | 3,
|
||||||
precision: 0,
|
knowledge: 0 as 0 | 1 | 2 | 3,
|
||||||
arts: 0,
|
precision: 0 as 0 | 1 | 2 | 3,
|
||||||
},
|
arts: 0 as 0 | 1 | 2 | 3,
|
||||||
speed: false,
|
},
|
||||||
defense: {
|
speed: false as number | false,
|
||||||
hardcap: Infinity,
|
defense: {
|
||||||
static: 6,
|
hardcap: Infinity,
|
||||||
activeparry: 0,
|
static: 6,
|
||||||
activedodge: 0,
|
activeparry: 0,
|
||||||
passiveparry: 0,
|
activedodge: 0,
|
||||||
passivedodge: 0,
|
passiveparry: 0,
|
||||||
},
|
passivedodge: 0,
|
||||||
mastery: {
|
get passive() { return clamp(compiled.defense.static + compiled.defense.passivedodge + compiled.defense.passiveparry, 0, compiled.defense.hardcap) },
|
||||||
strength: 0,
|
get parry() { return clamp(compiled.defense.static + compiled.defense.passivedodge + compiled.defense.activeparry, 0, compiled.defense.hardcap) },
|
||||||
dexterity: 0,
|
get dodge() { return clamp(compiled.defense.static + compiled.defense.activedodge + compiled.defense.passiveparry, 0, compiled.defense.hardcap) },
|
||||||
shield: 0,
|
},
|
||||||
armor: 0,
|
mastery: {
|
||||||
multiattack: 1,
|
strength: 0,
|
||||||
magicpower: 0,
|
dexterity: 0,
|
||||||
magicspeed: 0,
|
shield: 0,
|
||||||
magicelement: 0,
|
armor: 0,
|
||||||
magicinstinct: 0,
|
multiattack: 1,
|
||||||
},
|
magicpower: 0,
|
||||||
bonus: {
|
magicspeed: 0,
|
||||||
abilities: {},
|
magicelement: 0,
|
||||||
defense: {},
|
magicinstinct: 0,
|
||||||
},
|
},
|
||||||
resistance: {},
|
bonus: {
|
||||||
initiative: 0,
|
abilities: {},
|
||||||
capacity: 0,
|
defense: {},
|
||||||
lists: {
|
},
|
||||||
action: [],
|
resistance: {},
|
||||||
freeaction: [],
|
initiative: 0,
|
||||||
reaction: [],
|
capacity: 0,
|
||||||
passive: [],
|
lists: {
|
||||||
spells: [],
|
action: [],
|
||||||
},
|
freeaction: [],
|
||||||
aspect: "",
|
reaction: [],
|
||||||
notes: Object.assign({ public: '', private: '' }, character.notes),
|
passive: [],
|
||||||
});
|
spells: [],
|
||||||
|
},
|
||||||
|
aspect: "",
|
||||||
|
notes: Object.assign({ public: '', private: '' }, character.notes),
|
||||||
|
}
|
||||||
|
return compiled;
|
||||||
|
};
|
||||||
export const mainStatTexts: Record<MainStat, string> = {
|
export const mainStatTexts: Record<MainStat, string> = {
|
||||||
"strength": "Force",
|
"strength": "Force",
|
||||||
"dexterity": "Dextérité",
|
"dexterity": "Dextérité",
|
||||||
|
|
@ -257,7 +263,7 @@ export const CharacterValidation = z.object({
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
people: z.string().nullable(),
|
people: z.string().nullable(),
|
||||||
level: z.number().min(1).max(20),
|
level: z.number().min(1).max(20),
|
||||||
aspect: z.number().nullable().optional(),
|
aspect: z.string(),
|
||||||
notes: CharacterNotesValidation,
|
notes: CharacterNotesValidation,
|
||||||
training: z.record(z.enum(MAIN_STATS), z.record(z.enum(TRAINING_LEVELS.map(String)), z.number().optional())),
|
training: z.record(z.enum(MAIN_STATS), z.record(z.enum(TRAINING_LEVELS.map(String)), z.number().optional())),
|
||||||
leveling: z.record(z.enum(LEVELS.map(String)), z.number().optional()),
|
leveling: z.record(z.enum(LEVELS.map(String)), z.number().optional()),
|
||||||
|
|
@ -286,6 +292,7 @@ export class CharacterCompiler
|
||||||
'modifier/psyche': { value: 0, _dirty: false, min: -Infinity, list: [] },
|
'modifier/psyche': { value: 0, _dirty: false, min: -Infinity, list: [] },
|
||||||
};
|
};
|
||||||
private _variableDirty: boolean = false;
|
private _variableDirty: boolean = false;
|
||||||
|
private _variableDebounce: NodeJS.Timeout = setTimeout(() => {});
|
||||||
|
|
||||||
constructor(character: Character)
|
constructor(character: Character)
|
||||||
{
|
{
|
||||||
|
|
@ -353,11 +360,17 @@ export class CharacterCompiler
|
||||||
return substring;
|
return substring;
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
variable<T extends keyof CharacterVariables>(prop: T, value: CharacterVariables[T])
|
variable<T extends keyof CharacterVariables>(prop: T, value: CharacterVariables[T], autosave: boolean = true)
|
||||||
{
|
{
|
||||||
this._character.variables[prop] = value;
|
this._character.variables[prop] = value;
|
||||||
this._result.variables[prop] = value;
|
this._result.variables[prop] = value;
|
||||||
this._variableDirty = true;
|
this._variableDirty = true;
|
||||||
|
|
||||||
|
if(autosave)
|
||||||
|
{
|
||||||
|
clearTimeout(this._variableDebounce);
|
||||||
|
this._variableDebounce = setTimeout(() => this.saveVariables(), 2000);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
saveVariables()
|
saveVariables()
|
||||||
{
|
{
|
||||||
|
|
@ -716,7 +729,7 @@ export class CharacterBuilder extends CharacterCompiler
|
||||||
{
|
{
|
||||||
const feature = config.peoples[this._character.people!]!.options[level][this._character.leveling[level]!]!;
|
const feature = config.peoples[this._character.people!]!.options[level][this._character.leveling[level]!]!;
|
||||||
this.remove(feature);
|
this.remove(feature);
|
||||||
if(this._character.choices.hasOwnProperty(feature)) delete this._character.choices[feature];
|
if(feature in this._character.choices) delete this._character.choices[feature];
|
||||||
|
|
||||||
this.add(config.peoples[this._character.people!]!.options[level][choice]);
|
this.add(config.peoples[this._character.people!]!.options[level][choice]);
|
||||||
this._character.leveling[level] = choice;
|
this._character.leveling[level] = choice;
|
||||||
|
|
@ -868,8 +881,6 @@ class LevelPicker extends BuilderTab
|
||||||
{
|
{
|
||||||
private _levelInput: HTMLInputElement;
|
private _levelInput: HTMLInputElement;
|
||||||
private _pointsInput: HTMLInputElement;
|
private _pointsInput: HTMLInputElement;
|
||||||
private _healthText: Text;
|
|
||||||
private _manaText: Text;
|
|
||||||
|
|
||||||
private _options: HTMLElement[][];
|
private _options: HTMLElement[][];
|
||||||
|
|
||||||
|
|
@ -886,7 +897,6 @@ class LevelPicker extends BuilderTab
|
||||||
this.updateLevel();
|
this.updateLevel();
|
||||||
} });
|
} });
|
||||||
this._pointsInput = dom("input", { class: `w-14 mx-4 text-light-70 dark:text-dark-70 tabular-nums bg-light-10 dark:bg-dark-10 appearance-none outline-none ps-3 pe-1 py-1 focus:shadow-raw transition-[box-shadow] border bg-light-20 bg-dark-20 border-light-20 dark:border-dark-20`, attributes: { type: "number", disabled: true }});
|
this._pointsInput = dom("input", { class: `w-14 mx-4 text-light-70 dark:text-dark-70 tabular-nums bg-light-10 dark:bg-dark-10 appearance-none outline-none ps-3 pe-1 py-1 focus:shadow-raw transition-[box-shadow] border bg-light-20 bg-dark-20 border-light-20 dark:border-dark-20`, attributes: { type: "number", disabled: true }});
|
||||||
this._healthText = text("0"), this._manaText = text("0");
|
|
||||||
|
|
||||||
this._options = Object.entries(config.peoples[this._builder.character.people!]!.options).map(
|
this._options = Object.entries(config.peoples[this._builder.character.people!]!.options).map(
|
||||||
(level) => [ div("w-full flex h-px", [div("border-t border-dashed border-light-50 dark:border-dark-50 w-full"), dom('span', { class: "relative left-4" }, [ text(level[0]) ])]),
|
(level) => [ div("w-full flex h-px", [div("border-t border-dashed border-light-50 dark:border-dark-50 w-full"), dom('span', { class: "relative left-4" }, [ text(level[0]) ])]),
|
||||||
|
|
@ -913,11 +923,11 @@ class LevelPicker extends BuilderTab
|
||||||
]),
|
]),
|
||||||
div("flex justify-center items-center gap-2 my-2 md:text-base text-sm", [
|
div("flex justify-center items-center gap-2 my-2 md:text-base text-sm", [
|
||||||
dom("span", { text: "Vie" }),
|
dom("span", { text: "Vie" }),
|
||||||
this._healthText,
|
text(this._builder, '{{compiled.health}}'),
|
||||||
]),
|
]),
|
||||||
div("flex justify-center items-center gap-2 my-2 md:text-base text-sm", [
|
div("flex justify-center items-center gap-2 my-2 md:text-base text-sm", [
|
||||||
dom("span", { text: "Mana" }),
|
dom("span", { text: "Mana" }),
|
||||||
this._manaText,
|
text(this._builder, '{{compiled.mana}}'),
|
||||||
]),
|
]),
|
||||||
button(text('Suivant'), () => this._builder.display(2), 'h-[35px] px-[15px]'),
|
button(text('Suivant'), () => this._builder.display(2), 'h-[35px] px-[15px]'),
|
||||||
]), div('flex flex-col flex-1 gap-4 mx-8 my-4', this._options.flatMap(e => [...e]))];
|
]), div('flex flex-col flex-1 gap-4 mx-8 my-4', this._options.flatMap(e => [...e]))];
|
||||||
|
|
@ -926,12 +936,10 @@ class LevelPicker extends BuilderTab
|
||||||
}
|
}
|
||||||
override update()
|
override update()
|
||||||
{
|
{
|
||||||
const values = this._builder.values;
|
this._builder.compiled;
|
||||||
|
|
||||||
this._levelInput.value = this._builder.character.level.toString();
|
this._levelInput.value = this._builder.character.level.toString();
|
||||||
this._pointsInput.value = (this._builder.character.level - Object.keys(this._builder.character.leveling).length).toString();
|
this._pointsInput.value = (this._builder.character.level - Object.keys(this._builder.character.leveling).length).toString();
|
||||||
this._healthText.textContent = values.health?.toString() ?? '0';
|
|
||||||
this._manaText.textContent = values.mana?.toString() ?? '0';
|
|
||||||
|
|
||||||
this.updateLevel();
|
this.updateLevel();
|
||||||
}
|
}
|
||||||
|
|
@ -957,8 +965,6 @@ class LevelPicker extends BuilderTab
|
||||||
class TrainingPicker extends BuilderTab
|
class TrainingPicker extends BuilderTab
|
||||||
{
|
{
|
||||||
private _pointsInput: HTMLInputElement;
|
private _pointsInput: HTMLInputElement;
|
||||||
private _healthText: Text;
|
|
||||||
private _manaText: Text;
|
|
||||||
private _options: Record<MainStat, HTMLDivElement[][]>;
|
private _options: Record<MainStat, HTMLDivElement[][]>;
|
||||||
|
|
||||||
private _tab: number = 0;
|
private _tab: number = 0;
|
||||||
|
|
@ -989,7 +995,6 @@ class TrainingPicker extends BuilderTab
|
||||||
}
|
}
|
||||||
|
|
||||||
this._pointsInput = dom("input", { class: `w-14 mx-4 text-light-70 dark:text-dark-70 tabular-nums bg-light-10 dark:bg-dark-10 appearance-none outline-none ps-3 pe-1 py-1 focus:shadow-raw transition-[box-shadow] border bg-light-20 bg-dark-20 border-light-20 dark:border-dark-20`, attributes: { type: "number", disabled: true }});
|
this._pointsInput = dom("input", { class: `w-14 mx-4 text-light-70 dark:text-dark-70 tabular-nums bg-light-10 dark:bg-dark-10 appearance-none outline-none ps-3 pe-1 py-1 focus:shadow-raw transition-[box-shadow] border bg-light-20 bg-dark-20 border-light-20 dark:border-dark-20`, attributes: { type: "number", disabled: true }});
|
||||||
this._healthText = text("0"), this._manaText = text("0");
|
|
||||||
|
|
||||||
this._options = MAIN_STATS.reduce((p, v) => { p[v] = statRenderBlock(v); return p; }, {} as Record<MainStat, HTMLDivElement[][]>);
|
this._options = MAIN_STATS.reduce((p, v) => { p[v] = statRenderBlock(v); return p; }, {} as Record<MainStat, HTMLDivElement[][]>);
|
||||||
|
|
||||||
|
|
@ -1007,11 +1012,11 @@ class TrainingPicker extends BuilderTab
|
||||||
]),
|
]),
|
||||||
div("flex justify-center items-center gap-2 my-2 md:text-base text-sm", [
|
div("flex justify-center items-center gap-2 my-2 md:text-base text-sm", [
|
||||||
dom("span", { text: "Vie" }),
|
dom("span", { text: "Vie" }),
|
||||||
this._healthText,
|
text(this._builder, '{{compiled.health}}'),
|
||||||
]),
|
]),
|
||||||
div("flex justify-center items-center gap-2 my-2 md:text-base text-sm", [
|
div("flex justify-center items-center gap-2 my-2 md:text-base text-sm", [
|
||||||
dom("span", { text: "Mana" }),
|
dom("span", { text: "Mana" }),
|
||||||
this._manaText,
|
text(this._builder, '{{compiled.mana}}'),
|
||||||
]),
|
]),
|
||||||
button(text('Suivant'), () => this._builder.display(3), 'h-[35px] px-[15px]'),
|
button(text('Suivant'), () => this._builder.display(3), 'h-[35px] px-[15px]'),
|
||||||
]), dom('span')
|
]), dom('span')
|
||||||
|
|
@ -1036,8 +1041,6 @@ class TrainingPicker extends BuilderTab
|
||||||
const training = Object.values(this._builder.character.training).reduce((p, v) => p + Object.values(v).filter(e => e !== undefined).length, 0);
|
const training = Object.values(this._builder.character.training).reduce((p, v) => p + Object.values(v).filter(e => e !== undefined).length, 0);
|
||||||
|
|
||||||
this._pointsInput.value = ((values.training ?? 0) - training).toString();
|
this._pointsInput.value = ((values.training ?? 0) - training).toString();
|
||||||
this._healthText.textContent = values.health?.toString() ?? '0';
|
|
||||||
this._manaText.textContent = values.mana?.toString() ?? '0';
|
|
||||||
|
|
||||||
Object.keys(this._options).forEach(stat => {
|
Object.keys(this._options).forEach(stat => {
|
||||||
const max = Object.keys(this._builder.character.training[stat as MainStat]).length;
|
const max = Object.keys(this._builder.character.training[stat as MainStat]).length;
|
||||||
|
|
@ -1146,8 +1149,8 @@ class AspectPicker extends BuilderTab
|
||||||
this._mentalInput = dom("input", { class: `w-14 mx-4 text-light-70 dark:text-dark-70 tabular-nums bg-light-10 dark:bg-dark-10 appearance-none outline-none ps-3 pe-1 py-1 focus:shadow-raw transition-[box-shadow] border bg-light-20 bg-dark-20 border-light-20 dark:border-dark-20`, attributes: { type: "number", disabled: true }});
|
this._mentalInput = dom("input", { class: `w-14 mx-4 text-light-70 dark:text-dark-70 tabular-nums bg-light-10 dark:bg-dark-10 appearance-none outline-none ps-3 pe-1 py-1 focus:shadow-raw transition-[box-shadow] border bg-light-20 bg-dark-20 border-light-20 dark:border-dark-20`, attributes: { type: "number", disabled: true }});
|
||||||
this._personalityInput = dom("input", { class: `w-14 mx-4 text-light-70 dark:text-dark-70 tabular-nums bg-light-10 dark:bg-dark-10 appearance-none outline-none ps-3 pe-1 py-1 focus:shadow-raw transition-[box-shadow] border bg-light-20 bg-dark-20 border-light-20 dark:border-dark-20`, attributes: { type: "number", disabled: true }});
|
this._personalityInput = dom("input", { class: `w-14 mx-4 text-light-70 dark:text-dark-70 tabular-nums bg-light-10 dark:bg-dark-10 appearance-none outline-none ps-3 pe-1 py-1 focus:shadow-raw transition-[box-shadow] border bg-light-20 bg-dark-20 border-light-20 dark:border-dark-20`, attributes: { type: "number", disabled: true }});
|
||||||
|
|
||||||
this._options = config.aspects.map((e, i) => dom('div', { attributes: { "data-aspect": i.toString() }, listeners: { click: () => {
|
this._options = Object.values(config.aspects).map((e, i) => dom('div', { attributes: { "data-aspect": e.id }, listeners: { click: () => {
|
||||||
this._builder.character.aspect = i;
|
this._builder.character.aspect = e.id;
|
||||||
this._options.forEach(_e => _e.setAttribute('data-state', 'inactive'));
|
this._options.forEach(_e => _e.setAttribute('data-state', 'inactive'));
|
||||||
this._options[i]?.setAttribute('data-state', 'active');
|
this._options[i]?.setAttribute('data-state', 'active');
|
||||||
}}, class: 'group flex flex-col w-[360px] border border-light-35 dark:border-dark-35 hover:border-light-50 dark:hover:border-dark-50 cursor-pointer' }, [
|
}}, class: 'group flex flex-col w-[360px] border border-light-35 dark:border-dark-35 hover:border-light-50 dark:hover:border-dark-50 cursor-pointer' }, [
|
||||||
|
|
@ -1211,10 +1214,10 @@ class AspectPicker extends BuilderTab
|
||||||
this._personalityInput.value = personality.toString();
|
this._personalityInput.value = personality.toString();
|
||||||
|
|
||||||
(this._content[1] as HTMLElement).replaceChildren(...this._options.filter(e => {
|
(this._content[1] as HTMLElement).replaceChildren(...this._options.filter(e => {
|
||||||
const index = parseInt(e.getAttribute('data-aspect')!);
|
const id = e.getAttribute('data-aspect')!;
|
||||||
const aspect = config.aspects[index]!;
|
const aspect = config.aspects[id]!;
|
||||||
|
|
||||||
e.setAttribute('data-state', this._builder.character.aspect === index ? 'active' : 'inactive');
|
e.setAttribute('data-state', this._builder.character.aspect === id ? 'active' : 'inactive');
|
||||||
|
|
||||||
if(!this._filter)
|
if(!this._filter)
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -1392,13 +1395,13 @@ export class CharacterSheet
|
||||||
};
|
};
|
||||||
|
|
||||||
this.tabs = tabgroup([
|
this.tabs = tabgroup([
|
||||||
{ id: 'actions', title: [ text('Actions') ], content: () => this.actionsTab(character) },
|
{ id: 'actions', title: [ text('Actions') ], content: this.actionsTab(character) },
|
||||||
|
|
||||||
{ id: 'abilities', title: [ text('Aptitudes') ], content: () => this.abilitiesTab(character) },
|
{ id: 'abilities', title: [ text('Aptitudes') ], content: this.abilitiesTab(character) },
|
||||||
|
|
||||||
{ id: 'spells', title: [ text('Sorts') ], content: () => this.spellTab(character) },
|
{ id: 'spells', title: [ text('Sorts') ], content: this.spellTab(character) },
|
||||||
|
|
||||||
{ id: 'inventory', title: [ text('Inventaire') ], content: () => this.itemsTab(character) },
|
{ id: 'inventory', title: [ text('Inventaire') ], content: this.itemsTab(character) },
|
||||||
|
|
||||||
{ id: 'notes', title: [ text('Notes') ], content: () => [
|
{ id: 'notes', title: [ text('Notes') ], content: () => [
|
||||||
div('flex flex-col gap-2', [
|
div('flex flex-col gap-2', [
|
||||||
|
|
@ -1434,51 +1437,51 @@ export class CharacterSheet
|
||||||
dom("span", { class: "flex flex-row items-center gap-2 text-3xl font-light" }, [
|
dom("span", { class: "flex flex-row items-center gap-2 text-3xl font-light" }, [
|
||||||
text("PV: "),
|
text("PV: "),
|
||||||
health.readonly,
|
health.readonly,
|
||||||
text(`/ ${character.health}`)
|
text(character, `/ {{health}}`),
|
||||||
]),
|
]),
|
||||||
dom("span", { class: "flex flex-row items-center gap-2 text-3xl font-light" }, [
|
dom("span", { class: "flex flex-row items-center gap-2 text-3xl font-light" }, [
|
||||||
text("Mana: "),
|
text("Mana: "),
|
||||||
mana.readonly,
|
mana.readonly,
|
||||||
text(`/ ${character.mana}`)
|
text(character, `/ {{mana}}`),
|
||||||
])
|
]),
|
||||||
])
|
]),
|
||||||
]),
|
]),
|
||||||
|
|
||||||
div("self-center", [
|
div("self-center", [
|
||||||
this.user.value && this.user.value.id === character.owner ?
|
this.user.value && this.user.value.id === character.owner ?
|
||||||
button(icon("radix-icons:pencil-2"), () => useRouter().push({ name: 'character-id-edit', params: { id: this.character?.character.id } }), "p-1")
|
button(icon("radix-icons:pencil-2"), () => useRouter().push({ name: 'character-id-edit', params: { id: this.character?.character.id } }), "p-1")
|
||||||
: div()
|
: div(),
|
||||||
])
|
]),
|
||||||
]),
|
]),
|
||||||
|
|
||||||
div("flex flex-row justify-center 2xl:gap-4 gap-2 p-4 border-b border-light-35 dark:border-dark-35", [
|
div("flex flex-row justify-center 2xl:gap-4 gap-2 p-4 border-b border-light-35 dark:border-dark-35", [
|
||||||
div("flex 2xl:gap-4 gap-2 flex-row items-center justify-between", [
|
div("flex 2xl:gap-4 gap-2 flex-row items-center justify-between", [
|
||||||
div("flex flex-col items-center px-2", [
|
div("flex flex-col items-center px-2", [
|
||||||
dom("span", { class: "2xl:text-2xl text-xl font-bold", text: `+${character.modifier.strength}` }),
|
dom("span", { class: "2xl:text-2xl text-xl font-bold" }, [ text(character, `+{{modifier.strength}}`) ]),
|
||||||
dom("span", { class: "text-sm 2xl:text-base", text: "Force" })
|
dom("span", { class: "text-sm 2xl:text-base", text: "Force" })
|
||||||
]),
|
]),
|
||||||
div("flex flex-col items-center px-2", [
|
div("flex flex-col items-center px-2", [
|
||||||
dom("span", { class: "2xl:text-2xl text-xl font-bold", text: `+${character.modifier.dexterity}` }),
|
dom("span", { class: "2xl:text-2xl text-xl font-bold" }, [ text(character, `+{{modifier.dexterity}}`) ]),
|
||||||
dom("span", { class: "text-sm 2xl:text-base", text: "Dextérité" })
|
dom("span", { class: "text-sm 2xl:text-base", text: "Dextérité" })
|
||||||
]),
|
]),
|
||||||
div("flex flex-col items-center px-2", [
|
div("flex flex-col items-center px-2", [
|
||||||
dom("span", { class: "2xl:text-2xl text-xl font-bold", text: `+${character.modifier.constitution}` }),
|
dom("span", { class: "2xl:text-2xl text-xl font-bold" }, [ text(character, `+{{modifier.constitution}}`) ]),
|
||||||
dom("span", { class: "text-sm 2xl:text-base", text: "Constitution" })
|
dom("span", { class: "text-sm 2xl:text-base", text: "Constitution" })
|
||||||
]),
|
]),
|
||||||
div("flex flex-col items-center px-2", [
|
div("flex flex-col items-center px-2", [
|
||||||
dom("span", { class: "2xl:text-2xl text-xl font-bold", text: `+${character.modifier.intelligence}` }),
|
dom("span", { class: "2xl:text-2xl text-xl font-bold" }, [ text(character, `+{{modifier.intelligence}}`) ]),
|
||||||
dom("span", { class: "text-sm 2xl:text-base", text: "Intelligence" })
|
dom("span", { class: "text-sm 2xl:text-base", text: "Intelligence" })
|
||||||
]),
|
]),
|
||||||
div("flex flex-col items-center px-2", [
|
div("flex flex-col items-center px-2", [
|
||||||
dom("span", { class: "2xl:text-2xl text-xl font-bold", text: `+${character.modifier.curiosity}` }),
|
dom("span", { class: "2xl:text-2xl text-xl font-bold" }, [ text(character, `+{{modifier.curiosity}}`) ]),
|
||||||
dom("span", { class: "text-sm 2xl:text-base", text: "Curiosité" })
|
dom("span", { class: "text-sm 2xl:text-base", text: "Curiosité" })
|
||||||
]),
|
]),
|
||||||
div("flex flex-col items-center px-2", [
|
div("flex flex-col items-center px-2", [
|
||||||
dom("span", { class: "2xl:text-2xl text-xl font-bold", text: `+${character.modifier.charisma}` }),
|
dom("span", { class: "2xl:text-2xl text-xl font-bold" }, [ text(character, `+{{modifier.charisma}}`) ]),
|
||||||
dom("span", { class: "text-sm 2xl:text-base", text: "Charisme" })
|
dom("span", { class: "text-sm 2xl:text-base", text: "Charisme" })
|
||||||
]),
|
]),
|
||||||
div("flex flex-col items-center px-2", [
|
div("flex flex-col items-center px-2", [
|
||||||
dom("span", { class: "2xl:text-2xl text-xl font-bold", text: `+${character.modifier.psyche}` }),
|
dom("span", { class: "2xl:text-2xl text-xl font-bold" }, [ text(character, `+{{modifier.psyche}}`) ]),
|
||||||
dom("span", { class: "text-sm 2xl:text-base", text: "Psyché" })
|
dom("span", { class: "text-sm 2xl:text-base", text: "Psyché" })
|
||||||
])
|
])
|
||||||
]),
|
]),
|
||||||
|
|
@ -1487,11 +1490,11 @@ export class CharacterSheet
|
||||||
|
|
||||||
div("flex 2xl:gap-4 gap-2 flex-row items-center justify-between", [
|
div("flex 2xl:gap-4 gap-2 flex-row items-center justify-between", [
|
||||||
div("flex flex-col px-2 items-center", [
|
div("flex flex-col px-2 items-center", [
|
||||||
dom("span", { class: "2xl:text-2xl text-xl font-bold", text: `+${character.initiative}` }),
|
dom("span", { class: "2xl:text-2xl text-xl font-bold" }, [ text(character, `+{{initiative}}`) ]),
|
||||||
dom("span", { class: "text-sm 2xl:text-base", text: "Initiative" })
|
dom("span", { class: "text-sm 2xl:text-base", text: "Initiative" })
|
||||||
]),
|
]),
|
||||||
div("flex flex-col px-2 items-center", [
|
div("flex flex-col px-2 items-center", [
|
||||||
dom("span", { class: "2xl:text-2xl text-xl font-bold", text: character.speed === false ? "Aucun déplacement" : `${character.speed} cases` }),
|
dom("span", { class: "2xl:text-2xl text-xl font-bold" }, [ text(character, () => character.speed === false ? "Aucun déplacement" : `{{speed}} cases`) ]),
|
||||||
dom("span", { class: "text-sm 2xl:text-base", text: "Course" })
|
dom("span", { class: "text-sm 2xl:text-base", text: "Course" })
|
||||||
])
|
])
|
||||||
]),
|
]),
|
||||||
|
|
@ -1501,24 +1504,15 @@ export class CharacterSheet
|
||||||
div("flex 2xl:gap-4 gap-2 flex-row items-center justify-between", [
|
div("flex 2xl:gap-4 gap-2 flex-row items-center justify-between", [
|
||||||
icon("game-icons:checked-shield", { width: 32, height: 32 }),
|
icon("game-icons:checked-shield", { width: 32, height: 32 }),
|
||||||
div("flex flex-col px-2 items-center", [
|
div("flex flex-col px-2 items-center", [
|
||||||
dom("span", {
|
dom("span", { class: "2xl:text-2xl text-xl font-bold" }, [ text(character, `{{defense.passive}}`) ]),
|
||||||
class: "2xl:text-2xl text-xl font-bold",
|
|
||||||
text: `${clamp(character.defense.static + character.defense.passivedodge + character.defense.passiveparry, 0, character.defense.hardcap)}`
|
|
||||||
}),
|
|
||||||
dom("span", { class: "text-sm 2xl:text-base", text: "Passive" })
|
dom("span", { class: "text-sm 2xl:text-base", text: "Passive" })
|
||||||
]),
|
]),
|
||||||
div("flex flex-col px-2 items-center", [
|
div("flex flex-col px-2 items-center", [
|
||||||
dom("span", {
|
dom("span", { class: "2xl:text-2xl text-xl font-bold" }, [ text(character, `{{defense.parry}}`) ]),
|
||||||
class: "2xl:text-2xl text-xl font-bold",
|
|
||||||
text: `${clamp(character.defense.static + character.defense.passivedodge + character.defense.activeparry, 0, character.defense.hardcap)}`
|
|
||||||
}),
|
|
||||||
dom("span", { class: "text-sm 2xl:text-base", text: "Blocage" })
|
dom("span", { class: "text-sm 2xl:text-base", text: "Blocage" })
|
||||||
]),
|
]),
|
||||||
div("flex flex-col px-2 items-center", [
|
div("flex flex-col px-2 items-center", [
|
||||||
dom("span", {
|
dom("span", { class: "2xl:text-2xl text-xl font-bold" }, [ text(character, `{{defense.dodge}}`) ]),
|
||||||
class: "2xl:text-2xl text-xl font-bold",
|
|
||||||
text: `${clamp(character.defense.static + character.defense.activedodge + character.defense.passiveparry, 0, character.defense.hardcap)}`
|
|
||||||
}),
|
|
||||||
dom("span", { class: "text-sm 2xl:text-base", text: "Esquive" })
|
dom("span", { class: "text-sm 2xl:text-base", text: "Esquive" })
|
||||||
])
|
])
|
||||||
]),
|
]),
|
||||||
|
|
@ -1533,10 +1527,10 @@ export class CharacterSheet
|
||||||
]),
|
]),
|
||||||
|
|
||||||
div("grid grid-cols-2 gap-2",
|
div("grid grid-cols-2 gap-2",
|
||||||
Object.entries(character.abilities).map(([ability, value]) =>
|
Object.keys(character.abilities).map((ability) =>
|
||||||
div("flex flex-row px-1 justify-between items-center", [
|
div("flex flex-row px-1 justify-between items-center", [
|
||||||
span("text-sm text-light-70 dark:text-dark-70 max-w-20 truncate", abilityTexts[ability as Ability] || ability),
|
span("text-sm text-light-70 dark:text-dark-70 max-w-20 truncate", abilityTexts[ability as Ability] || ability),
|
||||||
span("font-bold text-base text-light-100 dark:text-dark-100", `+${value}`),
|
span("font-bold text-base text-light-100 dark:text-dark-100", text(character.abilities, `+{{${ability}}}`)),
|
||||||
])
|
])
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
|
@ -1595,10 +1589,10 @@ export class CharacterSheet
|
||||||
|
|
||||||
div('flex flex-col gap-2', [
|
div('flex flex-col gap-2', [
|
||||||
div('flex flex-row flex-wrap gap-2 text-light-60 dark:text-dark-60', ["Attaquer", "Désarmer", "Saisir", "Faire chuter", "Déplacer", "Courir", "Pas de coté", "Charger", "Lancer un sort", "S'interposer", "Se transformer", "Utiliser un objet", "Anticiper une action", "Improviser"].map(e => dom('span', { text: e, class: 'cursor-pointer text-sm decoration-dotted underline' }))),
|
div('flex flex-row flex-wrap gap-2 text-light-60 dark:text-dark-60', ["Attaquer", "Désarmer", "Saisir", "Faire chuter", "Déplacer", "Courir", "Pas de coté", "Charger", "Lancer un sort", "S'interposer", "Se transformer", "Utiliser un objet", "Anticiper une action", "Improviser"].map(e => dom('span', { text: e, class: 'cursor-pointer text-sm decoration-dotted underline' }))),
|
||||||
...(character.lists.action?.map(e => div('flex flex-col gap-1', [
|
div('flex flex-col gap-2', { render: (e) => div('flex flex-col gap-1', [
|
||||||
div('flex flex-row justify-between', [dom('span', { class: 'text-lg', text: config.action[e]?.name }), config.action[e]?.cost ? div('flex flex-row', [dom('span', { class: 'font-bold', text: config.action[e]?.cost?.toString() }), text(`point${config.action[e]?.cost > 1 ? 's' : ''} d'action`)]) : undefined]),
|
div('flex flex-row justify-between', [dom('span', { class: 'text-lg', text: config.action[e]?.name }), config.action[e]?.cost ? div('flex flex-row gap-1', [dom('span', { class: 'font-bold', text: config.action[e]?.cost?.toString() }), text(`point${config.action[e]?.cost > 1 ? 's' : ''} d'action`)]) : undefined]),
|
||||||
markdown(getText(config.action[e]?.description), undefined, { tags: { a: preview } }),
|
markdown(getText(config.action[e]?.description), undefined, { tags: { a: preview } }),
|
||||||
])) ?? [])
|
]), list: character.lists.action }),
|
||||||
]),
|
]),
|
||||||
]),
|
]),
|
||||||
div('flex flex-col gap-2', [
|
div('flex flex-col gap-2', [
|
||||||
|
|
@ -1610,10 +1604,10 @@ export class CharacterSheet
|
||||||
|
|
||||||
div('flex flex-col gap-2', [
|
div('flex flex-col gap-2', [
|
||||||
div('flex flex-row flex-wrap gap-2 text-light-60 dark:text-dark-60', ["Parer", "Esquiver", "Saisir une opportunité", "Prendre en tenaille", "Intercepter"].map(e => dom('span', { text: e, class: 'cursor-pointer text-sm decoration-dotted underline' }))),
|
div('flex flex-row flex-wrap gap-2 text-light-60 dark:text-dark-60', ["Parer", "Esquiver", "Saisir une opportunité", "Prendre en tenaille", "Intercepter"].map(e => dom('span', { text: e, class: 'cursor-pointer text-sm decoration-dotted underline' }))),
|
||||||
...(character.lists.reaction?.map(e => div('flex flex-col gap-1', [
|
div('flex flex-col gap-2', { render: (e) => div('flex flex-col gap-1', [
|
||||||
div('flex flex-row justify-between', [dom('span', { class: 'text-lg', text: config.reaction[e]?.name }), config.reaction[e]?.cost ? div('flex flex-row', [dom('span', { class: 'font-bold', text: config.reaction[e]?.cost?.toString() }), text(`point${config.reaction[e]?.cost > 1 ? 's' : ''} d'action`)]) : undefined]),
|
div('flex flex-row justify-between', [dom('span', { class: 'text-lg', text: config.reaction[e]?.name }), config.reaction[e]?.cost ? div('flex flex-row gap-1', [dom('span', { class: 'font-bold', text: config.reaction[e]?.cost?.toString() }), text(`point${config.reaction[e]?.cost > 1 ? 's' : ''} d'action`)]) : undefined]),
|
||||||
markdown(getText(config.reaction[e]?.description), undefined, { tags: { a: preview } }),
|
markdown(getText(config.reaction[e]?.description), undefined, { tags: { a: preview } }),
|
||||||
])) ?? [])
|
]), list: character.lists.reaction }),
|
||||||
]),
|
]),
|
||||||
]),
|
]),
|
||||||
div('flex flex-col gap-2', [
|
div('flex flex-col gap-2', [
|
||||||
|
|
@ -1624,10 +1618,10 @@ export class CharacterSheet
|
||||||
|
|
||||||
div('flex flex-col gap-2', [
|
div('flex flex-col gap-2', [
|
||||||
div('flex flex-row flex-wrap gap-2 text-light-60 dark:text-dark-60', ["Analyser une situation", "Communiquer", "Dégainer", "Attraper un objet"].map(e => dom('span', { text: e, class: 'cursor-pointer text-sm decoration-dotted underline' }))),
|
div('flex flex-row flex-wrap gap-2 text-light-60 dark:text-dark-60', ["Analyser une situation", "Communiquer", "Dégainer", "Attraper un objet"].map(e => dom('span', { text: e, class: 'cursor-pointer text-sm decoration-dotted underline' }))),
|
||||||
...(character.lists.freeaction?.map(e => div('flex flex-col gap-1', [
|
div('flex flex-col gap-2', { render: e => div('flex flex-col gap-1', [
|
||||||
div('flex flex-row justify-between', [dom('span', { class: 'text-lg', text: config.freeaction[e]?.name }) ]),
|
div('flex flex-row justify-between', [dom('span', { class: 'text-lg', text: config.freeaction[e]?.name }) ]),
|
||||||
markdown(getText(config.freeaction[e]?.description), undefined, { tags: { a: preview } }),
|
markdown(getText(config.freeaction[e]?.description), undefined, { tags: { a: preview } }),
|
||||||
])) ?? [])
|
]), list: character.lists.reaction })
|
||||||
]),
|
]),
|
||||||
]),
|
]),
|
||||||
]),
|
]),
|
||||||
|
|
@ -1636,50 +1630,54 @@ export class CharacterSheet
|
||||||
abilitiesTab(character: CompiledCharacter)
|
abilitiesTab(character: CompiledCharacter)
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
div('flex flex-col gap-2', [
|
div('flex flex-col gap-2', { render: e => div('flex flex-col gap-1', [
|
||||||
...(character.lists.passive?.map(e => div('flex flex-col gap-1', [
|
div('flex flex-row justify-between', [dom('span', { class: 'text-lg', text: config.passive[e]?.name }) ]),
|
||||||
div('flex flex-row justify-between', [dom('span', { class: 'text-lg', text: config.passive[e]?.name }) ]),
|
markdown(getText(config.passive[e]?.description), undefined, { tags: { a: preview } }),
|
||||||
markdown(getText(config.passive[e]?.description), undefined, { tags: { a: preview } }),
|
]), list: character.lists.passive }),
|
||||||
])) ?? []),
|
|
||||||
]),
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
spellTab(character: CompiledCharacter)
|
spellTab(character: CompiledCharacter)
|
||||||
{
|
{
|
||||||
let sortPreference = (localStorage.getItem('character-sort') ?? 'rank') as 'rank' | 'type' | 'element';
|
let sortPreference = (localStorage.getItem('character-sort') ?? 'rank') as 'rank' | 'type' | 'element';
|
||||||
|
|
||||||
const sort = (spells: Array<{ id: string, spell?: SpellConfig, source: string }>) => {
|
const sort = () => {
|
||||||
spells = spells.filter(e => !!e.spell);
|
|
||||||
switch(sortPreference)
|
switch(sortPreference)
|
||||||
{
|
{
|
||||||
case 'rank': return spells.sort((a, b) => a.spell!.rank - b.spell!.rank || SPELL_ELEMENTS.indexOf(a.spell!.elements[0]!) - SPELL_ELEMENTS.indexOf(b.spell!.elements[0]!));
|
case 'rank': return container.array.sort((a, b) => (config.spells[a]?.rank ?? 0) - (config.spells[b]?.rank ?? 0) || SPELL_ELEMENTS.indexOf(config.spells[a]?.elements[0]!) - SPELL_ELEMENTS.indexOf(config.spells[b]?.elements[0]!));
|
||||||
case 'type': return spells.sort((a, b) => a.spell!.type.localeCompare(b.spell!.type) || a.spell!.rank - b.spell!.rank);
|
case 'type': return container.array.sort((a, b) => config.spells[a]?.type.localeCompare(config.spells[b]?.type ?? '') || (config.spells[a]?.rank ?? 0) - (config.spells[b]?.rank ?? 0));
|
||||||
case 'element': return spells.sort((a, b) => SPELL_ELEMENTS.indexOf(a.spell!.elements[0]!) - SPELL_ELEMENTS.indexOf(b.spell!.elements[0]!) || a.spell!.rank - b.spell!.rank);
|
case 'element': return container.array.sort((a, b) => SPELL_ELEMENTS.indexOf(config.spells[a]?.elements[0]!) - SPELL_ELEMENTS.indexOf(config.spells[b]?.elements[0]!) || (config.spells[a]?.rank ?? 0) - (config.spells[b]?.rank ?? 0));
|
||||||
default: return spells;
|
default: return container.array;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const spells = sort([...(character.lists.spells ?? []).map(e => ({ id: e, spell: config.spells.find(_e => _e.id === e), source: 'feature' })), ...character.variables.spells.map(e => ({ id: e, spell: config.spells.find(_e => _e.id === e), source: 'player' }))]).map(e => ({...e, dom:
|
const container = div('flex flex-col gap-2', { render: e => {
|
||||||
e.spell ? div('flex flex-col gap-2', [
|
const spell = config.spells[e];
|
||||||
div('flex flex-row items-center gap-4', [ dom('span', { class: 'font-semibold text-lg', text: e.spell.name ?? 'Inconnu' }), div('flex-1 border-b border-dashed border-light-50 dark:border-dark-50'), dom('span', { class: 'text-light-70 dark:text-dark-70', text: `${e.spell.cost ?? 0} mana` }) ]),
|
|
||||||
|
if(!spell)
|
||||||
|
return;
|
||||||
|
|
||||||
|
return div('flex flex-col gap-2', [
|
||||||
|
div('flex flex-row items-center gap-4', [ dom('span', { class: 'font-semibold text-lg', text: spell.name ?? 'Inconnu' }), div('flex-1 border-b border-dashed border-light-50 dark:border-dark-50'), dom('span', { class: 'text-light-70 dark:text-dark-70', text: `${spell.cost ?? 0} mana` }) ]),
|
||||||
div('flex flex-row justify-between items-center gap-2 text-light-70 dark:text-dark-70', [
|
div('flex flex-row justify-between items-center gap-2 text-light-70 dark:text-dark-70', [
|
||||||
div('flex flex-row gap-2', [ span('flex flex-row', e.spell.rank === 4 ? 'Sort unique' : `Sort ${e.spell.type === 'instinct' ? 'd\'instinct' : e.spell.type === 'knowledge' ? 'de savoir' : 'de précision'} de rang ${e.spell.rank}`), ...(e.spell.elements ?? []).map(elementDom) ]),
|
div('flex flex-row gap-2', [ span('flex flex-row', spell.rank === 4 ? 'Sort unique' : `Sort ${spell.type === 'instinct' ? 'd\'instinct' : spell.type === 'knowledge' ? 'de savoir' : 'de précision'} de rang ${spell.rank}`), ...(spell.elements ?? []).map(elementDom) ]),
|
||||||
div('flex flex-row gap-4 items-center', [ e.spell.concentration ? proses('a', preview, [span('italic text-sm', 'concentration')], { href: '' }) : undefined, span(undefined, typeof e.spell.range === 'number' && e.spell.range > 0 ? `${e.spell.range} case${e.spell.range > 1 ? 's' : ''}` : e.spell.range === 0 ? 'toucher' : 'personnel'), span(undefined, typeof e.spell.speed === 'number' ? `${e.spell.speed} minute${e.spell.speed > 1 ? 's' : ''}` : e.spell.speed) ])
|
div('flex flex-row gap-4 items-center', [ spell.concentration ? proses('a', preview, [span('italic text-sm', 'concentration')], { href: '' }) : undefined, span(undefined, typeof spell.range === 'number' && spell.range > 0 ? `${spell.range} case${spell.range > 1 ? 's' : ''}` : spell.range === 0 ? 'toucher' : 'personnel'), span(undefined, typeof spell.speed === 'number' ? `${spell.speed} minute${spell.speed > 1 ? 's' : ''}` : spell.speed) ])
|
||||||
]),
|
]),
|
||||||
div('flex flex-row ps-4 p-1 border-l-4 border-light-35 dark:border-dark-35', [ markdown(e.spell.description) ]),
|
div('flex flex-row ps-4 p-1 border-l-4 border-light-35 dark:border-dark-35', [ markdown(spell.description) ]),
|
||||||
]) : undefined }));
|
])
|
||||||
|
}, list: [...(character.lists.spells ?? []), ...character.variables.spells] });
|
||||||
|
sort().render();
|
||||||
return [
|
return [
|
||||||
div('flex flex-col gap-2', [
|
div('flex flex-col gap-2', [
|
||||||
div('flex flex-row justify-between items-center', [
|
div('flex flex-row justify-between items-center', [
|
||||||
div('flex flex-row gap-2 items-center', [
|
div('flex flex-row gap-2 items-center', [
|
||||||
dom('span', { class: 'italic tracking-tight text-sm', text: 'Trier par' }),
|
dom('span', { class: 'italic tracking-tight text-sm', text: 'Trier par' }),
|
||||||
buttongroup<'rank' | 'type' | 'element'>([{ text: 'Rang', value: 'rank' }, { text: 'Type', value: 'type' }, { text: 'Element', value: 'element' }], { value: sortPreference, class: { option: 'px-2 py-1 text-sm' }, onChange: (value) => { localStorage.setItem('character-sort', value); sortPreference = value; this.tabs?.refresh(); } }),
|
buttongroup<'rank' | 'type' | 'element'>([{ text: 'Rang', value: 'rank' }, { text: 'Type', value: 'type' }, { text: 'Element', value: 'element' }], { value: sortPreference, class: { option: 'px-2 py-1 text-sm' }, onChange: (value) => { localStorage.setItem('character-sort', value); sortPreference = value; sort().render(); } }),
|
||||||
]),
|
]),
|
||||||
div('flex flex-row gap-2 items-center', [
|
div('flex flex-row gap-2 items-center', [
|
||||||
dom('span', { class: ['italic text-sm', { 'text-light-red dark:text-dark-red': character.variables.spells.length !== character.spellslots }], text: `${character.variables.spells.length}/${character.spellslots} sort${character.variables.spells.length > 1 ? 's' : ''} maitrisé${character.variables.spells.length > 1 ? 's' : ''}` }),
|
dom('span', { class: ['italic text-sm', { 'text-light-red dark:text-dark-red': character.variables.spells.length !== character.spellslots }], text: `${character.variables.spells.length}/${character.spellslots} sort${character.variables.spells.length > 1 ? 's' : ''} maitrisé${character.variables.spells.length > 1 ? 's' : ''}` }),
|
||||||
button(text('Modifier'), () => this.spellPanel(character), 'py-1 px-4'),
|
button(text('Modifier'), () => this.spellPanel(character), 'py-1 px-4'),
|
||||||
])
|
])
|
||||||
]),
|
]),
|
||||||
div('flex flex-col gap-2', spells.map(e => e.dom))
|
container,
|
||||||
])
|
])
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -1752,66 +1750,59 @@ export class CharacterSheet
|
||||||
}
|
}
|
||||||
itemsTab(character: CompiledCharacter)
|
itemsTab(character: CompiledCharacter)
|
||||||
{
|
{
|
||||||
let debounceId: NodeJS.Timeout | undefined;
|
|
||||||
//TODO: Recompile values on "equip" checkbox change
|
|
||||||
const items = (character.variables.items.map(e => ({ ...e, item: config.items[e.id], ref: e })).filter(e => !!e.item) as Array<ItemState & { item: ItemConfig, ref: ItemState }>).map(e => {
|
|
||||||
const price = div(['flex flex-row min-w-16 gap-2 justify-between items-center px-2', { 'cursor-help': e.amount > 1 }], [ icon('ph:coin', { width: 16, height: 16, class: 'text-light-70 dark:text-dark-70' }), span({ 'underline decoration-1 decoration-dotted underline-offset-2': e.amount > 1 }, e.item.price ? `${e.item.price * e.amount}` : '-') ]);
|
|
||||||
const weight = div(['flex flex-row min-w-16 gap-2 justify-between items-center px-2', { 'cursor-help': e.amount > 1 }], [ icon('mdi:weight', { width: 16, height: 16, class: 'text-light-70 dark:text-dark-70' }), span({ 'underline decoration-1 decoration-dotted underline-offset-2': e.amount > 1 }, e.item.weight ? `${e.item.weight * e.amount}` : '-') ]);
|
|
||||||
return foldable(() => [
|
|
||||||
markdown(getText(e.item.description)),
|
|
||||||
div('flex flex-row justify-center', [
|
|
||||||
this.character?.character.campaign ? button(text('Partager'), () => {
|
|
||||||
|
|
||||||
}, 'p-1') : undefined,
|
|
||||||
button(icon('radix-icons:trash'), () => {
|
|
||||||
const idx = this.character!.character.variables.items.findIndex(_e => _e.id === e.id);
|
|
||||||
if(idx === -1) return;
|
|
||||||
|
|
||||||
this.character!.character.variables.items[idx]!.amount--;
|
|
||||||
if(this.character!.character.variables.items[idx]!.amount >= 0) this.character!.character.variables.items.splice(idx, 1);
|
|
||||||
|
|
||||||
this.character!.variable('items', this.character!.character.variables.items);
|
|
||||||
|
|
||||||
debounceId && clearTimeout(debounceId);
|
|
||||||
debounceId = setTimeout(() => this.character?.saveVariables(), 2000);
|
|
||||||
|
|
||||||
this.tabs?.refresh();
|
|
||||||
}, 'p-1'),
|
|
||||||
]) ], [div('flex flex-row justify-between', [
|
|
||||||
div('flex flex-row items-center gap-4', [
|
|
||||||
e.item.equippable ? checkbox({ defaultValue: e.equipped, change: v => {
|
|
||||||
e.equipped = e.ref.equipped = v;
|
|
||||||
|
|
||||||
this.character!.variable('items', this.character!.character.variables.items);
|
|
||||||
|
|
||||||
debounceId && clearTimeout(debounceId);
|
|
||||||
debounceId = setTimeout(() => this.character?.saveVariables(), 2000);
|
|
||||||
}, class: { container: '!w-5 !h-5' } }) : undefined,
|
|
||||||
div('flex flex-row items-center gap-4', [ span([colorByRarity[e.item.rarity], 'text-lg'], e.item.name), div('flex flex-row gap-2 text-light-60 dark:text-dark-60 text-sm italic', subnameFactory(e.item).map(e => span('', e))) ]),
|
|
||||||
]),
|
|
||||||
div('flex flex-row items-center divide-x divide-light-50 dark:divide-dark-50 divide-dashed px-2', [
|
|
||||||
e.amount > 1 ? tooltip(price, `Prix unitaire: ${e.item.price}`, 'bottom') : price,
|
|
||||||
div('flex flex-row min-w-16 gap-2 justify-between items-center px-2', [ icon('radix-icons:cross-2', { width: 16, height: 16, class: 'text-light-70 dark:text-dark-70' }), span('', e.amount?.toString() ?? '-') ]),
|
|
||||||
div('flex flex-row min-w-16 gap-2 justify-between items-center px-2', [ icon('game-icons:bolt-drop', { width: 16, height: 16, class: 'text-light-70 dark:text-dark-70' }), span('', e.item.powercost || e.item.capacity ? `${e.item.powercost ?? 0}/${e.item.capacity ?? 0}` : '-') ]),
|
|
||||||
e.amount > 1 ? tooltip(weight, `Poids unitaire: ${e.item.weight}`, 'bottom') : weight,
|
|
||||||
div('flex flex-row min-w-16 gap-2 justify-between items-center px-2', [ icon('game-icons:battery-pack', { width: 16, height: 16, class: 'text-light-70 dark:text-dark-70' }), span('', e.item.charge ? `${e.item.charge}` : '-') ]),
|
|
||||||
]),
|
|
||||||
])], { open: false, class: { icon: 'px-2', container: 'p-1 gap-2', content: 'px-4 pb-1 flex flex-col' } })
|
|
||||||
});
|
|
||||||
|
|
||||||
const power = character.variables.items.filter(e => config.items[e.id]?.equippable && e.equipped).reduce((p, v) => p + ((config.items[v.id]?.powercost ?? 0) + (v.enchantments?.reduce((_p, _v) => (config.enchantments[_v]?.power ?? 0) + _p, 0) ?? 0) * v.amount), 0);
|
const power = character.variables.items.filter(e => config.items[e.id]?.equippable && e.equipped).reduce((p, v) => p + ((config.items[v.id]?.powercost ?? 0) + (v.enchantments?.reduce((_p, _v) => (config.enchantments[_v]?.power ?? 0) + _p, 0) ?? 0) * v.amount), 0);
|
||||||
const weight = character.variables.items.reduce((p, v) => p + (config.items[v.id]?.weight ?? 0) * v.amount, 0);
|
const weight = character.variables.items.reduce((p, v) => p + (config.items[v.id]?.weight ?? 0) * v.amount, 0);
|
||||||
|
const items = this.character!.character.variables.items;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
div('flex flex-col gap-2', [
|
div('flex flex-col gap-2', [
|
||||||
div('flex flex-row justify-end items-center gap-8', [
|
div('flex flex-row justify-end items-center gap-8', [
|
||||||
dom('span', { class: ['italic text-sm', { 'text-light-red dark:text-dark-red': weight > character.itempower }], text: `Poids total: ${weight}/${character.itempower}` }),
|
dom('span', { class: () => ['italic text-sm', { 'text-light-red dark:text-dark-red': weight > character.itempower }], text: () => `Poids total: ${weight}/${character.itempower}` }),
|
||||||
dom('span', { class: ['italic text-sm', { 'text-light-red dark:text-dark-red': power > (character.capacity === false ? 0 : character.capacity) }], text: `Puissance magique: ${power}/${character.capacity}` }),
|
dom('span', { class: () => ['italic text-sm', { 'text-light-red dark:text-dark-red': power > (character.capacity === false ? 0 : character.capacity) }], text: () => `Puissance magique: ${power}/${character.capacity}` }),
|
||||||
button(text('Modifier'), () => this.itemsPanel(character), 'py-1 px-4'),
|
button(text('Modifier'), () => this.itemsPanel(character), 'py-1 px-4'),
|
||||||
]),
|
]),
|
||||||
div('flex flex-col flex-1 divide-y divide-light-35 dark:divide-dark-35', items)
|
div('flex flex-col flex-1 divide-y divide-light-35 dark:divide-dark-35', { list: character.variables.items, redraw: true, render: e => {
|
||||||
|
const item = config.items[e.id];
|
||||||
|
|
||||||
|
if(!item) return;
|
||||||
|
|
||||||
|
const price = div(() => ['flex flex-row min-w-16 gap-2 justify-between items-center px-2', { 'cursor-help': e.amount > 1 && !!item.price }], [ icon('ph:coin', { width: 16, height: 16, class: 'text-light-70 dark:text-dark-70' }), span(() => ({ 'underline decoration-1 decoration-dotted underline-offset-2': e.amount > 1 && !!item.price }), () => item.price ? `${item.price * e.amount}` : '-') ]);
|
||||||
|
const weight = div(() => ['flex flex-row min-w-16 gap-2 justify-between items-center px-2', { 'cursor-help': e.amount > 1 && !!item.weight }], [ icon('mdi:weight', { width: 16, height: 16, class: 'text-light-70 dark:text-dark-70' }), span(() => ({ 'underline decoration-1 decoration-dotted underline-offset-2': e.amount > 1 && !!item.weight }), () => item.weight ? `${item.weight * e.amount}` : '-') ]);
|
||||||
|
return foldable(() => [
|
||||||
|
markdown(getText(item.description)),
|
||||||
|
div('flex flex-row justify-center', [
|
||||||
|
this.character?.character.campaign ? button(text('Partager'), () => {
|
||||||
|
|
||||||
|
}, 'p-1') : undefined,
|
||||||
|
button(icon('radix-icons:trash'), () => {
|
||||||
|
const idx = items.findIndex(_e => _e === e);
|
||||||
|
if(idx === -1) return;
|
||||||
|
|
||||||
|
items[idx]!.amount--;
|
||||||
|
if(items[idx]!.amount >= 0) items.splice(idx, 1);
|
||||||
|
|
||||||
|
this.character!.variable('items', items);
|
||||||
|
}, 'p-1'),
|
||||||
|
]) ], [div('flex flex-row justify-between', [
|
||||||
|
div('flex flex-row items-center gap-4', [
|
||||||
|
item.equippable ? checkbox({ defaultValue: e.equipped, change: v => {
|
||||||
|
e.equipped = v;
|
||||||
|
|
||||||
|
this.character!.variable('items', items);
|
||||||
|
}, class: { container: '!w-5 !h-5' } }) : undefined,
|
||||||
|
div('flex flex-row items-center gap-4', [ span([colorByRarity[item.rarity], 'text-lg'], item.name), div('flex flex-row gap-2 text-light-60 dark:text-dark-60 text-sm italic', subnameFactory(item).map(e => span('', e))) ]),
|
||||||
|
]),
|
||||||
|
div('flex flex-row items-center divide-x divide-light-50 dark:divide-dark-50 divide-dashed px-2', [
|
||||||
|
e.amount > 1 && !!item.price ? tooltip(price, `Prix unitaire: ${item.price}`, 'bottom') : price,
|
||||||
|
div('flex flex-row min-w-16 gap-2 justify-between items-center px-2', [ icon('radix-icons:cross-2', { width: 16, height: 16, class: 'text-light-70 dark:text-dark-70' }), span('', e.amount?.toString() ?? '-') ]),
|
||||||
|
div('flex flex-row min-w-16 gap-2 justify-between items-center px-2', [ icon('game-icons:bolt-drop', { width: 16, height: 16, class: 'text-light-70 dark:text-dark-70' }), span('', item.powercost || item.capacity ? `${item.powercost ?? 0}/${item.capacity ?? 0}` : '-') ]),
|
||||||
|
e.amount > 1 && !!item.weight ? tooltip(weight, `Poids unitaire: ${item.weight}`, 'bottom') : weight,
|
||||||
|
div('flex flex-row min-w-16 gap-2 justify-between items-center px-2', [ icon('game-icons:battery-pack', { width: 16, height: 16, class: 'text-light-70 dark:text-dark-70' }), span('', item.charge ? `${item.charge}` : '-') ]),
|
||||||
|
]),
|
||||||
|
])], { open: false, class: { icon: 'px-2', container: 'p-1 gap-2', content: 'px-4 pb-1 flex flex-col' } })
|
||||||
|
}})
|
||||||
])
|
])
|
||||||
]
|
];
|
||||||
}
|
}
|
||||||
itemsPanel(character: CompiledCharacter)
|
itemsPanel(character: CompiledCharacter)
|
||||||
{
|
{
|
||||||
|
|
@ -1825,12 +1816,12 @@ export class CharacterSheet
|
||||||
div('flex flex-row w-16 gap-2 justify-between items-center px-2', [ icon('game-icons:battery-pack', { width: 16, height: 16, class: 'text-light-70 dark:text-dark-70' }), span('', item.charge ? `${item.charge}` : '-') ]),
|
div('flex flex-row w-16 gap-2 justify-between items-center px-2', [ icon('game-icons:battery-pack', { width: 16, height: 16, class: 'text-light-70 dark:text-dark-70' }), span('', item.charge ? `${item.charge}` : '-') ]),
|
||||||
div('flex flex-row w-16 gap-2 justify-between items-center px-2', [ icon('ph:coin', { width: 16, height: 16, class: 'text-light-70 dark:text-dark-70' }), span('', item.price ? `${item.price}` : '-') ]),
|
div('flex flex-row w-16 gap-2 justify-between items-center px-2', [ icon('ph:coin', { width: 16, height: 16, class: 'text-light-70 dark:text-dark-70' }), span('', item.price ? `${item.price}` : '-') ]),
|
||||||
button(icon('radix-icons:plus', { width: 16, height: 16 }), () => {
|
button(icon('radix-icons:plus', { width: 16, height: 16 }), () => {
|
||||||
const list = [...this.character!.character.variables.items];
|
const list = this.character!.character.variables.items;
|
||||||
if(item.equippable) list.push({ id: item.id, amount: 1, charges: item.charge, enchantments: [], equipped: false });
|
if(item.equippable) list.push({ id: item.id, amount: 1, charges: item.charge, enchantments: [], equipped: false });
|
||||||
else if(list.find(e => e.id === item.id)) this.character!.character.variables.items.find(e => e.id === item.id)!.amount++;
|
else if(list.find(e => e.id === item.id)) list.find(e => e.id === item.id)!.amount++;
|
||||||
else list.push({ id: item.id, amount: 1, charges: item.charge, enchantments: [] });
|
else list.push({ id: item.id, amount: 1, charges: item.charge, enchantments: [] });
|
||||||
this.character!.variable('items', list); //TO REWORK
|
(list as DOMList<ItemState>)?.render();
|
||||||
this.tabs?.refresh();
|
this.character!.variable('items', list);
|
||||||
}, 'p-1 !border-solid !border-r'),
|
}, 'p-1 !border-solid !border-r'),
|
||||||
]),
|
]),
|
||||||
])], { open: false, class: { icon: 'px-2', container: 'border border-light-35 dark:border-dark-35 p-1 gap-2', content: 'px-2 pb-1' } }) }));
|
])], { open: false, class: { icon: 'px-2', container: 'border border-light-35 dark:border-dark-35 p-1 gap-2', content: 'px-2 pb-1' } }) }));
|
||||||
|
|
|
||||||
|
|
@ -1,66 +1,148 @@
|
||||||
import { iconLoaded, loadIcon } from 'iconify-icon';
|
import { iconLoaded, loadIcon } from 'iconify-icon';
|
||||||
|
|
||||||
export type Node = HTMLElement | SVGElement | Text | undefined;
|
export type RedrawableHTML<T extends keyof HTMLElementTagNameMap> = HTMLElementTagNameMap[T] & { update: (recursive: boolean) => void }
|
||||||
|
export type Node = RedrawableHTML<any> & { update: (recursive: boolean) => void } | SVGElement | Text | undefined;
|
||||||
export type NodeChildren = Array<Node> | undefined;
|
export type NodeChildren = Array<Node> | undefined;
|
||||||
|
|
||||||
export type Class = string | Array<Class> | Record<string, boolean> | undefined;
|
export type Class = Reactive<string | Array<Class> | Record<string, boolean> | undefined>;
|
||||||
type Listener<K extends keyof HTMLElementEventMap> = | ((this: HTMLElement, ev: HTMLElementEventMap[K]) => any) | {
|
type Listener<K extends keyof HTMLElementEventMap> = | ((this: HTMLElement, ev: HTMLElementEventMap[K]) => any) | {
|
||||||
options?: boolean | AddEventListenerOptions;
|
options?: boolean | AddEventListenerOptions;
|
||||||
listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any;
|
listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any;
|
||||||
} | undefined;
|
} | undefined;
|
||||||
|
|
||||||
export type DOMList = Node[] & {
|
export type Reactive<T> = T | (() => T);
|
||||||
remove(predicate: (item: Node, index: number, array: Node[]) => boolean): Node[];
|
export interface DOMList<T> extends Array<T>{
|
||||||
|
render(redraw?: boolean): void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface NodeProperties
|
export interface NodeProperties
|
||||||
{
|
{
|
||||||
attributes?: Record<string, string | undefined | boolean | number>;
|
attributes?: Record<string, Reactive<string | undefined | boolean | number>>;
|
||||||
text?: string;
|
text?: Reactive<string | Text>;
|
||||||
class?: Class;
|
class?: Class;
|
||||||
style?: Record<string, string | undefined | boolean | number> | string;
|
style?: Reactive<Record<string, string | undefined | boolean | number> | string>;
|
||||||
listeners?: {
|
listeners?: {
|
||||||
[K in keyof HTMLElementEventMap]?: Listener<K>
|
[K in keyof HTMLElementEventMap]?: Listener<K>
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const cancelPropagation = (e: Event) => e.stopImmediatePropagation();
|
export const cancelPropagation = (e: Event) => e.stopImmediatePropagation();
|
||||||
export function dom<K extends keyof HTMLElementTagNameMap>(tag: K, properties?: NodeProperties, children?: NodeChildren | DOMList): HTMLElementTagNameMap[K]
|
export function dom<T extends keyof HTMLElementTagNameMap>(tag: T, properties?: NodeProperties, children?: NodeChildren): RedrawableHTML<T>;
|
||||||
|
export function dom<T extends keyof HTMLElementTagNameMap, U extends any>(tag: T, properties?: NodeProperties, children?: { render: (data: U) => Node, list?: Array<U>, redraw?: boolean }): RedrawableHTML<T> & { array?: DOMList<U> };
|
||||||
|
export function dom<T extends keyof HTMLElementTagNameMap, U extends any>(tag: T, properties?: NodeProperties, children?: NodeChildren | { render: (data: U) => Node, list?: Array<U>, redraw?: boolean }): RedrawableHTML<T> & { array?: DOMList<U> }
|
||||||
{
|
{
|
||||||
const element = document.createElement(tag);
|
const element = document.createElement(tag) as HTMLElementTagNameMap[T] & { array?: DOMList<U>, update: (recursive: boolean) => void };
|
||||||
|
let setup = true, updating = false;
|
||||||
|
|
||||||
if(children && children.length > 0)
|
const _cache = new Map<U, Node>();
|
||||||
for(const c of children) if(c !== undefined) element.appendChild(c);
|
|
||||||
|
|
||||||
if(properties?.attributes)
|
const update = (recursive: boolean) => {
|
||||||
for(const [k, v] of Object.entries(properties.attributes))
|
updating = true;
|
||||||
if(typeof v === 'string' || typeof v === 'number') element.setAttribute(k, v.toString(10));
|
|
||||||
else if(typeof v === 'boolean') element.toggleAttribute(k, v);
|
|
||||||
|
|
||||||
if(properties?.text)
|
if(children !== undefined && (setup || recursive))
|
||||||
element.textContent = properties.text;
|
|
||||||
|
|
||||||
if(properties?.listeners)
|
|
||||||
{
|
|
||||||
for(let [k, v] of Object.entries(properties.listeners))
|
|
||||||
{
|
{
|
||||||
const key = k as keyof HTMLElementEventMap, value = v as Listener<typeof key>;
|
element.replaceChildren();
|
||||||
if(typeof value === 'function')
|
if(Array.isArray(children))
|
||||||
element.addEventListener(key, value.bind(element));
|
{
|
||||||
else if(value)
|
for(const c of children)
|
||||||
element.addEventListener(key, value.listener.bind(element), value.options);
|
{
|
||||||
}
|
if(c !== undefined)
|
||||||
}
|
{
|
||||||
|
element.appendChild(c);
|
||||||
|
recursive && 'update' in c && c.update(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(children.list !== undefined)
|
||||||
|
{
|
||||||
|
if(setup || recursive)
|
||||||
|
{
|
||||||
|
_cache.clear();
|
||||||
|
children.list.forEach(e => _cache.set(e, children.render(e)));
|
||||||
|
}
|
||||||
|
|
||||||
styling(element, properties ?? {});
|
if(setup)
|
||||||
|
{
|
||||||
|
const _push = children.list.push;
|
||||||
|
children.list.push = (...items: U[]) => {
|
||||||
|
items.forEach(e => {
|
||||||
|
const dom = children.render(e);
|
||||||
|
_cache.set(e, dom);
|
||||||
|
dom && element.appendChild(dom);
|
||||||
|
});
|
||||||
|
if(children.redraw) update(false);
|
||||||
|
return _push.bind(children.list)(...items);
|
||||||
|
};
|
||||||
|
const _splice = children.list.splice;
|
||||||
|
children.list.splice = (start: number, deleteCount: number, ...items: U[]) => {
|
||||||
|
const list = _splice.bind(children.list)(start, deleteCount, ...items);
|
||||||
|
list.forEach(e => _cache.get(e)?.remove() || _cache.delete(e));
|
||||||
|
if(children.redraw) update(false);
|
||||||
|
return list;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
element.array = children.list as DOMList<U>;
|
||||||
|
element.array.render = (redraw?: boolean) => {
|
||||||
|
element.replaceChildren(...children.list?.map(e => _cache.get(e)).filter(e => !!e) ?? []);
|
||||||
|
if((redraw !== undefined || children.redraw !== undefined) && !updating) update(redraw ?? children.redraw!);
|
||||||
|
}
|
||||||
|
|
||||||
|
element.array.render();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(properties?.attributes)
|
||||||
|
{
|
||||||
|
for(const [k, v] of Object.entries(properties.attributes))
|
||||||
|
{
|
||||||
|
if(!setup && typeof v !== 'function') continue;
|
||||||
|
|
||||||
|
const value = typeof v === 'function' ? v() : v;
|
||||||
|
if(typeof value === 'string' || typeof value === 'number') element.setAttribute(k, value.toString(10));
|
||||||
|
else if(typeof value === 'boolean') element.toggleAttribute(k, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(properties?.text && (setup || typeof properties.text === 'function'))
|
||||||
|
{
|
||||||
|
const text = typeof properties.text === 'function' ? properties.text() : properties.text;
|
||||||
|
if(typeof text === 'string')
|
||||||
|
element.textContent = text;
|
||||||
|
else
|
||||||
|
element.appendChild(text as Text);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(properties?.listeners)
|
||||||
|
{
|
||||||
|
for(let [k, v] of Object.entries(properties.listeners))
|
||||||
|
{
|
||||||
|
const key = k as keyof HTMLElementEventMap, value = v as Listener<typeof key>;
|
||||||
|
if(typeof value === 'function')
|
||||||
|
element.addEventListener(key, value.bind(element));
|
||||||
|
else if(value)
|
||||||
|
element.addEventListener(key, value.listener.bind(element), value.options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
styling(element, properties ?? {});
|
||||||
|
updating = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
update(false);
|
||||||
|
setup = false;
|
||||||
|
element.update = update;
|
||||||
|
|
||||||
return element;
|
return element;
|
||||||
}
|
}
|
||||||
export function div(cls?: Class, children?: NodeChildren): HTMLDivElement
|
export function div<U extends any>(cls?: Class, children?: NodeChildren): RedrawableHTML<'div'>
|
||||||
|
export function div<U extends any>(cls?: Class, children?: { render: (data: U) => Node, list?: Array<U>, redraw?: boolean }): RedrawableHTML<'div'> & { array: DOMList<U> }
|
||||||
|
export function div<U extends any>(cls?: Class, children?: NodeChildren | { render: (data: U) => Node, list?: Array<U>, redraw?: boolean }): RedrawableHTML<'div'> & { array?: DOMList<U> }
|
||||||
{
|
{
|
||||||
|
//@ts-expect-error
|
||||||
return dom("div", { class: cls }, children);
|
return dom("div", { class: cls }, children);
|
||||||
}
|
}
|
||||||
export function span(cls?: Class, text?: string): HTMLSpanElement
|
export function span(cls?: Class, text?: Reactive<string | Text>): RedrawableHTML<'span'>
|
||||||
{
|
{
|
||||||
return dom("span", { class: cls, text: text });
|
return dom("span", { class: cls, text: text });
|
||||||
}
|
}
|
||||||
|
|
@ -76,20 +158,115 @@ export function svg<K extends keyof SVGElementTagNameMap>(tag: K, properties?: N
|
||||||
if(typeof v === 'string') element.setAttribute(k, v);
|
if(typeof v === 'string') element.setAttribute(k, v);
|
||||||
else if(typeof v === 'boolean') element.toggleAttribute(k, v);
|
else if(typeof v === 'boolean') element.toggleAttribute(k, v);
|
||||||
|
|
||||||
if(properties?.text)
|
if(properties?.text && typeof properties.text === 'string')
|
||||||
element.textContent = properties.text;
|
element.textContent = properties.text;
|
||||||
|
|
||||||
styling(element, properties ?? {});
|
styling(element, properties ?? {});
|
||||||
|
|
||||||
return element;
|
return element;
|
||||||
}
|
}
|
||||||
export function text(data: string): Text
|
export function text(data: string): Text;
|
||||||
|
export function text(data: {}, _txt: Reactive<string>): Text;
|
||||||
|
export function text(data: any, _txt?: Reactive<string>): Text
|
||||||
{
|
{
|
||||||
return document.createTextNode(data);
|
if(typeof data === 'string')
|
||||||
|
return document.createTextNode(data);
|
||||||
|
else if(_txt)
|
||||||
|
{
|
||||||
|
const cache = new Map<string, number>();
|
||||||
|
let txtCache = (typeof _txt === 'function' ? _txt() : _txt);
|
||||||
|
const setup = (property: string) => {
|
||||||
|
const prop = property.split('.');
|
||||||
|
let obj = data;
|
||||||
|
|
||||||
|
for(let i = 0; i < prop.length - 1; i++)
|
||||||
|
{
|
||||||
|
if(prop[i]! in obj) obj = obj[prop[i]!];
|
||||||
|
else return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const last = prop.slice(-1)[0]!;
|
||||||
|
if(last in obj)
|
||||||
|
{
|
||||||
|
const prototype = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(obj), last);
|
||||||
|
let clone = obj[last];
|
||||||
|
delete obj[last];
|
||||||
|
Object.defineProperty(obj, last, { ...prototype, get: () => prototype?.get ? prototype.get() : clone, set: (v) => { if(prototype?.set) { prototype.set(v); clone = obj[last]; } else if(!prototype?.get) { clone = v; } cache.set(property, v); replace(); }, enumerable: true, configurable: true, });
|
||||||
|
cache.set(property, clone);
|
||||||
|
|
||||||
|
return obj[last];
|
||||||
|
}
|
||||||
|
else return 0;
|
||||||
|
}
|
||||||
|
const apply = (_setup: boolean) => txtCache.replace(/\{\{(.+?)\}\}/g, (_, txt: string) => {
|
||||||
|
let i = 0, current = 0, property = '', nextOp = '';
|
||||||
|
const _compute = () => {
|
||||||
|
if(property.length > 0)
|
||||||
|
{
|
||||||
|
let value = 0;
|
||||||
|
if(_setup)
|
||||||
|
value = setup(property);
|
||||||
|
else
|
||||||
|
value = cache.get(property)!;
|
||||||
|
|
||||||
|
if(nextOp === '+')
|
||||||
|
current += value;
|
||||||
|
else if(nextOp === '-')
|
||||||
|
current -= value;
|
||||||
|
else if(nextOp === '*')
|
||||||
|
current *= value;
|
||||||
|
else if(nextOp === '/')
|
||||||
|
current /= value;
|
||||||
|
else if(nextOp === '%')
|
||||||
|
current /= value;
|
||||||
|
else if(nextOp === '')
|
||||||
|
current = value;
|
||||||
|
|
||||||
|
nextOp = '';
|
||||||
|
property = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while(i < txt.length)
|
||||||
|
{
|
||||||
|
switch(txt.charAt(i))
|
||||||
|
{
|
||||||
|
case '+':
|
||||||
|
case '-':
|
||||||
|
case '*':
|
||||||
|
case '/':
|
||||||
|
case '%':
|
||||||
|
_compute();
|
||||||
|
|
||||||
|
nextOp = txt.charAt(i).trim();
|
||||||
|
break;
|
||||||
|
case ' ':
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
property += txt.charAt(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
_compute();
|
||||||
|
return current.toString();
|
||||||
|
});
|
||||||
|
const replace = () => {
|
||||||
|
const txt = (typeof _txt === 'function' ? _txt() : _txt);
|
||||||
|
if(txt !== txtCache)
|
||||||
|
{
|
||||||
|
txtCache = txt;
|
||||||
|
node.textContent = apply(true);
|
||||||
|
}
|
||||||
|
else node.textContent = apply(false);
|
||||||
|
};
|
||||||
|
const node = document.createTextNode(apply(true));
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
else return document.createTextNode('');
|
||||||
}
|
}
|
||||||
export function styling(element: SVGElement | HTMLElement, properties: {
|
export function styling(element: SVGElement | RedrawableHTML<any>, properties: {
|
||||||
class?: Class;
|
class?: Class;
|
||||||
style?: Record<string, string | undefined | boolean | number> | string;
|
style?: Reactive<Record<string, string | undefined | boolean | number> | string>;
|
||||||
}): SVGElement | HTMLElement
|
}): SVGElement | HTMLElement
|
||||||
{
|
{
|
||||||
if(properties?.class)
|
if(properties?.class)
|
||||||
|
|
@ -114,8 +291,8 @@ export interface IconProperties
|
||||||
mode?: string;
|
mode?: string;
|
||||||
inline?: boolean;
|
inline?: boolean;
|
||||||
noobserver?: boolean;
|
noobserver?: boolean;
|
||||||
width?: string|number;
|
width?: string | number;
|
||||||
height?: string|number;
|
height?: string | number;
|
||||||
flip?: string;
|
flip?: string;
|
||||||
rotate?: number|string;
|
rotate?: number|string;
|
||||||
style?: Record<string, string | undefined> | string;
|
style?: Record<string, string | undefined> | string;
|
||||||
|
|
@ -165,8 +342,9 @@ export function icon(name: string, properties?: IconProperties): HTMLElement
|
||||||
return element;
|
return element;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function mergeClasses(classes: Class): string
|
export function mergeClasses(cls: Class): string
|
||||||
{
|
{
|
||||||
|
const classes = typeof cls === 'function' ? cls() : cls;
|
||||||
if(typeof classes === 'string')
|
if(typeof classes === 'string')
|
||||||
{
|
{
|
||||||
return classes.trim();
|
return classes.trim();
|
||||||
|
|
|
||||||
|
|
@ -94,7 +94,7 @@ export class HomebrewBuilder
|
||||||
const peopleRender = (people: RaceConfig) => {
|
const peopleRender = (people: RaceConfig) => {
|
||||||
return foldable(() => Object.entries(people.options).flatMap(level => [ div("w-full flex h-px", [div("border-t border-dashed border-light-50 dark:border-dark-50 w-full"), dom('span', { class: "relative" }, [ text(level[0]) ])]),
|
return foldable(() => Object.entries(people.options).flatMap(level => [ div("w-full flex h-px", [div("border-t border-dashed border-light-50 dark:border-dark-50 w-full"), dom('span', { class: "relative" }, [ text(level[0]) ])]),
|
||||||
div("flex flex-row gap-4 justify-center", level[1].map((option) => render(people.id, parseInt(level[0], 10) as Level, option))),
|
div("flex flex-row gap-4 justify-center", level[1].map((option) => render(people.id, parseInt(level[0], 10) as Level, option))),
|
||||||
]), [ input('text', { defaultValue: people.name, input: (value) => people.name = value, class: 'w-32' }), input('text', { defaultValue: people.description, input: (value) => people.description = value, class: 'w-full' }) ], { class: { container: 'gap-2 max-h-full', title: 'flex flex-row', content: 'flex flex-shrink-0 flex-col gap-4 relative w-full overflow-y-auto px-8' }, open: false })
|
]), [ input('text', { defaultValue: people.name, input: (value) => { people.name = value }, class: 'w-32' }), input('text', { defaultValue: people.description, input: (value) => { people.description = value }, class: 'w-full' }) ], { class: { container: 'gap-2 max-h-full', title: 'flex flex-row', content: 'flex flex-shrink-0 flex-col gap-4 relative w-full overflow-y-auto px-8' }, open: false })
|
||||||
}
|
}
|
||||||
const container = div('flex flex-col gap-2', Object.values(config.peoples).map(peopleRender));
|
const container = div('flex flex-col gap-2', Object.values(config.peoples).map(peopleRender));
|
||||||
const content = [ div('flex flex-col py-2 gap-2', [ div('w-full flex flex-row-reverse', [ button(icon('radix-icons:plus'), add, 'p-1') ]), container ]) ];
|
const content = [ div('flex flex-col py-2 gap-2', [ div('w-full flex flex-row-reverse', [ button(icon('radix-icons:plus'), add, 'p-1') ]), container ]) ];
|
||||||
|
|
@ -166,8 +166,8 @@ export class HomebrewBuilder
|
||||||
{
|
{
|
||||||
const render = (aspect: AspectConfig) => {
|
const render = (aspect: AspectConfig) => {
|
||||||
return {
|
return {
|
||||||
name: input('text', { input: (value) => aspect.name = value, defaultValue: aspect.name, class: '!m-0 w-full' }),
|
name: input('text', { input: (value) => { aspect.name = value }, defaultValue: aspect.name, class: '!m-0 w-full' }),
|
||||||
description: input('text', { input: (value) => aspect.description = value, defaultValue: aspect.description, class: '!m-0 w-full' }),
|
description: input('text', { input: (value) => { aspect.description = value }, defaultValue: aspect.description, class: '!m-0 w-full' }),
|
||||||
stat: select(MAIN_STATS.map(f => ({ text: mainStatTexts[f], value: f })), { change: (value) => aspect.stat = value, defaultValue: aspect.stat, class: { container: '!m-0 w-full' } }),
|
stat: select(MAIN_STATS.map(f => ({ text: mainStatTexts[f], value: f })), { change: (value) => aspect.stat = value, defaultValue: aspect.stat, class: { container: '!m-0 w-full' } }),
|
||||||
alignment: select(ALIGNMENTS.map(f => ({ text: alignmentTexts[f], value: f })), { change: (value) => aspect.alignment = value, defaultValue: aspect.alignment, class: { container: '!m-0 w-full' } }),
|
alignment: select(ALIGNMENTS.map(f => ({ text: alignmentTexts[f], value: f })), { change: (value) => aspect.alignment = value, defaultValue: aspect.alignment, class: { container: '!m-0 w-full' } }),
|
||||||
magic: toggle({ defaultValue: aspect.magic, change: (value) => aspect.magic = value, class: { container: '' } }),
|
magic: toggle({ defaultValue: aspect.magic, change: (value) => aspect.magic = value, class: { container: '' } }),
|
||||||
|
|
@ -179,7 +179,9 @@ export class HomebrewBuilder
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const add = () => {
|
const add = () => {
|
||||||
this._config.aspects.push({
|
const id = getID();
|
||||||
|
this._config.aspects[id] = {
|
||||||
|
id,
|
||||||
name: '',
|
name: '',
|
||||||
description: '',
|
description: '',
|
||||||
stat: 'strength',
|
stat: 'strength',
|
||||||
|
|
@ -190,7 +192,7 @@ export class HomebrewBuilder
|
||||||
mental: { min: 0, max: 20 },
|
mental: { min: 0, max: 20 },
|
||||||
personality: { min: 0, max: 20 },
|
personality: { min: 0, max: 20 },
|
||||||
options: []
|
options: []
|
||||||
});
|
};
|
||||||
|
|
||||||
const element = redraw();
|
const element = redraw();
|
||||||
content.parentElement?.replaceChild(element, content);
|
content.parentElement?.replaceChild(element, content);
|
||||||
|
|
@ -200,7 +202,7 @@ export class HomebrewBuilder
|
||||||
confirm('Voulez vous vraiment supprimer cet aspect ?').then(e => {
|
confirm('Voulez vous vraiment supprimer cet aspect ?').then(e => {
|
||||||
if(e)
|
if(e)
|
||||||
{
|
{
|
||||||
config.aspects = config.aspects.filter(e => e !== aspect);
|
delete config.aspects[aspect.id];
|
||||||
|
|
||||||
const element = redraw();
|
const element = redraw();
|
||||||
content.parentElement?.replaceChild(element, content);
|
content.parentElement?.replaceChild(element, content);
|
||||||
|
|
@ -208,7 +210,7 @@ export class HomebrewBuilder
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
const redraw = () => table(this._config.aspects.map(render), { name: 'Nom', description: 'Description', stat: 'Buff de stat', alignment: 'Alignement', magic: 'Magie', difficulty: 'Difficulté', physic: 'Physique', mental: 'Mental', personality: 'Caractère', action: 'Actions' }, { class: { table: 'flex-1' } });
|
const redraw = () => table(Object.values(this._config.aspects).map(render), { name: 'Nom', description: 'Description', stat: 'Buff de stat', alignment: 'Alignement', magic: 'Magie', difficulty: 'Difficulté', physic: 'Physique', mental: 'Mental', personality: 'Caractère', action: 'Actions' }, { class: { table: 'flex-1' } });
|
||||||
let content = redraw();
|
let content = redraw();
|
||||||
return [ div('flex px-8 py-4 flex-col gap-4', [ div('flex flex-row-reverse', [ button(icon('radix-icons:plus'), add, 'p-1') ]), content ] ) ];
|
return [ div('flex px-8 py-4 flex-col gap-4', [ div('flex flex-row-reverse', [ button(icon('radix-icons:plus'), add, 'p-1') ]), content ] ) ];
|
||||||
}
|
}
|
||||||
|
|
@ -224,11 +226,12 @@ export class HomebrewBuilder
|
||||||
dom('label', { class: 'flex flex-col items-center justify-start gap-2 flex-1 *:text-center' }, [ text('Portée'), select<'personnal' | number>([{ text: 'Toucher', value: 0 }, { text: 'Personnel', value: 'personnal' }, { text: '3 cases', value: 3 }, { text: '6 cases', value: 6 }, { text: '9 cases', value: 9 }, { text: '12 cases', value: 12 }, { text: '18 cases', value: 18 }], { change: (value) => spell.range = value, defaultValue: spell.range, class: { container: '!m-0 !h-9 w-full' } }), ]),
|
dom('label', { class: 'flex flex-col items-center justify-start gap-2 flex-1 *:text-center' }, [ text('Portée'), select<'personnal' | number>([{ text: 'Toucher', value: 0 }, { text: 'Personnel', value: 'personnal' }, { text: '3 cases', value: 3 }, { text: '6 cases', value: 6 }, { text: '9 cases', value: 9 }, { text: '12 cases', value: 12 }, { text: '18 cases', value: 18 }], { change: (value) => spell.range = value, defaultValue: spell.range, class: { container: '!m-0 !h-9 w-full' } }), ]),
|
||||||
dom('label', { class: 'flex flex-col items-center justify-start gap-2 flex-1 *:text-center' }, [ text('Tags'), multiselect([{ text: 'Dégâts', value: 'damage' }, { text: 'Buff', value: 'buff' }, { text: 'Debuff', value: 'debuff' }, { text: 'Support', value: 'support' }, { text: 'Tank', value: 'tank' }, { text: 'Mouvement', value: 'movement' }, { text: 'Utilitaire', value: 'utilitary' }], { change: (value) => spell.tags = value, defaultValue: spell.tags, class: { container: '!m-0 !h-9 w-full' } }), ]),
|
dom('label', { class: 'flex flex-col items-center justify-start gap-2 flex-1 *:text-center' }, [ text('Tags'), multiselect([{ text: 'Dégâts', value: 'damage' }, { text: 'Buff', value: 'buff' }, { text: 'Debuff', value: 'debuff' }, { text: 'Support', value: 'support' }, { text: 'Tank', value: 'tank' }, { text: 'Mouvement', value: 'movement' }, { text: 'Utilitaire', value: 'utilitary' }], { change: (value) => spell.tags = value, defaultValue: spell.tags, class: { container: '!m-0 !h-9 w-full' } }), ]),
|
||||||
dom('label', { class: 'flex flex-col items-center justify-start gap-2 flex-1 *:text-center' }, [ text('Concentration'), toggle({ change: (value) => spell.concentration = value, defaultValue: spell.concentration, class: { container: '!m-0 !flex-none' } }), ]),
|
dom('label', { class: 'flex flex-col items-center justify-start gap-2 flex-1 *:text-center' }, [ text('Concentration'), toggle({ change: (value) => spell.concentration = value, defaultValue: spell.concentration, class: { container: '!m-0 !flex-none' } }), ]),
|
||||||
], [ div('gap-4 px-4 flex', [ input('text', { input: (value) => spell.name = value, defaultValue: spell.name, class: '!m-0 w-64' }), input('text', { input: (value) => spell.description = value, defaultValue: spell.description, class: '!m-0 w-full' }),div('flex flex-row justify-center gap-2', [ button(icon('radix-icons:trash', { noobserver: true }), () => remove(spell), 'p-1') ]) ]) ], { class: { container: 'border-light-35 dark:border-dark-35 py-1', content: 'gap-2 px-4 py-1 flex items-center *:flex-1' }, open: false });
|
], [ div('gap-4 px-4 flex', [ input('text', { input: (value) => { spell.name = value }, defaultValue: spell.name, class: '!m-0 w-64' }), input('text', { input: (value) => { spell.description = value }, defaultValue: spell.description, class: '!m-0 w-full' }),div('flex flex-row justify-center gap-2', [ button(icon('radix-icons:trash', { noobserver: true }), () => remove(spell), 'p-1') ]) ]) ], { class: { container: 'border-light-35 dark:border-dark-35 py-1', content: 'gap-2 px-4 py-1 flex items-center *:flex-1' }, open: false });
|
||||||
}
|
}
|
||||||
const add = () => {
|
const add = () => {
|
||||||
this._config.spells.push({
|
const id = getID();
|
||||||
id: getID(),
|
this._config.spells[id] = {
|
||||||
|
id,
|
||||||
name: '',
|
name: '',
|
||||||
rank: 1,
|
rank: 1,
|
||||||
type: 'precision',
|
type: 'precision',
|
||||||
|
|
@ -239,7 +242,7 @@ export class HomebrewBuilder
|
||||||
concentration: false,
|
concentration: false,
|
||||||
range: 0,
|
range: 0,
|
||||||
tags: [],
|
tags: [],
|
||||||
});
|
};
|
||||||
|
|
||||||
const element = redraw();
|
const element = redraw();
|
||||||
content.parentElement?.replaceChild(element, content);
|
content.parentElement?.replaceChild(element, content);
|
||||||
|
|
@ -249,7 +252,7 @@ export class HomebrewBuilder
|
||||||
confirm('Voulez vous vraiment supprimer ce sort ?').then(e => {
|
confirm('Voulez vous vraiment supprimer ce sort ?').then(e => {
|
||||||
if(e)
|
if(e)
|
||||||
{
|
{
|
||||||
this._config.spells = this._config.spells.filter(e => e !== spell);
|
delete this._config.spells[spell.id];
|
||||||
|
|
||||||
const element = redraw();
|
const element = redraw();
|
||||||
content.parentElement?.replaceChild(element, content);
|
content.parentElement?.replaceChild(element, content);
|
||||||
|
|
@ -257,7 +260,7 @@ export class HomebrewBuilder
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const redraw = () => div('flex flex-col divide-y', this._config.spells.map(render));
|
const redraw = () => div('flex flex-col divide-y', Object.values(this._config.spells).map(render));
|
||||||
let content = redraw();
|
let content = redraw();
|
||||||
return [ div('flex px-8 py-4 flex-col gap-4', [ div('flex flex-row-reverse', [ button(icon('radix-icons:plus'), add, 'p-1') ]), content ] ) ];
|
return [ div('flex px-8 py-4 flex-col gap-4', [ div('flex flex-row-reverse', [ button(icon('radix-icons:plus'), add, 'p-1') ]), content ] ) ];
|
||||||
}
|
}
|
||||||
|
|
@ -269,7 +272,7 @@ export class HomebrewBuilder
|
||||||
const buttons = div('flex flex-row items-center gap-2', [ span('text-sm text-light-70 dark:text-dark-70', type), tooltip(button(icon('radix-icons:pencil-1'), () => edit(type, feature.id), 'p-1'), 'Modifier', 'left'), tooltip(button(icon('radix-icons:trash'), () => remove(type, feature.id), 'p-1'), 'Supprimer', 'right') ]);
|
const buttons = div('flex flex-row items-center gap-2', [ span('text-sm text-light-70 dark:text-dark-70', type), tooltip(button(icon('radix-icons:pencil-1'), () => edit(type, feature.id), 'p-1'), 'Modifier', 'left'), tooltip(button(icon('radix-icons:trash'), () => remove(type, feature.id), 'p-1'), 'Supprimer', 'right') ]);
|
||||||
return {
|
return {
|
||||||
dom: div('flex flex-col gap-2', [
|
dom: div('flex flex-col gap-2', [
|
||||||
div('flex flex-row justify-between', [ input('text', { defaultValue: feature.name, input: value => feature.name = value, placeholder: 'Nom', class: '!mx-0 w-80' }), div('flex flex-row gap-2 items-center', [ type === 'action' || type === 'reaction' ? div('flex flex-row items-center', [ numberpicker({ defaultValue: feature?.cost ?? 0, input: value => feature.cost = value, class: '!mx-1', max: type === 'action' ? 3 : 2, min: 0 }), text(`point${(feature?.cost ?? 0) > 1 ? 's' : ''}`)]) : undefined, buttons ])]),
|
div('flex flex-row justify-between', [ input('text', { defaultValue: feature.name, input: value => { feature.name = value }, placeholder: 'Nom', class: '!mx-0 w-80' }), div('flex flex-row gap-2 items-center', [ type === 'action' || type === 'reaction' ? div('flex flex-row items-center', [ numberpicker({ defaultValue: feature?.cost ?? 0, input: value => feature.cost = value, class: '!mx-1', max: type === 'action' ? 3 : 2, min: 0 }), text(`point${(feature?.cost ?? 0) > 1 ? 's' : ''}`)]) : undefined, buttons ])]),
|
||||||
md.current,
|
md.current,
|
||||||
]),
|
]),
|
||||||
buttons,
|
buttons,
|
||||||
|
|
@ -564,7 +567,7 @@ class FeatureEditor
|
||||||
{
|
{
|
||||||
if(buffer.list === 'spells')
|
if(buffer.list === 'spells')
|
||||||
{
|
{
|
||||||
list = config.spells.map(e => ({ text: e.name, render: () => div('flex flex-col', [ div('flex flex-row justify-between', [ dom('span', { text: e.name, class: 'font-bold' }), div('flex flex-row gap-8', [ dom('span', { class: 'italic', text: `Rang ${e.rank === 4 ? 'spécial' : e.rank}` }), dom('span', { text: spellTypeTexts[e.type] }) ]) ]), div('text-sm text-light-70 dark:text-dark-70', [ text(renderMDAsText(e.description)) ]) ]), value: e.id }));
|
list = Object.values(config.spells).map(e => ({ text: e.name, render: () => div('flex flex-col', [ div('flex flex-row justify-between', [ dom('span', { text: e.name, class: 'font-bold' }), div('flex flex-row gap-8', [ dom('span', { class: 'italic', text: `Rang ${e.rank === 4 ? 'spécial' : e.rank}` }), dom('span', { text: spellTypeTexts[e.type] }) ]) ]), div('text-sm text-light-70 dark:text-dark-70', [ text(renderMDAsText(e.description)) ]) ]), value: e.id }));
|
||||||
}
|
}
|
||||||
else if(buffer.list)
|
else if(buffer.list)
|
||||||
{
|
{
|
||||||
|
|
@ -573,7 +576,7 @@ class FeatureEditor
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
list = (Object.values(config.features).flatMap(e => e.effect).filter(e => e.category === 'list' && e.list === buffer.list && e.action === 'add') as FeatureList[]).map((e) => e.list === 'spells' ? config.spells.find(f => f.id === e.item)! : config[e.list][e.item]!).map((e) => ({ text: e.name, render: () => div('flex flex-col', [ div('flex flex-row justify-between', [ dom('span', { text: e.name, class: 'font-bold' }) ]), div('text-sm text-light-70 dark:text-dark-70', [ text(renderMDAsText(getText(e.description))) ]) ]), value: e.id }));
|
list = (Object.values(config.features).flatMap(e => e.effect).filter(e => e.category === 'list' && e.list === buffer.list && e.action === 'add') as FeatureList[]).map((e) => config[e.list][e.item]!).map((e) => ({ text: e.name, render: () => div('flex flex-col', [ div('flex flex-row justify-between', [ dom('span', { text: e.name, class: 'font-bold' }) ]), div('text-sm text-light-70 dark:text-dark-70', [ text(renderMDAsText(getText(e.description))) ]) ]), value: e.id }));
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
@ -619,7 +622,7 @@ class FeatureEditor
|
||||||
}
|
}
|
||||||
const renderOption = (option: { text: string; effects: (Partial<FeatureValue | FeatureList>)[] }, state: boolean) => {
|
const renderOption = (option: { text: string; effects: (Partial<FeatureValue | FeatureList>)[] }, state: boolean) => {
|
||||||
const effects = div('flex flex-col -m-px flex flex-col ms-px ps-8 w-full', option.effects.map(e => renderEffect(option, e)));
|
const effects = div('flex flex-col -m-px flex flex-col ms-px ps-8 w-full', option.effects.map(e => renderEffect(option, e)));
|
||||||
let _content = foldable([ effects ], [ div('flex flex-row flex-1 justify-between', [ input('text', { defaultValue: option.text, input: (value) => option.text = value, placeholder: 'Nom de l\'option', class: 'bg-light-25 dark:bg-dark-25 !-m-px hover:z-10 h-[36px] flex-shrink-1' }), div('flex flex-row flex-shrink-1', [ tooltip(button(icon('radix-icons:plus'), () => effects.appendChild(renderEffect(option, addEffect(option))), 'px-2 -m-px hover:z-10 border border-light-35 dark:border-dark-35 hover:border-light-50 dark:hover:border-dark-50'), 'Nouvel effet', 'bottom'), , tooltip(button(icon('radix-icons:trash'), () => {
|
let _content = foldable([ effects ], [ div('flex flex-row flex-1 justify-between', [ input('text', { defaultValue: option.text, input: (value) => { option.text = value }, placeholder: 'Nom de l\'option', class: 'bg-light-25 dark:bg-dark-25 !-m-px hover:z-10 h-[36px] flex-shrink-1' }), div('flex flex-row flex-shrink-1', [ tooltip(button(icon('radix-icons:plus'), () => effects.appendChild(renderEffect(option, addEffect(option))), 'px-2 -m-px hover:z-10 border border-light-35 dark:border-dark-35 hover:border-light-50 dark:hover:border-dark-50'), 'Nouvel effet', 'bottom'), , tooltip(button(icon('radix-icons:trash'), () => {
|
||||||
_content.remove();
|
_content.remove();
|
||||||
buffer.options?.splice(buffer.options.findIndex(e => e !== option), 1);
|
buffer.options?.splice(buffer.options.findIndex(e => e !== option), 1);
|
||||||
}, 'px-2 -m-px hover:z-10 border border-light-35 dark:border-dark-35 hover:border-light-50 dark:hover:border-dark-50'), 'Supprimer', 'bottom') ]) ]) ], { class: { title: 'border-b border-light-35 dark:border-dark-35', icon: 'w-[34px] h-[34px]', content: 'border-b border-light-35 dark:border-dark-35' }, open: state });
|
}, 'px-2 -m-px hover:z-10 border border-light-35 dark:border-dark-35 hover:border-light-50 dark:hover:border-dark-50'), 'Supprimer', 'bottom') ]) ]) ], { class: { title: 'border-b border-light-35 dark:border-dark-35', icon: 'w-[34px] h-[34px]', content: 'border-b border-light-35 dark:border-dark-35' }, open: state });
|
||||||
|
|
@ -628,7 +631,7 @@ class FeatureEditor
|
||||||
const list = div('flex flex-col flex-1 divide-y divide-light-35 dark:divide-dark-35 gap-2', buffer.options?.map(e => renderOption(e, false)) ?? []);
|
const list = div('flex flex-col flex-1 divide-y divide-light-35 dark:divide-dark-35 gap-2', buffer.options?.map(e => renderOption(e, false)) ?? []);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
top: [ input('text', { defaultValue: buffer.text, input: (value) => (buffer as FeatureChoice).text = value, class: 'bg-light-25 dark:bg-dark-25 !-m-px hover:z-10 h-[36px] w-full', placeholder: 'Description' }), tooltip(button(icon('radix-icons:plus'), () => list.appendChild(renderOption(addChoice(), true)), 'px-2 -m-px hover:z-10 border border-light-35 dark:border-dark-35 hover:border-light-50 dark:hover:border-dark-50'), 'Nouvelle option', 'bottom') ],
|
top: [ input('text', { defaultValue: buffer.text, input: (value) => { (buffer as FeatureChoice).text = value }, class: 'bg-light-25 dark:bg-dark-25 !-m-px hover:z-10 h-[36px] w-full', placeholder: 'Description' }), tooltip(button(icon('radix-icons:plus'), () => list.appendChild(renderOption(addChoice(), true)), 'px-2 -m-px hover:z-10 border border-light-35 dark:border-dark-35 hover:border-light-50 dark:hover:border-dark-50'), 'Nouvelle option', 'bottom') ],
|
||||||
bottom: [ list ],
|
bottom: [ list ],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -735,7 +738,7 @@ export class ItemPanel
|
||||||
}, 'p-1'), 'Valider', 'left'),
|
}, 'p-1'), 'Valider', 'left'),
|
||||||
dom('label', { class: 'flex justify-center items-center my-2' }, [
|
dom('label', { class: 'flex justify-center items-center my-2' }, [
|
||||||
dom('span', { class: 'pb-1 md:p-0', text: "Nom" }),
|
dom('span', { class: 'pb-1 md:p-0', text: "Nom" }),
|
||||||
input('text', { defaultValue: _item.name, input: (v) => _item.name = v })
|
input('text', { defaultValue: _item.name, input: (v) => { _item.name = v }})
|
||||||
]),
|
]),
|
||||||
tooltip(button(icon('radix-icons:cross-1', { width: 20, height: 20 }), () => {
|
tooltip(button(icon('radix-icons:cross-1', { width: 20, height: 20 }), () => {
|
||||||
failure!(item);
|
failure!(item);
|
||||||
|
|
@ -758,7 +761,7 @@ export class ItemPanel
|
||||||
], [ span('text-lg font-bold', "Armure") ], { class: { content: 'group-data-[active]:grid grid-cols-2 my-2 gap-4', title: 'grid grid-cols-2 gap-4 mx-2', container: 'pb-2 border-b border-light-35 dark:border-dark-35' }, open: true } ) : undefined,
|
], [ span('text-lg font-bold', "Armure") ], { class: { content: 'group-data-[active]:grid grid-cols-2 my-2 gap-4', title: 'grid grid-cols-2 gap-4 mx-2', container: 'pb-2 border-b border-light-35 dark:border-dark-35' }, open: true } ) : undefined,
|
||||||
_item.category === 'weapon' ? foldable([
|
_item.category === 'weapon' ? foldable([
|
||||||
div('flex flex-row gap-2 items-center justify-between', [ div('flex flex-row gap-2 items-center', [ span('', 'Type de dégâts'), ]), select(Object.keys(damageTypeTexts).map(e => ({ text: damageTypeTexts[e as DamageType], value: e as DamageType })), { defaultValue: _item.damage.type, change: (v) => _item.damage.type = v, class: { container: '!w-1/3' } }), ]),
|
div('flex flex-row gap-2 items-center justify-between', [ div('flex flex-row gap-2 items-center', [ span('', 'Type de dégâts'), ]), select(Object.keys(damageTypeTexts).map(e => ({ text: damageTypeTexts[e as DamageType], value: e as DamageType })), { defaultValue: _item.damage.type, change: (v) => _item.damage.type = v, class: { container: '!w-1/3' } }), ]),
|
||||||
div('flex flex-row gap-2 items-center justify-between', [ div('flex flex-row gap-2 items-center', [ span('', 'Dégats'), ]), input('text', { defaultValue: _item.damage.value, input: (v) => _item.damage.value = v, class: '!w-1/3' }), ]),
|
div('flex flex-row gap-2 items-center justify-between', [ div('flex flex-row gap-2 items-center', [ span('', 'Dégats'), ]), input('text', { defaultValue: _item.damage.value, input: (v) => { _item.damage.value = v }, class: '!w-1/3' }), ]),
|
||||||
], [ span('text-lg font-bold', "Propriétés"), div('flex flex-row gap-2 items-center justify-between', [ div('flex flex-row gap-2 items-center', [ span('', 'Rareté'), ]), multiselect(Object.keys(weaponTypeTexts).map(e => ({ text: weaponTypeTexts[e as WeaponType], value: e as WeaponType })), { defaultValue: _item.type, change: (v) => _item.type = v, class: { container: '!w-1/2' } }), ]) ], { class: { content: 'group-data-[active]:grid grid-cols-2 my-2 gap-4', title: 'grid grid-cols-2 gap-4 mx-2', container: 'pb-2 border-b border-light-35 dark:border-dark-35' }, open: true } ) : undefined,
|
], [ span('text-lg font-bold', "Propriétés"), div('flex flex-row gap-2 items-center justify-between', [ div('flex flex-row gap-2 items-center', [ span('', 'Rareté'), ]), multiselect(Object.keys(weaponTypeTexts).map(e => ({ text: weaponTypeTexts[e as WeaponType], value: e as WeaponType })), { defaultValue: _item.type, change: (v) => _item.type = v, class: { container: '!w-1/2' } }), ]) ], { class: { content: 'group-data-[active]:grid grid-cols-2 my-2 gap-4', title: 'grid grid-cols-2 gap-4 mx-2', container: 'pb-2 border-b border-light-35 dark:border-dark-35' }, open: true } ) : undefined,
|
||||||
foldable([div('p-1 border border-light-40 dark:border-dark-40 w-full bg-light-25 dark:bg-dark-25 min-h-48 max-h-[32rem]', [ MarkdownEditor.singleton.dom ])], [ span('text-lg font-bold px-2', "Description") ], { class: { container: 'gap-4 pb-2 border-b border-light-35 dark:border-dark-35' }, open: true, }),
|
foldable([div('p-1 border border-light-40 dark:border-dark-40 w-full bg-light-25 dark:bg-dark-25 min-h-48 max-h-[32rem]', [ MarkdownEditor.singleton.dom ])], [ span('text-lg font-bold px-2', "Description") ], { class: { container: 'gap-4 pb-2 border-b border-light-35 dark:border-dark-35' }, open: true, }),
|
||||||
foldable([ effectContainer ], [ dom('h3', { class: 'text-lg font-bold', text: 'Effets' }),
|
foldable([ effectContainer ], [ dom('h3', { class: 'text-lg font-bold', text: 'Effets' }),
|
||||||
|
|
@ -1006,7 +1009,7 @@ function textFromEffect(effect: Partial<FeatureOption>): string
|
||||||
case 'passive':
|
case 'passive':
|
||||||
return effect.action === 'add' ? `Gain du passif "${effect.item ? (config.passive[effect.item]?.name ?? 'Inconnu') : 'Inconnu'}"` : `Suppression du passif "${effect.item ? (config.passive[effect.item]?.name ?? 'Inconnu') : 'Inconnu'}"`;
|
return effect.action === 'add' ? `Gain du passif "${effect.item ? (config.passive[effect.item]?.name ?? 'Inconnu') : 'Inconnu'}"` : `Suppression du passif "${effect.item ? (config.passive[effect.item]?.name ?? 'Inconnu') : 'Inconnu'}"`;
|
||||||
case 'spells':
|
case 'spells':
|
||||||
return effect.action === 'add' ? `Maitrise du sort "${config.spells.find(e => e.id === effect.item)?.name ?? 'Sort inconnu'}".` : `Perte de maitrise du sort "${config.spells.find(e => e.id === effect.item)?.name ?? 'Sort inconnu'}".`;
|
return effect.action === 'add' ? `Maitrise du sort "${effect.item ? (config.spells[effect.item]?.name ?? 'Sort inconnu') : 'Sort inconnu'}".` : `Perte de maitrise du sort "${effect.item ? (config.passive[effect.item]?.name ?? 'Sort inconnu') : 'Sort inconnu'}".`;
|
||||||
case 'sickness':
|
case 'sickness':
|
||||||
return effect.action === 'add' ? `Maladie "${effect.item ? (config.sickness[effect.item]?.name ?? 'inconnue') : 'inconnue'}" permanente.` : `Maladie "${effect.item ? (config.sickness[effect.item]?.name ?? 'inconnue') : 'inconnue'}" supprimée.`;
|
return effect.action === 'add' ? `Maladie "${effect.item ? (config.sickness[effect.item]?.name ?? 'inconnue') : 'inconnue'}" permanente.` : `Maladie "${effect.item ? (config.sickness[effect.item]?.name ?? 'inconnue') : 'inconnue'}" supprimée.`;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue