Floater pinned true handler, SQL schema update to handle private/public notes on character, fix Canvas zoom debounce on move.
This commit is contained in:
parent
a577e3ccfc
commit
443612cc58
BIN
db.sqlite-shm
BIN
db.sqlite-shm
Binary file not shown.
BIN
db.sqlite-wal
BIN
db.sqlite-wal
Binary file not shown.
|
|
@ -1,6 +1,5 @@
|
|||
import { relations } from 'drizzle-orm';
|
||||
import { int, text, sqliteTable as table, primaryKey, blob } from 'drizzle-orm/sqlite-core';
|
||||
import { ABILITIES, MAIN_STATS } from '~/shared/character.util';
|
||||
|
||||
export const usersTable = table("users", {
|
||||
id: int().primaryKey({ autoIncrement: true }),
|
||||
|
|
@ -55,9 +54,10 @@ export const characterTable = table("character", {
|
|||
owner: int().notNull().references(() => usersTable.id, { onDelete: 'cascade', onUpdate: 'cascade' }),
|
||||
people: text().notNull(),
|
||||
level: int().notNull().default(1),
|
||||
variables: text({ mode: 'json' }).notNull().default('{"health": 0,"mana": 0,"spells": [],"equipment": [],"exhaustion": 0,"sickness": [],"poisons": []}'),
|
||||
variables: text({ mode: 'json' }).notNull().default('{"health": 0,"mana": 0,"spells": [],"items": [],"exhaustion": 0,"sickness": [],"poisons": []}'),
|
||||
aspect: int(),
|
||||
notes: text(),
|
||||
public_notes: text(),
|
||||
private_notes: text(),
|
||||
|
||||
visibility: text({ enum: ['private', 'public'] }).notNull().default('private'),
|
||||
thumbnail: blob(),
|
||||
|
|
|
|||
|
|
@ -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` integer,
|
||||
`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,711 @@
|
|||
{
|
||||
"version": "6",
|
||||
"dialect": "sqlite",
|
||||
"id": "153969ef-bcdb-4bbd-bd57-01fbd8004fc6",
|
||||
"prevId": "05b549e7-5b3f-40f4-9461-05db59391e20",
|
||||
"tables": {
|
||||
"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": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"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": {
|
||||
"\"character\".\"notes\"": "\"character\".\"public_notes\""
|
||||
}
|
||||
},
|
||||
"internal": {
|
||||
"indexes": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -120,6 +120,13 @@
|
|||
"when": 1756221197092,
|
||||
"tag": "0016_wild_the_anarchist",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 17,
|
||||
"version": "6",
|
||||
"when": 1760531331328,
|
||||
"tag": "0017_workable_scrambler",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -31,7 +31,7 @@
|
|||
import { ZodError } from 'zod/v4';
|
||||
import { schema, type Registration } from '~/schemas/registration';
|
||||
import { Icon } from '@iconify/vue/dist/iconify.js';
|
||||
import { floater, Toaster } from '#shared/components.util';
|
||||
import { Toaster } from '#shared/components.util';
|
||||
|
||||
definePageMeta({
|
||||
layout: 'login',
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ export default defineEventHandler(async (e) => {
|
|||
people: character.people,
|
||||
level: character.level,
|
||||
aspect: character.aspect,
|
||||
notes: character.notes,
|
||||
notes: { public: character.public_notes, private: character.private_notes },
|
||||
variables: character.variables,
|
||||
|
||||
training: character.training.reduce((p, v) => { p[v.stat] ??= {}; p[v.stat][v.level as TrainingLevel] = v.choice; return p; }, {} as Record<MainStat, Partial<Record<TrainingLevel, number>>>),
|
||||
|
|
|
|||
|
|
@ -42,7 +42,8 @@ export default defineEventHandler(async (e) => {
|
|||
people: body.data.people!,
|
||||
level: body.data.level,
|
||||
aspect: body.data.aspect,
|
||||
notes: body.data.notes,
|
||||
public_notes: body.data.notes.public,
|
||||
private_notes: body.data.notes.private,
|
||||
variables: body.data.variables,
|
||||
visibility: body.data.visibility,
|
||||
thumbnail: body.data.thumbnail,
|
||||
|
|
|
|||
|
|
@ -530,19 +530,22 @@ export class Canvas
|
|||
this._zoom = clamp(this._zoom * diff, this.containZoom, Canvas.maxZoom);
|
||||
}
|
||||
|
||||
this.updateTransform();
|
||||
this.updateTransform(true);
|
||||
};
|
||||
|
||||
this.computeLimits();
|
||||
this.reset();
|
||||
}
|
||||
|
||||
protected updateTransform()
|
||||
protected updateTransform(debounce: boolean)
|
||||
{
|
||||
this.transform.style.transform = `scale3d(${this.visualZoom}, ${this.visualZoom}, 1) translate3d(${this.visualX}px, ${this.visualY}px, 0)`;
|
||||
|
||||
if(debounce)
|
||||
{
|
||||
clearTimeout(this.debouncedTimeout);
|
||||
this.debouncedTimeout = setTimeout(this.updateScale.bind(this), 150);
|
||||
this.debouncedTimeout = setTimeout(this.updateScale.bind(this), 50);
|
||||
}
|
||||
}
|
||||
|
||||
private updateScale()
|
||||
|
|
@ -563,7 +566,7 @@ export class Canvas
|
|||
this.visualY = lerp(e, oldY, y);
|
||||
this.visualZoom = lerp(e, oldZoom, zoom);
|
||||
|
||||
this.updateTransform();
|
||||
this.updateTransform(true);
|
||||
}, 50);
|
||||
}
|
||||
|
||||
|
|
@ -581,7 +584,7 @@ export class Canvas
|
|||
this.lastX = e.clientX;
|
||||
this.lastY = e.clientY;
|
||||
|
||||
this.updateTransform();
|
||||
this.updateTransform(false);
|
||||
}
|
||||
protected dragEnd(e: MouseEvent) {}
|
||||
|
||||
|
|
@ -847,9 +850,9 @@ export class CanvasEditor extends Canvas
|
|||
e.stopImmediatePropagation();
|
||||
}
|
||||
|
||||
override updateTransform()
|
||||
override updateTransform(debounce: boolean = true)
|
||||
{
|
||||
super.updateTransform();
|
||||
super.updateTransform(debounce);
|
||||
|
||||
this.pattern.parentElement?.classList.toggle('hidden', !this.preferences.value.gridSnap);
|
||||
if(this.preferences.value.gridSnap)
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -1,14 +1,15 @@
|
|||
import type { Ability, Alignment, Character, CharacterConfig, CharacterVariables, CompiledCharacter, FeatureItem, Level, MainStat, Resistance, SpellConfig, SpellElement, SpellType, TrainingLevel } from "~/types/character";
|
||||
import type { Ability, Alignment, Character, CharacterConfig, CharacterVariables, CompiledCharacter, DamageType, FeatureItem, Level, MainStat, Resistance, SpellConfig, SpellElement, SpellType, TrainingLevel, WeaponType } from "~/types/character";
|
||||
import { z } from "zod/v4";
|
||||
import characterConfig from '#shared/character-config.json';
|
||||
import proses, { preview } from "#shared/proses";
|
||||
import { button, buttongroup, foldable, input, loading, numberpicker, select, tabgroup, Toaster, toggle } from "#shared/components.util";
|
||||
import { button, buttongroup, floater, foldable, input, loading, numberpicker, select, tabgroup, Toaster, toggle } from "#shared/components.util";
|
||||
import { div, dom, icon, span, text } from "#shared/dom.util";
|
||||
import { followermenu, fullblocker, tooltip } from "#shared/floating.util";
|
||||
import { clamp } from "#shared/general.util";
|
||||
import markdown from "#shared/markdown.util";
|
||||
import { getText } from "./i18n";
|
||||
import type { User } from "~/types/auth";
|
||||
import { MarkdownEditor } from "./editor.util";
|
||||
|
||||
const config = characterConfig as CharacterConfig;
|
||||
|
||||
|
|
@ -21,6 +22,8 @@ export const CATEGORIES = ["action","reaction","freeaction","misc"] as const;
|
|||
export const SPELL_ELEMENTS = ["fire","ice","thunder","earth","arcana","air","nature","light","psyche"] as const;
|
||||
export const ALIGNMENTS = ['loyal_good', 'neutral_good', 'chaotic_good', 'loyal_neutral', 'neutral_neutral', 'chaotic_neutral', 'loyal_evil', 'neutral_evil', 'chaotic_evil'] as const;
|
||||
export const RESISTANCES = ['stun','bleed','poison','fear','influence','charm','possesion','precision','knowledge','instinct'] as const;
|
||||
export const DAMAGE_TYPES = ['slashing', 'piercing', 'bludgening', 'magic', 'fire', 'thunder', 'cold'] as const;
|
||||
export const WEAPON_TYPES = ["light", "shield", "heavy", "classic", "throw", "natural", "twohanded", "finesse", "reach", "projectile"] as const;
|
||||
|
||||
export const defaultCharacter: Character = {
|
||||
id: -1,
|
||||
|
|
@ -212,6 +215,27 @@ export const resistanceTexts: Record<Resistance, string> = {
|
|||
'knowledge': 'Sorts de savoir',
|
||||
'instinct': 'Sorts d\'instinct',
|
||||
};
|
||||
export const damageTypeTexts: Record<DamageType, string> = {
|
||||
'bludgening': 'Contondant',
|
||||
'cold': 'Froid',
|
||||
'fire': 'Feu',
|
||||
'magic': 'Magique',
|
||||
'piercing': 'Perçant',
|
||||
'slashing': 'Tranchant',
|
||||
'thunder': 'Foudre',
|
||||
};
|
||||
export const weaponTypeTexts: Record<WeaponType, string> = {
|
||||
"light": "Arme légère",
|
||||
"shield": "Bouclier",
|
||||
"heavy": "Arme lourde",
|
||||
"classic": "Arme",
|
||||
"throw": "Arme de jet",
|
||||
"natural": "Arme naturelle",
|
||||
"twohanded": "Deux mains",
|
||||
"finesse": "Arme maniable",
|
||||
"reach": "Arme longue",
|
||||
"projectile": "Arme à projectile",
|
||||
};
|
||||
|
||||
export const CharacterVariablesValidation = z.object({
|
||||
health: z.number(),
|
||||
|
|
@ -235,7 +259,10 @@ export const CharacterValidation = z.object({
|
|||
people: z.string().nullable(),
|
||||
level: z.number().min(1).max(20),
|
||||
aspect: z.number().nullable().optional(),
|
||||
notes: z.string().nullable().optional(),
|
||||
notes: z.object({
|
||||
public: z.string().optional(),
|
||||
private: z.string().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()),
|
||||
abilities: z.record(z.enum(ABILITIES), z.number().optional()),
|
||||
|
|
@ -539,6 +566,14 @@ export class CharacterBuilder extends CharacterCompiler
|
|||
}
|
||||
private render()
|
||||
{
|
||||
const publicNotes = new MarkdownEditor(), privateNotes = new MarkdownEditor();
|
||||
this._character.notes ??= { public: '', private: '' };
|
||||
|
||||
publicNotes.onChange = (v) => this._character.notes!.public = this._result.notes.public = v;
|
||||
privateNotes.onChange = (v) => this._character.notes!.private = this._result.notes.private = v;
|
||||
publicNotes.content = this._character.notes.public!;
|
||||
privateNotes.content = this._character.notes.private!;
|
||||
|
||||
this._steps = [
|
||||
PeoplePicker,
|
||||
LevelPicker,
|
||||
|
|
@ -556,7 +591,7 @@ export class CharacterBuilder extends CharacterCompiler
|
|||
this._content = dom('div', { class: 'flex-1 outline-none max-w-full w-full overflow-y-auto', attributes: { id: 'characterEditorContainer' } });
|
||||
this._container.appendChild(div('flex flex-1 flex-col justify-start items-center px-8 w-full h-full overflow-y-hidden', [
|
||||
div("flex w-full flex-row gap-4 items-center justify-between px-4 bg-light-0 dark:bg-dark-0 z-20", [
|
||||
div(), div("flex w-full flex-row gap-4 items-center justify-center relative", this._stepsHeader), div(undefined, [ tooltip(icon("radix-icons:question-mark-circled", { height: 20, width: 20 }), this._helperText, "bottom-end") ]),
|
||||
div('flex flex-row gap-2', [ floater(tooltip(button(icon('radix-icons:pencil-2', { width: 16, height: 16 }), undefined, 'p-1'), 'Notes publics', 'left'), [ publicNotes.dom ], { pinned: true, events: { show: ['click'], hide: [] }, title: 'Notes publics', position: 'bottom-start' }), floater(tooltip(button(icon('radix-icons:eye-none', { width: 16, height: 16 }), undefined, 'p-1'), 'Notes privés', 'right'), [ privateNotes.dom ], { pinned: true, events: { show: ['click'], hide: [] }, title: 'Notes privés', position: 'bottom-start' }) ]), div("flex w-full flex-row gap-4 items-center justify-center relative", this._stepsHeader), div(undefined, [ tooltip(icon("radix-icons:question-mark-circled", { height: 20, width: 20 }), this._helperText, "bottom-end") ]),
|
||||
]),
|
||||
this._content,
|
||||
]));
|
||||
|
|
@ -1505,7 +1540,7 @@ export class CharacterSheet
|
|||
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', [ e.spell.concentration ? proses('a', preview, [span('italic text-sm', 'concentration')], { href: '' }) : undefined, span(undefined, typeof e.spell.speed === 'number' ? `${e.spell.speed} minute${e.spell.speed > 1 ? 's' : ''}` : e.spell.speed) ])
|
||||
]),
|
||||
div('flex flex-row ps-4 p-1 border-l-4 border-light-35 dark:border-dark-35', [ markdown(e.spell.effect) ]),
|
||||
div('flex flex-row ps-4 p-1 border-l-4 border-light-35 dark:border-dark-35', [ markdown(e.spell.description) ]),
|
||||
]) : undefined }));
|
||||
return [
|
||||
div('flex flex-col gap-2', [
|
||||
|
|
@ -1559,7 +1594,7 @@ export class CharacterSheet
|
|||
}, "px-2 py-1 text-sm font-normal");
|
||||
toggleButton.disabled = state === 'given';
|
||||
return foldable(() => [
|
||||
markdown(spell.effect),
|
||||
markdown(spell.description),
|
||||
], [ div("flex flex-row justify-between gap-2", [
|
||||
dom("span", { class: "text-lg font-bold", text: spell.name }),
|
||||
div("flex flex-row items-center gap-6", [
|
||||
|
|
|
|||
|
|
@ -400,7 +400,7 @@ export function numberpicker(settings?: { defaultValue?: number, change?: (value
|
|||
}
|
||||
return false;
|
||||
}
|
||||
const field = dom("input", { attributes: { disabled: settings?.disabled }, class: [`w-14 mx-4 caret-light-50 dark:caret-dark-50 text-light-100 dark:text-dark-100 placeholder:text-light-50 dark:placeholder:text-dark-50 bg-light-20 dark:bg-dark-20 appearance-none outline-none px-3 py-1 focus:shadow-raw transition-[box-shadow] focus:shadow-light-40 dark:focus:shadow-dark-40 border border-light-35 dark:border-dark-35 hover:border-light-50 dark:hover:border-dark-50 disabled:shadow-none disabled:bg-light-20 dark:disabled:bg-dark-20 disabled:border-light-20 dark:disabled:border-dark-20`, settings?.class], listeners: {
|
||||
const field = dom("input", { attributes: { disabled: settings?.disabled }, class: [`w-14 mx-4 caret-light-50 dark:caret-dark-50 text-light-100 dark:text-dark-100 placeholder:text-light-50 dark:placeholder:text-dark-50 bg-light-20 dark:bg-dark-20 appearance-none outline-none px-3 py-1 focus:shadow-raw transition-[box-shadow] focus:shadow-light-40 dark:focus:shadow-dark-40 border border-light-35 dark:border-dark-35 hover:border-light-50 dark:hover:border-dark-50 disabled:shadow-none disabled:bg-light-20 dark:disabled:bg-dark-20 disabled:border-dashed disabled:border-light-30 dark:disabled:border-dark-30`, settings?.class], listeners: {
|
||||
input: () => validateAndChange(parseInt(field.value.trim().toLowerCase().normalize().replace(/[a-z,.]/g, ""), 10)) && settings?.input && settings.input(storedValue),
|
||||
keydown: (e: KeyboardEvent) => {
|
||||
if(field.disabled)
|
||||
|
|
@ -526,12 +526,29 @@ export function tabgroup(tabs: Array<{ id: string, title: NodeChildren, content:
|
|||
})
|
||||
return container as HTMLDivElement & { refresh: () => void };
|
||||
}
|
||||
export function floater(container: HTMLElement, content: NodeChildren | (() => NodeChildren), settings?: { href?: RouteLocationRaw, class?: Class, position?: Placement, pinned?: boolean, minimizable?: boolean, cover?: 'width' | 'height' | 'all' | 'none', events?: { show: Array<keyof HTMLElementEventMap>, hide: Array<keyof HTMLElementEventMap>, onshow?: (this: HTMLElement, state: FloatState) => boolean, onhide?: (this: HTMLElement, state: FloatState) => boolean }, title?: string })
|
||||
export function floater(container: HTMLElement, content: NodeChildren | (() => NodeChildren), settings?: { href?: RouteLocationRaw, class?: Class, position?: Placement, pinned?: boolean, minimizable?: boolean, cover?: 'width' | 'height' | 'all' | 'none', events?: { show: Array<keyof HTMLElementEventMap>, hide: Array<keyof HTMLElementEventMap>, onshow?: (state: FloatState) => boolean, onhide?: (state: FloatState) => boolean }, title?: string })
|
||||
{
|
||||
let viewport = document.getElementById('mainContainer') ?? undefined;
|
||||
let diffX, diffY;
|
||||
let minimizeBox: DOMRect, minimized = false;
|
||||
|
||||
const events: { show: Array<keyof HTMLElementEventMap>, hide: Array<keyof HTMLElementEventMap>, onshow?: (this: HTMLElement, state: FloatState) => boolean, onhide?: (this: HTMLElement, state: FloatState) => boolean } = Object.assign({
|
||||
show: ['mouseenter', 'mousemove', 'focus'],
|
||||
hide: ['mouseleave', 'blur'],
|
||||
}, settings?.events ?? {});
|
||||
|
||||
if(settings?.pinned)
|
||||
{
|
||||
events.onshow = (state) => {
|
||||
if(!settings?.events?.onshow || settings?.events?.onshow(state))
|
||||
{
|
||||
floating.show();
|
||||
pin();
|
||||
}
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
const dragstart = (e: MouseEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
|
|
@ -585,6 +602,30 @@ export function floater(container: HTMLElement, content: NodeChildren | (() => N
|
|||
window.removeEventListener('mouseup', resizeend);
|
||||
};
|
||||
|
||||
const pin = () => {
|
||||
if(floating.content.hasAttribute('data-pinned'))
|
||||
return;
|
||||
|
||||
const box = floating.content.children.item(0)!.getBoundingClientRect();
|
||||
const viewbox = viewport?.getBoundingClientRect() ?? { x: 0, y: 0, width: window.innerWidth, height: window.innerHeight, left: 0, right: window.innerWidth, top: 0, bottom: window.innerHeight };
|
||||
Object.assign(floating.content.style, {
|
||||
left: `${clamp(box.left, viewbox.left, viewbox.right)}px`,
|
||||
top: `${clamp(box.top, viewbox.top, viewbox.bottom)}px`,
|
||||
width: `${box.width + 21}px`,
|
||||
height: `${box.height + 21}px`,
|
||||
});
|
||||
floating.content.attributeStyleMap.delete('bottom');
|
||||
floating.content.attributeStyleMap.delete('right');
|
||||
floating.stop();
|
||||
|
||||
floating.content.addEventListener('mousedown', function() {
|
||||
if(!floating.content.hasAttribute('data-pinned'))
|
||||
return;
|
||||
|
||||
[...this.parentElement?.children ?? []].forEach(e => (e as any as HTMLElement).attributeStyleMap.set('z-index', -1));
|
||||
this.attributeStyleMap.set('z-index', 0);
|
||||
}, { passive: true });
|
||||
}
|
||||
const minimize = () => {
|
||||
minimized = !minimized;
|
||||
floating.content.toggleAttribute('data-minimized', minimized);
|
||||
|
|
@ -612,13 +653,17 @@ export function floater(container: HTMLElement, content: NodeChildren | (() => N
|
|||
|
||||
const floating = popper(container, {
|
||||
arrow: true,
|
||||
delay: 150,
|
||||
delay: settings?.pinned ? 0 : 150,
|
||||
offset: 12,
|
||||
cover: settings?.cover,
|
||||
placement: settings?.position,
|
||||
class: 'bg-light-10 dark:bg-dark-10 border border-light-35 dark:border-dark-35 group-data-[pinned]:bg-light-15 dark:group-data-[pinned]:bg-dark-15 group-data-[pinned]:border-light-50 dark:group-data-[pinned]:border-dark-50 text-light-100 dark:text-dark-100 z-[45] relative group-data-[pinned]:h-full',
|
||||
content: () => [
|
||||
settings?.pinned !== undefined ? div('hidden group-data-[pinned]:flex flex-row items-center border-b border-light-35 dark:border-dark-35', [ dom('span', { class: 'flex-1 w-full h-full cursor-move group-data-[minimized]:cursor-default text-xs px-2', listeners: { mousedown: dragstart }, text: (settings?.title?.substring(0, 1)?.toUpperCase() ?? '') + (settings?.title?.substring(1)?.toLowerCase() ?? '') }), settings?.title ? tooltip(dom('div', { class: 'cursor-pointer flex', listeners: { click: minimize } }, [icon('radix-icons:minus', { width: 12, height: 12, class: 'p-1' })]), text('Réduire'), 'top') : undefined, settings?.href ? tooltip(dom('div', { class: 'cursor-pointer flex', listeners: { mousedown: (e) => { useRouter().push(settings.href!); floating.hide(); } } }, [icon('radix-icons:external-link', { width: 12, height: 12, class: 'p-1' })]), 'Ouvrir', 'top') : undefined, tooltip(dom('div', { class: 'cursor-pointer flex', listeners: { mousedown: (e) => {
|
||||
settings?.pinned !== undefined ? div('hidden group-data-[pinned]:flex flex-row items-center border-b border-light-35 dark:border-dark-35', [
|
||||
dom('span', { class: 'flex-1 w-full h-full cursor-move group-data-[minimized]:cursor-default text-xs px-2', listeners: { mousedown: dragstart }, text: (settings?.title?.substring(0, 1)?.toUpperCase() ?? '') + (settings?.title?.substring(1)?.toLowerCase() ?? '') }),
|
||||
settings?.title ? tooltip(dom('div', { class: 'cursor-pointer flex', listeners: { click: minimize } }, [icon('radix-icons:minus', { width: 12, height: 12, class: 'p-1' })]), text('Réduire'), 'top') : undefined,
|
||||
settings?.href ? tooltip(dom('div', { class: 'cursor-pointer flex', listeners: { mousedown: (e) => { ((e.ctrlKey || e.button === 1) ? window.open : useRouter().push)(useRouter().resolve(settings.href!).href); floating.hide(); } } }, [icon('radix-icons:external-link', { width: 12, height: 12, class: 'p-1' })]), 'Ouvrir', 'top') : undefined,
|
||||
tooltip(dom('div', { class: 'cursor-pointer flex', listeners: { mousedown: (e) => {
|
||||
e.stopImmediatePropagation();
|
||||
floating.hide();
|
||||
|
||||
|
|
@ -634,41 +679,11 @@ export function floater(container: HTMLElement, content: NodeChildren | (() => N
|
|||
div('group-data-[minimized]:hidden h-full group-data-[pinned]:h-[calc(100%-21px)] w-full min-w-[200px] min-h-[150px] max-w-[600px] max-h-[600px] group-data-[pinned]:min-h-[initial] group-data-[pinned]:min-w-[initial] group-data-[pinned]:max-h-[initial] group-data-[pinned]:max-w-[initial] overflow-auto box-content', typeof content === 'function' ? content() : content), dom('span', { class: 'hidden group-data-[pinned]:flex group-data-[minimized]:hidden absolute bottom-0 right-0 cursor-nw-resize z-50', listeners: { mousedown: resizestart } }, [ icon('ph:notches', { width: 12, height: 12 }) ])
|
||||
],
|
||||
viewport,
|
||||
events: settings?.events,
|
||||
events: events
|
||||
});
|
||||
|
||||
if(settings?.pinned === false)
|
||||
{
|
||||
floating.content.addEventListener('dblclick', () => {
|
||||
if(floating.content.hasAttribute('data-pinned'))
|
||||
return;
|
||||
|
||||
const box = floating.content.children.item(0)!.getBoundingClientRect();
|
||||
const viewbox = viewport?.getBoundingClientRect() ?? { x: 0, y: 0, width: window.innerWidth, height: window.innerHeight, left: 0, right: window.innerWidth, top: 0, bottom: window.innerHeight };
|
||||
Object.assign(floating.content.style, {
|
||||
left: `${clamp(box.left, viewbox.left, viewbox.right)}px`,
|
||||
top: `${clamp(box.top, viewbox.top, viewbox.bottom)}px`,
|
||||
width: `${box.width + 21}px`,
|
||||
height: `${box.height + 21}px`,
|
||||
});
|
||||
floating.content.attributeStyleMap.delete('bottom');
|
||||
floating.content.attributeStyleMap.delete('right');
|
||||
floating.stop();
|
||||
|
||||
floating.content.addEventListener('mousedown', function() {
|
||||
if(!floating.content.hasAttribute('data-pinned'))
|
||||
return;
|
||||
|
||||
[...this.parentElement?.children ?? []].forEach(e => (e as any as HTMLElement).attributeStyleMap.set('z-index', -1));
|
||||
this.attributeStyleMap.set('z-index', 0);
|
||||
}, { passive: true });
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
floating.stop();
|
||||
floating.show();
|
||||
}
|
||||
floating.content.addEventListener('dblclick', pin);
|
||||
|
||||
return container;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import type { Ability, AspectConfig, CharacterConfig, CommonItemConfig, Feature, FeatureChoice, FeatureEquipment, FeatureItem, FeatureList, FeatureValue, i18nID, ItemConfig, Level, MainStat, RaceConfig, Resistance, SpellConfig, TrainingLevel } from "~/types/character";
|
||||
import type { Ability, AspectConfig, CharacterConfig, CommonItemConfig, DamageType, Feature, FeatureChoice, FeatureEquipment, FeatureItem, FeatureList, FeatureValue, i18nID, ItemConfig, Level, MainStat, RaceConfig, Resistance, SpellConfig, TrainingLevel, WeaponType } from "~/types/character";
|
||||
import { div, dom, icon, span, text, type NodeChildren } from "#shared/dom.util";
|
||||
import { MarkdownEditor } from "#shared/editor.util";
|
||||
import { preview } from "#shared/proses";
|
||||
import { button, checkbox, combobox, foldable, input, multiselect, numberpicker, optionmenu, select, tabgroup, table, toggle, type Option } from "#shared/components.util";
|
||||
import { confirm, contextmenu, fullblocker, tooltip } from "#shared/floating.util";
|
||||
import { ABILITIES, abilityTexts, ALIGNMENTS, alignmentTexts, elementTexts, LEVELS, MAIN_STATS, mainStatShortTexts, mainStatTexts, RESISTANCES, resistanceTexts, SPELL_ELEMENTS, SPELL_TYPES, spellTypeTexts } from "#shared/character.util";
|
||||
import { ABILITIES, abilityTexts, ALIGNMENTS, alignmentTexts, damageTypeTexts, elementTexts, LEVELS, MAIN_STATS, mainStatShortTexts, mainStatTexts, RESISTANCES, resistanceTexts, SPELL_ELEMENTS, SPELL_TYPES, spellTypeTexts, weaponTypeTexts } from "#shared/character.util";
|
||||
import characterConfig from "#shared/character-config.json";
|
||||
import { getID } from "#shared/general.util";
|
||||
import markdown, { markdownReference, renderMDAsText } from "#shared/markdown.util";
|
||||
|
|
@ -78,9 +78,10 @@ export class HomebrewBuilder
|
|||
}
|
||||
const render = (people: string, level: Level, feature: string) => {
|
||||
let element = dom("div", { class: ["border border-light-40 dark:border-dark-40 cursor-pointer px-2 py-1 w-[400px] hover:border-light-50 dark:hover:border-dark-50"], listeners: { click: e => {
|
||||
this.edit(config.features[feature]!).then(e => {
|
||||
FeaturePanel.edit(config.features[feature]!).then(e => {
|
||||
config.features[feature] = e;
|
||||
element.replaceChildren(markdown(config.features[feature]!.description, undefined, { tags: { a: preview } }));
|
||||
});
|
||||
}).catch(e => {});
|
||||
}, contextmenu: (e) => {
|
||||
e.preventDefault();
|
||||
const context = contextmenu(e.clientX, e.clientY, [
|
||||
|
|
@ -126,9 +127,10 @@ export class HomebrewBuilder
|
|||
}
|
||||
const render = (stat: MainStat, level: TrainingLevel, feature: string) => {
|
||||
let element = dom("div", { class: ["border border-light-40 dark:border-dark-40 cursor-pointer px-2 py-1 w-[400px] hover:border-light-50 dark:hover:border-dark-50"], listeners: { click: e => {
|
||||
this.edit(config.features[feature]!).then(e => {
|
||||
FeaturePanel.edit(config.features[feature]!).then(e => {
|
||||
config.features[feature] = e;
|
||||
element.replaceChildren(markdown(config.features[feature]!.description, undefined, { tags: { a: preview } }));
|
||||
});
|
||||
}).catch(e => {});
|
||||
}, contextmenu: (e) => {
|
||||
e.preventDefault();
|
||||
const context = contextmenu(e.clientX, e.clientY, [
|
||||
|
|
@ -235,7 +237,7 @@ 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('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' } }), ]),
|
||||
], [ 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.effect = value, defaultValue: spell.effect, 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 = () => {
|
||||
this._config.spells.push({
|
||||
|
|
@ -246,7 +248,7 @@ export class HomebrewBuilder
|
|||
cost: 1,
|
||||
speed: 'action',
|
||||
elements: [],
|
||||
effect: '',
|
||||
description: '',
|
||||
concentration: false,
|
||||
range: 0,
|
||||
tags: [],
|
||||
|
|
@ -385,7 +387,7 @@ export class HomebrewBuilder
|
|||
case 'armor':
|
||||
return { ...common, category: category, health: 0, absorb: { percent: 0, static: 0 }, type: 'light' };
|
||||
case 'weapon':
|
||||
return { ...common, category: category, damage: '0', type: ['classic'] };
|
||||
return { ...common, category: category, damage: { type: 'slashing', value: '0' }, type: ['classic'] };
|
||||
case 'wondrous':
|
||||
case 'mundane':
|
||||
return { ...common, category: category };
|
||||
|
|
@ -435,108 +437,94 @@ export class HomebrewBuilder
|
|||
const optionHolder = div('grid grid-cols-3 gap-2', options.map(e => e.dom));
|
||||
return [ div('flex px-8 py-4 flex-col gap-4', [ div('flex flex-row-reverse', [ button(icon('radix-icons:plus'), optionmenu([{ title: 'Objet inerte', click: () => add('mundane') }, { title: 'Armure', click: () => add('armor') }, { title: 'Arme', click: () => add('weapon') }, { title: 'Objet magique', click: () => add('wondrous') }], { position: 'left-start' }), 'p-1') ]), optionHolder ] ) ];
|
||||
}
|
||||
edit(feature: Feature): Promise<Feature>
|
||||
{
|
||||
const promise: Promise<Feature> = this._featureEditor.edit(feature).then(f => {
|
||||
this._config.features[feature.id] = f;
|
||||
return f;
|
||||
}).catch((e) => { return feature; }).finally(() => {
|
||||
setTimeout(popup.close, 150);
|
||||
this._featureEditor.container.setAttribute('data-state', 'inactive');
|
||||
});
|
||||
const popup = fullblocker([this._featureEditor.container], {
|
||||
priority: true, closeWhenOutside: false,
|
||||
});
|
||||
setTimeout(() => this._featureEditor.container.setAttribute('data-state', 'active'), 1);
|
||||
return promise;
|
||||
}
|
||||
private save()
|
||||
{
|
||||
navigator.clipboard.writeText(JSON.stringify(this._config));
|
||||
}
|
||||
}
|
||||
|
||||
export class FeaturePanel
|
||||
type FeatureOption = Partial<FeatureValue | FeatureEquipment | FeatureList | FeatureChoice> & { id: string };
|
||||
class FeatureEditor
|
||||
{
|
||||
private _container: HTMLDivElement;
|
||||
private _list: Record<string, FeatureOption> | FeatureOption[];
|
||||
private _id: string;
|
||||
private _draft: boolean;
|
||||
|
||||
private _success?: Function;
|
||||
private _failure?: Function;
|
||||
private _feature?: Feature;
|
||||
private _arr: boolean;
|
||||
private option!: FeatureOption;
|
||||
|
||||
private _idInput: HTMLInputElement;
|
||||
private _table: HTMLDivElement;
|
||||
container!: HTMLElement;
|
||||
|
||||
constructor()
|
||||
constructor(list: Record<string, FeatureOption> | FeatureOption[], id: string, draft: boolean)
|
||||
{
|
||||
this._idInput = dom("input", { attributes: { 'disabled': true }, class: `mx-4 text-light-70 dark:text-dark-70 appearance-none outline-none px-3 py-1 focus:shadow-raw transition-[box-shadow] border bg-light-25 dark:bg-dark-25 border-light-30 dark:border-dark-30` });
|
||||
this._table = div('grid grid-cols-2 gap-4 px-2');
|
||||
this._container = dom('div', { attributes: { 'data-state': 'inactive' }, class: 'border-light-35 dark:border-dark-35 bg-light-10 dark:bg-dark-10 border-l absolute top-0 bottom-0 right-0 w-[10%] data-[state=active]:w-1/2 flex flex-col gap-2 text-light-100 dark:text-dark-100 p-8 transition-[width] transition-delay-[150ms]' }, [
|
||||
div('flex flex-row justify-between items-center', [
|
||||
tooltip(button(icon('radix-icons:check', { width: 20, height: 20 }), () => {
|
||||
this._success!(this._feature);
|
||||
MarkdownEditor.singleton.onChange = undefined;
|
||||
}, 'p-1'), 'Valider', 'left'),
|
||||
dom('label', { class: 'flex justify-center items-center my-2' }, [
|
||||
dom('span', { class: 'pb-1 md:p-0', text: "ID" }),
|
||||
this._idInput
|
||||
]),
|
||||
tooltip(button(icon('radix-icons:cross-1', { width: 20, height: 20 }), () => {
|
||||
this._failure!(this._feature);
|
||||
MarkdownEditor.singleton.onChange = undefined;
|
||||
}, 'p-1'), 'Annuler', 'left'),
|
||||
]),
|
||||
dom('span', { class: 'flex flex-col justify-start items-start my-2 gap-4' }, [
|
||||
div('flex w-full items-center justify-between', [
|
||||
dom('span', { class: 'pb-1 md:p-0', text: "Description" }),
|
||||
tooltip(button(icon('radix-icons:clipboard', { width: 20, height: 20 }), () => {
|
||||
MarkdownEditor.singleton.content = this._feature?.effect.map(e => textFromEffect(e)).join('\n') ?? this._feature?.description ?? MarkdownEditor.singleton.content;
|
||||
if(this._feature?.description) this._feature.description = MarkdownEditor.singleton.content;
|
||||
}, 'p-1'), 'Description automatique', 'left'),
|
||||
]),
|
||||
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 ]),
|
||||
]),
|
||||
div('flex flex-col gap-2 w-full', [
|
||||
div('flex flex-row justify-between', [
|
||||
dom('h3', { class: 'text-lg font-bold', text: 'Effets' }),
|
||||
tooltip(button(icon('radix-icons:plus', { width: 20, height: 20 }), () => {
|
||||
this._table.appendChild(this._edit({ id: getID() }));
|
||||
}, 'p-1'), 'Ajouter', 'left'),
|
||||
]),
|
||||
this._table,
|
||||
])
|
||||
]);
|
||||
this._arr = Array.isArray(list);
|
||||
this._list = list;
|
||||
this._id = id;
|
||||
|
||||
if(this._arr ? !(list as FeatureOption[]).find(e => e.id === id) : !list.hasOwnProperty(id))
|
||||
throw new Error();
|
||||
|
||||
this.read();
|
||||
this._draft = draft;
|
||||
|
||||
this.container = div();
|
||||
|
||||
if(draft)
|
||||
this.edit();
|
||||
else
|
||||
this.show();
|
||||
}
|
||||
edit(feature: Feature): Promise<Feature>
|
||||
private read()
|
||||
{
|
||||
return new Promise((success, failure) => {
|
||||
this._success = success;
|
||||
this._failure = failure;
|
||||
|
||||
this._feature = JSON.parse(JSON.stringify(feature)) as Feature;
|
||||
|
||||
this._table.replaceChildren(...this._feature.effect.map(this._renderEffect.bind(this)));
|
||||
this._idInput.value = this._feature.id;
|
||||
MarkdownEditor.singleton.onChange = (e) => this._feature!.description = e;
|
||||
MarkdownEditor.singleton.content = this._feature.description;
|
||||
});
|
||||
this.option = JSON.parse(JSON.stringify(this._arr ? (this._list as FeatureOption[]).find(e => e.id === this._id)! : (this._list as Record<string, FeatureOption>)[this._id]!));
|
||||
}
|
||||
private _renderEffect(effect: Partial<FeatureItem>): HTMLDivElement
|
||||
private update()
|
||||
{
|
||||
if(this._arr)
|
||||
{
|
||||
const idx = (this._list as FeatureOption[]).findIndex(e => e.id === this.option.id);
|
||||
|
||||
if(idx === -1)
|
||||
throw new Error();
|
||||
|
||||
(this._list as FeatureOption[])[idx]! = this.option;
|
||||
}
|
||||
else
|
||||
{
|
||||
(this._list as Record<string, FeatureOption>)[this.option.id] = this.option;
|
||||
}
|
||||
}
|
||||
private delete()
|
||||
{
|
||||
if(this._arr)
|
||||
{
|
||||
const idx = (this._list as FeatureOption[]).findIndex(e => e.id === this.option.id);
|
||||
|
||||
if(idx === -1)
|
||||
throw new Error();
|
||||
|
||||
(this._list as FeatureOption[]).splice(idx, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
delete (this._list as Record<string, FeatureOption>)[this.option.id];
|
||||
}
|
||||
}
|
||||
private show()
|
||||
{
|
||||
const content = div('border border-light-30 dark:border-dark-30 col-span-1', [ div('flex justify-between items-center', [
|
||||
div('px-4 flex items-center h-full', [ markdown(textFromEffect(effect), undefined, { tags: { a: preview } }) ]),
|
||||
div('flex', [ tooltip(button(icon('radix-icons:pencil-1'), () => {
|
||||
content.replaceWith(this._edit(effect));
|
||||
}, 'p-2 -m-px hover:z-10 border border-light-35 dark:border-dark-35 hover:border-light-50 dark:hover:border-dark-50'), "Modifieur", "bottom"), tooltip(button(icon('radix-icons:trash'), () => {
|
||||
this._feature!.effect = this._feature!.effect.filter(e => e.id !== effect.id);
|
||||
content.remove();
|
||||
div('px-4 flex items-center h-full', [ markdown(textFromEffect(this.option), undefined, { tags: { a: preview } }) ]),
|
||||
div('flex', [ tooltip(button(icon('radix-icons:pencil-1'), () => this.edit(), 'p-2 -m-px hover:z-10 border border-light-35 dark:border-dark-35 hover:border-light-50 dark:hover:border-dark-50'), "Modifieur", "bottom"), tooltip(button(icon('radix-icons:trash'), () => {
|
||||
this.delete();
|
||||
this.container.remove();
|
||||
}, 'p-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") ])
|
||||
]) ]);
|
||||
return content;
|
||||
|
||||
this.container.replaceWith(content);
|
||||
this.container = content;
|
||||
}
|
||||
private _edit(effect: Partial<FeatureItem>): HTMLDivElement
|
||||
private static match(effect: FeatureOption): Partial<FeatureOption> | undefined
|
||||
{
|
||||
const match = (effect: FeatureItem): Partial<FeatureItem> | undefined => {
|
||||
switch(effect.category)
|
||||
{
|
||||
case 'value':
|
||||
|
|
@ -546,75 +534,76 @@ export class FeaturePanel
|
|||
case 'list':
|
||||
return flattenFeatureChoices.findLast(e => e.category === 'list' && e.list === effect.list);
|
||||
}
|
||||
};
|
||||
const approve = () => {
|
||||
const idx = this._feature!.effect.findIndex(e => e.id === _buffer.id);
|
||||
|
||||
if(idx === -1)
|
||||
this._feature!.effect.push(_buffer);
|
||||
else
|
||||
this._feature!.effect[idx] = _buffer;
|
||||
|
||||
this._table.replaceChild(this._renderEffect(_buffer), content);
|
||||
}, reject = () => {
|
||||
const idx = this._feature!.effect.findIndex(e => e.id === _buffer.id);
|
||||
|
||||
if(idx === -1)
|
||||
content.remove();
|
||||
else
|
||||
this._table.replaceChild(this._renderEffect(effect), content);
|
||||
}
|
||||
let _buffer = JSON.parse(JSON.stringify(effect)) as FeatureItem;
|
||||
|
||||
const drawByCategory = (buffer: Partial<FeatureItem>) => {
|
||||
private editByCategory(buffer: FeatureOption)
|
||||
{
|
||||
let top: NodeChildren = [], bottom: NodeChildren = [];
|
||||
switch(buffer.category)
|
||||
switch(this.option.category)
|
||||
{
|
||||
case 'value':
|
||||
const valueVariable = () => typeof buffer.value === 'number' ? numberpicker({ defaultValue: buffer.value, input: (value) => { (buffer as FeatureValue).value = value; summaryText.textContent = textFromEffect(buffer); }, class: 'bg-light-25 dark:bg-dark-25 !-m-px hover:z-10 h-[36px] w-[80px]' }) : select<`modifier/${MainStat}` | false>([...Object.entries(mainStatShortTexts).map(e => ({ text: 'Mod. de ' + e[1], value: `modifier/${e[0]}` as `modifier/${MainStat}` })), buffer.operation === 'add' ? undefined : { text: 'Interdit', value: false }], { class: { container: 'w-[160px] bg-light-25 dark:bg-dark-25 !-m-px hover:z-10 h-[36px]' }, defaultValue: buffer.value, change: (value) => { (buffer as FeatureValue).value = value; summaryText.textContent = textFromEffect(buffer); } });
|
||||
return this.editValue(buffer as Partial<FeatureValue>);
|
||||
case 'list':
|
||||
return this.editList(buffer as Partial<FeatureList>);
|
||||
case 'choice':
|
||||
return this.editChoice(buffer as Partial<FeatureChoice>);
|
||||
default: break;
|
||||
}
|
||||
return { top, bottom };
|
||||
}
|
||||
private editValue(buffer: Partial<FeatureValue | FeatureEquipment>)
|
||||
{
|
||||
const valueVariable = () => typeof buffer.value === 'number' ? numberpicker({ defaultValue: buffer.value, input: (value) => { buffer.value = value; summaryText.textContent = textFromEffect(buffer); }, class: 'bg-light-25 dark:bg-dark-25 !-m-px hover:z-10 h-[36px] w-[80px]' }) : select<`modifier/${MainStat}` | false>([...Object.entries(mainStatShortTexts).map(e => ({ text: 'Mod. de ' + e[1], value: `modifier/${e[0]}` as `modifier/${MainStat}` })), buffer.operation === 'add' ? undefined : { text: 'Interdit', value: false }], { class: { container: 'w-[160px] bg-light-25 dark:bg-dark-25 !-m-px hover:z-10 h-[36px]' }, defaultValue: buffer.value, change: (value) => { buffer.value = value; summaryText.textContent = textFromEffect(buffer); } });
|
||||
const summaryText = text(textFromEffect(buffer));
|
||||
let valueSelection = valueVariable();
|
||||
top = [
|
||||
select([ (['action', 'reaction'].includes(buffer.property ?? '') ? undefined : { text: '+', value: 'add' }), (['speed', 'capacity', 'action', 'reaction'].includes(buffer.property ?? '') || ['defense/'].some(e => (buffer as FeatureValue).property.startsWith(e))) ? { text: '=', value: 'set' } : undefined ], { defaultValue: buffer.operation, change: (value) => { (buffer as FeatureValue).operation = value as 'add' | 'set'; summaryText.textContent = textFromEffect(buffer); }, class: { container: 'bg-light-25 dark:bg-dark-25 !-m-px hover:z-10 h-[36px] w-[80px]' } }),
|
||||
|
||||
return { top: [
|
||||
select([ (['action', 'reaction'].includes(buffer.property ?? '') ? undefined : { text: '+', value: 'add' }), (['speed', 'capacity', 'action', 'reaction'].includes(buffer.property ?? '') || ['defense/'].some(e => buffer.property?.startsWith(e))) ? { text: '=', value: 'set' } : undefined ], { defaultValue: buffer.operation, change: (value) => { buffer.operation = value as 'add' | 'set'; summaryText.textContent = textFromEffect(buffer); }, class: { container: 'bg-light-25 dark:bg-dark-25 !-m-px hover:z-10 h-[36px] w-[80px]' } }),
|
||||
valueSelection,
|
||||
tooltip(button(icon('radix-icons:update'), () => {
|
||||
(buffer as FeatureValue).value = (typeof (buffer as FeatureValue).value === 'number' ? '' as any as false : 0);
|
||||
buffer.value = (typeof buffer.value === 'number' ? '' as any as false : 0);
|
||||
const newValueSelection = valueVariable();
|
||||
valueSelection.replaceWith(newValueSelection);
|
||||
valueSelection = newValueSelection;
|
||||
summaryText.textContent = textFromEffect(buffer);
|
||||
}, 'px-2 -m-px hover:z-10 border border-light-35 dark:border-dark-35 hover:border-light-50 dark:hover:border-dark-50'), 'Changer d\'editeur', 'bottom'),
|
||||
];
|
||||
bottom = [ div('px-2 py-1 flex items-center flex-1', [summaryText]) ];
|
||||
break;
|
||||
case 'list':
|
||||
top = [ select([ { text: 'Ajouter', value: 'add' }, { text: 'Supprimer', value: 'remove' } ], { defaultValue: buffer.action, change: (value) => {
|
||||
(buffer as FeatureList).action = value as 'add' | 'remove';
|
||||
const element = redraw();
|
||||
content.replaceWith(element);
|
||||
content = element;
|
||||
}, class: { container: 'bg-light-25 dark:bg-dark-25 !-m-px hover:z-10 h-[36px] w-32' } }) ];
|
||||
], bottom: [
|
||||
div('px-2 py-1 flex items-center flex-1', [summaryText])
|
||||
] };
|
||||
}
|
||||
private editList(buffer: Partial<FeatureList>)
|
||||
{
|
||||
let list: Option<string>[];
|
||||
if(buffer.action === 'add')
|
||||
{
|
||||
if(buffer.list === 'spells')
|
||||
{
|
||||
bottom = [ combobox(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.effect)) ]) ]), value: e.id })), { defaultValue: buffer.item, change: (value) => (buffer as FeatureList).item = value, class: { container: 'bg-light-25 dark:bg-dark-25 hover:z-10 h-[36px] w-full hover:outline-px outline-light-50 dark:outline-dark-50 !border-none' }, fill: 'contain' }) ];
|
||||
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 }));
|
||||
}
|
||||
else if(buffer.list)
|
||||
{
|
||||
bottom = [ combobox(Object.values(config[buffer.list]).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 })), { defaultValue: buffer.item, change: (value) => (buffer as FeatureList).item = value, class: { container: 'bg-light-25 dark:bg-dark-25 hover:z-10 h-[36px] w-full hover:outline-px outline-light-50 dark:outline-dark-50 !border-none' }, fill: 'contain' }) ];
|
||||
list = Object.values(config[buffer.list]).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 }));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
bottom = [ combobox(Object.values(config.features).flatMap(e => e.effect).filter(e => e.category === 'list' && e.list === buffer.list && e.action === 'add').map((e) => (e as FeatureList).list === 'spells' ? config.spells.find(f => f.id === (e as FeatureList).item) : config[(e as FeatureList).list][(e as FeatureList).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 })), { defaultValue: buffer.item, change: (item) => buffer.item = item, class: { container: 'bg-light-25 dark:bg-dark-25 !-m-px hover:z-10 h-[36px] w-full overflow-hidden truncate', option: 'max-h-[90px] text-sm' }, fill: 'contain' }) ];
|
||||
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 }));
|
||||
}
|
||||
break;
|
||||
case 'choice':
|
||||
const availableChoices: Option<Partial<FeatureValue | FeatureList>>[] = featureChoices.filter(e => (e?.value as FeatureItem)?.category !== 'choice').map(e => { if(e) e.value = Array.isArray(e.value) ? e.value.filter(f => (f?.value as FeatureItem)?.category !== 'choice') : e.value; return e; }) as Option<Partial<FeatureValue | FeatureList>>[];
|
||||
|
||||
return {
|
||||
top: [ select([ { text: 'Ajouter', value: 'add' }, { text: 'Supprimer', value: 'remove' } ], { defaultValue: buffer.action, change: (value) => {
|
||||
buffer.action = value as 'add' | 'remove';
|
||||
this.edit();
|
||||
}, class: { container: 'bg-light-25 dark:bg-dark-25 !-m-px hover:z-10 h-[36px] w-32' } }) ],
|
||||
bottom: [ combobox(list!, { defaultValue: buffer.item, change: (item) => buffer.item = item, class: { container: 'bg-light-25 dark:bg-dark-25 !-m-px hover:z-10 h-[36px] w-full overflow-hidden truncate', option: 'max-h-[90px] text-sm' }, fill: 'contain' }) ]
|
||||
}
|
||||
}
|
||||
private editChoice(buffer: Partial<FeatureChoice>)
|
||||
{
|
||||
const availableChoices: Option<Partial<FeatureValue | FeatureList>>[] = featureChoices.filter(e => (e?.value as FeatureOption)?.category !== 'choice').map(e => { if(e) e.value = Array.isArray(e.value) ? e.value.filter(f => (f?.value as FeatureOption)?.category !== 'choice') : e.value; return e; }) as Option<Partial<FeatureValue | FeatureList>>[];
|
||||
const addChoice = () => {
|
||||
const choice: { text: string; effects: (Partial<FeatureValue | FeatureList>)[]; } = { effects: [{ id: getID() }], text: '' };
|
||||
(buffer as FeatureChoice).options.push(choice as FeatureChoice["options"][number]);
|
||||
buffer.options ??= [];
|
||||
buffer.options.push(choice as FeatureChoice["options"][number]);
|
||||
return choice;
|
||||
};
|
||||
const addEffect = (choice: { text: string; effects: (Partial<FeatureValue | FeatureList>)[] }) => {
|
||||
|
|
@ -623,10 +612,10 @@ export class FeaturePanel
|
|||
return effect;
|
||||
};
|
||||
const renderEffect = (option: { text: string; effects: (Partial<FeatureValue | FeatureList>)[] }, effect: Partial<FeatureValue | FeatureList>) => {
|
||||
const { top: _top, bottom: _bottom } = drawByCategory(effect);
|
||||
const { top: _top, bottom: _bottom } = this.editByCategory(effect as FeatureOption);
|
||||
let element = div('border border-light-30 dark:border-dark-30 col-span-2 row-span-2', [ div('flex justify-between items-stretch', [
|
||||
div('flex flex-row flex-1', [
|
||||
combobox(availableChoices, { defaultValue: match(effect as FeatureItem) as Partial<FeatureValue | FeatureList> | undefined, class: { container: 'bg-light-25 dark:bg-dark-25 w-[300px] -m-px hover:z-10 h-[36px]' }, fill: 'cover', change: (e: Partial<FeatureValue | FeatureList>) => {
|
||||
combobox(availableChoices, { defaultValue: FeatureEditor.match(effect as FeatureOption) as Partial<FeatureValue | FeatureList> | undefined, class: { container: 'bg-light-25 dark:bg-dark-25 w-[300px] -m-px hover:z-10 h-[36px]' }, fill: 'cover', change: (e: Partial<FeatureValue | FeatureList>) => {
|
||||
const idx = option.effects.findIndex(e => e === effect);
|
||||
option.effects[idx] = effect = { ...e, id: effect.id };
|
||||
|
||||
|
|
@ -645,40 +634,102 @@ export class FeaturePanel
|
|||
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'), () => {
|
||||
_content.remove();
|
||||
(buffer as FeatureChoice).options = (buffer as FeatureChoice).options.filter(e => e !== option);
|
||||
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 });
|
||||
return _content;
|
||||
}
|
||||
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)) ?? []);
|
||||
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 ];
|
||||
break;
|
||||
default: break;
|
||||
|
||||
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') ],
|
||||
bottom: [ list ],
|
||||
}
|
||||
return { top, bottom };
|
||||
}
|
||||
private edit()
|
||||
{
|
||||
const redraw = () => {
|
||||
const { top, bottom } = drawByCategory(_buffer);
|
||||
const { top, bottom } = this.editByCategory(this.option);
|
||||
return div('border border-light-30 dark:border-dark-30 col-span-2 row-span-2', [ div('flex justify-between items-stretch', [
|
||||
div('flex flex-row flex-1', [
|
||||
combobox(featureChoices, { defaultValue: match(_buffer), class: { container: 'bg-light-25 dark:bg-dark-25 w-[300px] -m-px hover:z-10 h-[36px]' }, fill: 'cover', change: (e) => {
|
||||
_buffer = { id: _buffer.id, ...e } as FeatureItem;
|
||||
const element = redraw();
|
||||
content.replaceWith(element);
|
||||
content = element;
|
||||
combobox(featureChoices, { defaultValue: FeatureEditor.match(this.option), class: { container: 'bg-light-25 dark:bg-dark-25 w-[300px] -m-px hover:z-10 h-[36px]' }, fill: 'cover', change: (e) => {
|
||||
this.option = { id: this.option.id, ...e } as FeatureOption;
|
||||
content = redraw();
|
||||
|
||||
this.container.replaceWith(content);
|
||||
this.container = content;
|
||||
} }),
|
||||
...top,
|
||||
]),
|
||||
div('flex', [ tooltip(button(icon('radix-icons:check'), approve, 'p-2 -m-px hover:z-10 border border-light-35 dark:border-dark-35 hover:border-light-50 dark:hover:border-dark-50'), "Valider", "bottom"), tooltip(button(icon('radix-icons:cross-1'), reject, 'p-2 -m-px hover:z-10 border border-light-35 dark:border-dark-35 hover:border-light-50 dark:hover:border-dark-50'), "Annuler", "bottom") ])
|
||||
div('flex', [ tooltip(button(icon('radix-icons:check'), () => { this.update(); this.read(); this.show(); this._draft = false; }, 'p-2 -m-px hover:z-10 border border-light-35 dark:border-dark-35 hover:border-light-50 dark:hover:border-dark-50'), "Valider", "bottom"), tooltip(button(icon('radix-icons:cross-1'), () => { if(this._draft) { this.delete(); this.container.remove(); } else { this.read(); this.show(); } }, 'p-2 -m-px hover:z-10 border border-light-35 dark:border-dark-35 hover:border-light-50 dark:hover:border-dark-50'), "Annuler", "bottom") ])
|
||||
]), div('flex border-t border-light-35 dark:border-dark-35 max-h-[300px] min-h-[36px] overflow-y-auto overflow-x-hidden', bottom) ]);
|
||||
}
|
||||
|
||||
let content = redraw();
|
||||
return content;
|
||||
|
||||
this.container.replaceWith(content);
|
||||
this.container = content;
|
||||
}
|
||||
get container()
|
||||
}
|
||||
export class FeaturePanel
|
||||
{
|
||||
static render(feature: Feature, success: (feature: Feature) => void, failure: (feature: Feature) => void)
|
||||
{
|
||||
return this._container;
|
||||
const _feature = JSON.parse(JSON.stringify(feature)) as Feature;
|
||||
const effectContainer = div('grid grid-cols-2 gap-4 px-2', _feature.effect.map(e => new FeatureEditor(_feature.effect!, e.id, false).container));
|
||||
MarkdownEditor.singleton.content = getText(_feature.description);
|
||||
MarkdownEditor.singleton.onChange = (value) => ItemPanel.config.texts[_feature.description]!.default = value;
|
||||
return dom('div', { attributes: { 'data-state': 'inactive' }, class: 'border-light-35 dark:border-dark-35 bg-light-10 dark:bg-dark-10 border-l absolute top-0 bottom-0 right-0 w-[10%] data-[state=active]:w-1/2 flex flex-col gap-2 text-light-100 dark:text-dark-100 p-8 transition-[width] transition-delay-[150ms]' }, [
|
||||
div('flex flex-row justify-between items-center', [
|
||||
tooltip(button(icon('radix-icons:check', { width: 20, height: 20 }), () => {
|
||||
success!(_feature);
|
||||
MarkdownEditor.singleton.onChange = undefined;
|
||||
}, 'p-1'), 'Valider', 'left'),
|
||||
dom('label', { class: 'flex justify-center items-center my-2' }, [
|
||||
dom('span', { class: 'pb-1 md:p-0', text: "ID" }),
|
||||
input("text", { defaultValue: _feature.id, disabled: true, class: `mx-4 text-light-70 dark:text-dark-70 appearance-none outline-none px-3 py-1 focus:shadow-raw transition-[box-shadow] border bg-light-25 dark:bg-dark-25 border-light-30 dark:border-dark-30` })
|
||||
]),
|
||||
tooltip(button(icon('radix-icons:cross-1', { width: 20, height: 20 }), () => {
|
||||
failure!(feature);
|
||||
MarkdownEditor.singleton.onChange = undefined;
|
||||
}, 'p-1'), 'Annuler', 'left'),
|
||||
]),
|
||||
dom('span', { class: 'flex flex-col justify-start items-start my-2 gap-4' }, [
|
||||
div('flex w-full items-center justify-between', [
|
||||
dom('span', { class: 'pb-1 md:p-0', text: "Description" }),
|
||||
tooltip(button(icon('radix-icons:clipboard', { width: 20, height: 20 }), () => {
|
||||
MarkdownEditor.singleton.content = _feature?.effect.map(e => textFromEffect(e)).join('\n') ?? _feature?.description ?? MarkdownEditor.singleton.content;
|
||||
if(_feature?.description) _feature.description = MarkdownEditor.singleton.content;
|
||||
}, 'p-1'), 'Description automatique', 'left'),
|
||||
]),
|
||||
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 ]),
|
||||
]),
|
||||
div('flex flex-col gap-2 w-full', [
|
||||
div('flex flex-row justify-between', [
|
||||
dom('h3', { class: 'text-lg font-bold', text: 'Effets' }),
|
||||
tooltip(button(icon('radix-icons:plus', { width: 20, height: 20 }), () => {
|
||||
const f = { id: getID(), };
|
||||
//@ts-expect-error
|
||||
_feature.effect.push(f);
|
||||
effectContainer.appendChild(new FeatureEditor(_feature.effect, f.id, true).container);
|
||||
}, 'p-1'), 'Ajouter', 'left'),
|
||||
]),
|
||||
effectContainer,
|
||||
])
|
||||
]);
|
||||
}
|
||||
static edit(feature: Feature): Promise<Feature>
|
||||
{
|
||||
let container: HTMLElement, close: Function;
|
||||
return new Promise<Feature>((success, failure) => {
|
||||
container = FeaturePanel.render(feature, success, failure);
|
||||
close = fullblocker([container], {
|
||||
priority: true, closeWhenOutside: false,
|
||||
}).close;
|
||||
setTimeout(() => container.setAttribute('data-state', 'active'), 1);
|
||||
}).finally(() => {
|
||||
setTimeout(close, 150);
|
||||
container.setAttribute('data-state', 'inactive');
|
||||
});
|
||||
}
|
||||
}
|
||||
export class ItemPanel
|
||||
|
|
@ -689,6 +740,7 @@ export class ItemPanel
|
|||
const _item = JSON.parse(JSON.stringify(item)) as ItemConfig;
|
||||
MarkdownEditor.singleton.content = getText(_item.description);
|
||||
MarkdownEditor.singleton.onChange = (value) => ItemPanel.config.texts[_item.description]!.default = value;
|
||||
const effectContainer = div('grid grid-cols-2 gap-4 px-2 flex-1', _item.effects?.map(e => new FeatureEditor(_item.effects!, e.id, false).container));
|
||||
return dom('div', { attributes: { 'data-state': 'inactive' }, class: 'border-light-35 dark:border-dark-35 bg-light-10 dark:bg-dark-10 border-l absolute top-0 bottom-0 right-0 w-[10%] data-[state=active]:w-1/2 flex flex-col gap-2 text-light-100 dark:text-dark-100 p-8 transition-[width] transition-delay-[150ms]' }, [
|
||||
div('flex flex-row justify-between items-center', [
|
||||
tooltip(button(icon('radix-icons:check', { width: 20, height: 20 }), () => {
|
||||
|
|
@ -709,14 +761,28 @@ export class ItemPanel
|
|||
div('flex flex-row gap-2 items-center justify-between', [ div('flex flex-row gap-2 items-center', [ checkbox({ defaultValue: _item.price !== undefined, change: function(value) { _item.price = value ? 0 : undefined; if(this.parentElement?.parentElement?.children[1]) { (this.parentElement.parentElement.children[1] as Element & { disabled: boolean }).disabled = !value; (this.parentElement.parentElement.children[1] as HTMLInputElement).value = value ? '0' : ''; } this.parentElement?.toggleAttribute('data-disabled', !value) }, class: { container: '!w-4 !h-4' } }), span('', 'Prix'), ]), numberpicker({ defaultValue: _item.price, disabled: _item.price === undefined, input: (v) => _item.price = v, class: '!w-1/3' }), ]),
|
||||
div('flex flex-row gap-2 items-center justify-between', [ div('flex flex-row gap-2 items-center', [ checkbox({ defaultValue: _item.capacity !== undefined, change: function(value) { _item.capacity = value ? 0 : undefined; if(this.parentElement?.parentElement?.children[1]) { (this.parentElement.parentElement.children[1] as Element & { disabled: boolean }).disabled = !value; (this.parentElement.parentElement.children[1] as HTMLInputElement).value = value ? '0' : ''; } this.parentElement?.toggleAttribute('data-disabled', !value) }, class: { container: '!w-4 !h-4' } }), span('', 'Capacité magique'), ]), numberpicker({ defaultValue: _item.capacity, disabled: _item.capacity === undefined, input: (v) => _item.capacity = v, class: '!w-1/3' }), ]),
|
||||
div('flex flex-row gap-2 items-center justify-between', [ div('flex flex-row gap-2 items-center', [ checkbox({ defaultValue: _item.powercost !== undefined, change: function(value) { _item.powercost = value ? 0 : undefined; if(this.parentElement?.parentElement?.children[1]) { (this.parentElement.parentElement.children[1] as Element & { disabled: boolean }).disabled = !value; (this.parentElement.parentElement.children[1] as HTMLInputElement).value = value ? '0' : ''; } this.parentElement?.toggleAttribute('data-disabled', !value) }, class: { container: '!w-4 !h-4' } }), span('', 'Puissance magique'), ]), numberpicker({ defaultValue: _item.powercost, disabled: _item.powercost === undefined, input: (v) => _item.powercost = v, class: '!w-1/3' }), ]),
|
||||
div('flex flex-row gap-2 items-center justify-between', [ div('flex flex-row gap-2 items-center', [ checkbox({ defaultValue: _item.equippable, change: function(value) { _item.equippable = value; this.parentElement?.toggleAttribute('data-disabled', !value) }, class: { container: '!w-4 !h-4' } }), span('', 'Equipable'), ]), div('flex flex-row gap-2 items-center', [ checkbox({ defaultValue: _item.consummable, change: function(value) { _item.consummable = value; this.parentElement?.toggleAttribute('data-disabled', !value) }, class: { container: '!w-4 !h-4' } }), span('', 'Consommable'), ]) ]),
|
||||
div('flex flex-row gap-2 items-center justify-between', [ div('flex flex-row gap-2 items-center', [ checkbox({ defaultValue: _item.equippable, change: function(value) { _item.equippable = value; this.parentElement?.toggleAttribute('data-disabled', !value) }, class: { container: '!w-4 !h-4' } }), span('', 'Equipable'), ]), div('flex flex-row gap-2 items-center mx-4', [ checkbox({ defaultValue: _item.consummable, change: function(value) { _item.consummable = value; this.parentElement?.toggleAttribute('data-disabled', !value) }, class: { container: '!w-4 !h-4' } }), span('', 'Consommable'), ]) ]),
|
||||
div('flex flex-row gap-2 items-center justify-between', [ div('flex flex-row gap-2 items-center', [ checkbox({ defaultValue: _item.charge !== undefined, change: function(value) { _item.charge = value ? 0 : undefined; if(this.parentElement?.parentElement?.children[1]) { (this.parentElement.parentElement.children[1] as Element & { disabled: boolean }).disabled = !value; (this.parentElement.parentElement.children[1] as HTMLInputElement).value = value ? '0' : ''; } this.parentElement?.toggleAttribute('data-disabled', !value) }, class: { container: '!w-4 !h-4' } }), span('', 'Charges'), ]), numberpicker({ defaultValue: _item.charge, disabled: _item.charge === undefined, input: (v) => _item.charge = 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é'), ]), select(Object.keys(rarityText).map(e => ({ text: rarityText[e as Rarity], value: e as Rarity })), { defaultValue: _item.rarity, change: (v) => _item.rarity = v }), ]) ], { 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 } ),
|
||||
], [ 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é'), ]), select(Object.keys(rarityText).map(e => ({ text: rarityText[e as Rarity], value: e as Rarity })), { defaultValue: _item.rarity, change: (v) => _item.rarity = 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 } ),
|
||||
_item.category === 'armor' ? foldable([
|
||||
div('flex flex-row gap-2 items-center justify-between', [ div('flex flex-row gap-2 items-center', [ span('', 'Type'), ]), select<'light' | 'medium' | 'heavy'>([{ text: 'Armure légère', value: 'light' }, { text: 'Armure moyenne', value: 'medium' }, { text: 'Armure lourde', value: 'heavy' }], { defaultValue: _item.type, change: (v) => _item.type = v, class: { container: '!w-1/2' } }), ]),
|
||||
div('flex flex-row gap-2 items-center justify-between', [ div('flex flex-row gap-2 items-center', [ span('', 'Durabilité'), ]), numberpicker({ defaultValue: _item.health, input: (v) => _item.health = v, class: '!w-1/3' }), ]),
|
||||
div('flex flex-row gap-2 items-center justify-between', [ div('flex flex-row gap-2 items-center', [ span('', 'Absorbtion (fixe)'), ]), numberpicker({ defaultValue: _item.absorb.static, input: (v) => _item.absorb.static = v, class: '!w-1/3' }), ]),
|
||||
div('flex flex-row gap-2 items-center justify-between', [ div('flex flex-row gap-2 items-center', [ span('', 'Absorbtion (%)'), ]), numberpicker({ defaultValue: _item.absorb.percent, input: (v) => _item.absorb.percent = v, class: '!w-1/3' }), ]),
|
||||
], [ 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([
|
||||
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' }), ]),
|
||||
], [ 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('grid grid-cols-2 gap-4 px-2'), ], [ dom('h3', { class: 'text-lg font-bold', text: 'Effets' }),
|
||||
foldable([ effectContainer ], [ dom('h3', { class: 'text-lg font-bold', text: 'Effets' }),
|
||||
tooltip(button(icon('radix-icons:plus', { width: 20, height: 20 }), () => {
|
||||
//this._table.appendChild(this._edit({ id: getID() }));
|
||||
}, 'p-1'), 'Ajouter', 'left'),
|
||||
const f = { id: getID(), };
|
||||
_item.effects ??= [];
|
||||
//@ts-expect-error
|
||||
_item.effects.push(f);
|
||||
effectContainer.appendChild(new FeatureEditor(_item.effects, f.id, true).container);
|
||||
}, 'p-1 hidden group-data-[active]:block'), 'Ajouter', 'left'),
|
||||
], { class: { container: 'flex flex-col gap-2 w-full', title: 'flex flex-row justify-between px-2' } })
|
||||
]);
|
||||
}
|
||||
|
|
@ -734,164 +800,9 @@ export class ItemPanel
|
|||
container.setAttribute('data-state', 'inactive');
|
||||
});
|
||||
}
|
||||
/* private _renderEffect(effect: Partial<FeatureItem>): HTMLDivElement
|
||||
{
|
||||
const content = div('border border-light-30 dark:border-dark-30 col-span-1', [ div('flex justify-between items-center', [
|
||||
div('px-4 flex items-center h-full', [ markdown(textFromEffect(effect), undefined, { tags: { a: preview } }) ]),
|
||||
div('flex', [ tooltip(button(icon('radix-icons:pencil-1'), () => {
|
||||
content.replaceWith(this._edit(effect));
|
||||
}, 'p-2 -m-px hover:z-10 border border-light-35 dark:border-dark-35 hover:border-light-50 dark:hover:border-dark-50'), "Modifieur", "bottom"), tooltip(button(icon('radix-icons:trash'), () => {
|
||||
this._item!.effect = this._item!.effect.filter(e => e.id !== effect.id);
|
||||
content.remove();
|
||||
}, 'p-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") ])
|
||||
]) ]);
|
||||
return content;
|
||||
}
|
||||
private _edit(effect: Partial<FeatureItem>): HTMLDivElement
|
||||
{
|
||||
const match = (effect: FeatureItem): Partial<FeatureItem> | undefined => {
|
||||
switch(effect.category)
|
||||
{
|
||||
case 'value':
|
||||
return flattenFeatureChoices.findLast(e => e.category === 'value' && e.property === effect.property);
|
||||
case 'choice':
|
||||
return flattenFeatureChoices.findLast(e => e.category === 'choice');
|
||||
case 'list':
|
||||
return flattenFeatureChoices.findLast(e => e.category === 'list' && e.list === effect.list);
|
||||
}
|
||||
};
|
||||
const approve = () => {
|
||||
const idx = this._item!.effect.findIndex(e => e.id === _buffer.id);
|
||||
|
||||
if(idx === -1)
|
||||
this._item!.effect.push(_buffer);
|
||||
else
|
||||
this._item!.effect[idx] = _buffer;
|
||||
|
||||
this._table.replaceChild(this._renderEffect(_buffer), content);
|
||||
}, reject = () => {
|
||||
const idx = this._item!.effect.findIndex(e => e.id === _buffer.id);
|
||||
|
||||
if(idx === -1)
|
||||
content.remove();
|
||||
else
|
||||
this._table.replaceChild(this._renderEffect(effect), content);
|
||||
}
|
||||
let _buffer = JSON.parse(JSON.stringify(effect)) as FeatureItem;
|
||||
|
||||
const drawByCategory = (buffer: Partial<FeatureItem>) => {
|
||||
let top: NodeChildren = [], bottom: NodeChildren = [];
|
||||
switch(buffer.category)
|
||||
{
|
||||
case 'value':
|
||||
const valueVariable = () => typeof buffer.value === 'number' ? numberpicker({ defaultValue: buffer.value, input: (value) => { (buffer as FeatureValue).value = value; summaryText.textContent = textFromEffect(buffer); }, class: 'bg-light-25 dark:bg-dark-25 !-m-px hover:z-10 h-[36px] w-[80px]' }) : select<`modifier/${MainStat}` | false>([...Object.entries(mainStatShortTexts).map(e => ({ text: 'Mod. de ' + e[1], value: `modifier/${e[0]}` as `modifier/${MainStat}` })), buffer.operation === 'add' ? undefined : { text: 'Interdit', value: false }], { class: { container: 'w-[160px] bg-light-25 dark:bg-dark-25 !-m-px hover:z-10 h-[36px]' }, defaultValue: buffer.value, change: (value) => { (buffer as FeatureValue).value = value; summaryText.textContent = textFromEffect(buffer); } });
|
||||
const summaryText = text(textFromEffect(buffer));
|
||||
let valueSelection = valueVariable();
|
||||
top = [
|
||||
select([ (['action', 'reaction'].includes(buffer.property ?? '') ? undefined : { text: '+', value: 'add' }), (['speed', 'capacity', 'action', 'reaction'].includes(buffer.property ?? '') || ['defense/'].some(e => (buffer as FeatureValue).property.startsWith(e))) ? { text: '=', value: 'set' } : undefined ], { defaultValue: buffer.operation, change: (value) => { (buffer as FeatureValue).operation = value as 'add' | 'set'; summaryText.textContent = textFromEffect(buffer); }, class: { container: 'bg-light-25 dark:bg-dark-25 !-m-px hover:z-10 h-[36px] w-[80px]' } }),
|
||||
valueSelection,
|
||||
tooltip(button(icon('radix-icons:update'), () => {
|
||||
(buffer as FeatureValue).value = (typeof (buffer as FeatureValue).value === 'number' ? '' as any as false : 0);
|
||||
const newValueSelection = valueVariable();
|
||||
valueSelection.replaceWith(newValueSelection);
|
||||
valueSelection = newValueSelection;
|
||||
summaryText.textContent = textFromEffect(buffer);
|
||||
}, 'px-2 -m-px hover:z-10 border border-light-35 dark:border-dark-35 hover:border-light-50 dark:hover:border-dark-50'), 'Changer d\'editeur', 'bottom'),
|
||||
];
|
||||
bottom = [ div('px-2 py-1 flex items-center flex-1', [summaryText]) ];
|
||||
break;
|
||||
case 'list':
|
||||
top = [ select([ { text: 'Ajouter', value: 'add' }, { text: 'Supprimer', value: 'remove' } ], { defaultValue: buffer.action, change: (value) => {
|
||||
(buffer as FeatureList).action = value as 'add' | 'remove';
|
||||
const element = redraw();
|
||||
content.replaceWith(element);
|
||||
content = element;
|
||||
}, class: { container: 'bg-light-25 dark:bg-dark-25 !-m-px hover:z-10 h-[36px] w-32' } }) ];
|
||||
if(buffer.action === 'add')
|
||||
{
|
||||
if(buffer.list === 'spells')
|
||||
{
|
||||
bottom = [ combobox(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.effect)) ]) ]), value: e.id })), { defaultValue: buffer.item, change: (value) => (buffer as FeatureList).item = value, class: { container: 'bg-light-25 dark:bg-dark-25 hover:z-10 h-[36px] w-full hover:outline-px outline-light-50 dark:outline-dark-50 !border-none' }, fill: 'contain' }) ];
|
||||
}
|
||||
else if(buffer.list)
|
||||
{
|
||||
bottom = [ combobox(Object.values(config[buffer.list]).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 })), { defaultValue: buffer.item, change: (value) => (buffer as FeatureList).item = value, class: { container: 'bg-light-25 dark:bg-dark-25 hover:z-10 h-[36px] w-full hover:outline-px outline-light-50 dark:outline-dark-50 !border-none' }, fill: 'contain' }) ];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
bottom = [ combobox(Object.values(config.features).flatMap(e => e.effect).filter(e => e.category === 'list' && e.list === buffer.list && e.action === 'add').map((e) => (e as FeatureList).list === 'spells' ? config.spells.find(f => f.id === (e as FeatureList).item) : config[(e as FeatureList).list][(e as FeatureList).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 })), { defaultValue: buffer.item, change: (item) => buffer.item = item, class: { container: 'bg-light-25 dark:bg-dark-25 !-m-px hover:z-10 h-[36px] w-full overflow-hidden truncate', option: 'max-h-[90px] text-sm' }, fill: 'contain' }) ];
|
||||
}
|
||||
break;
|
||||
case 'choice':
|
||||
const availableChoices: Option<Partial<FeatureValue | FeatureList>>[] = featureChoices.filter(e => (e?.value as FeatureItem)?.category !== 'choice').map(e => { if(e) e.value = Array.isArray(e.value) ? e.value.filter(f => (f?.value as FeatureItem)?.category !== 'choice') : e.value; return e; }) as Option<Partial<FeatureValue | FeatureList>>[];
|
||||
const addChoice = () => {
|
||||
const choice: { text: string; effects: (Partial<FeatureValue | FeatureList>)[]; } = { effects: [{ id: getID() }], text: '' };
|
||||
(buffer as FeatureChoice).options.push(choice as FeatureChoice["options"][number]);
|
||||
return choice;
|
||||
};
|
||||
const addEffect = (choice: { text: string; effects: (Partial<FeatureValue | FeatureList>)[] }) => {
|
||||
const effect: (Partial<FeatureValue | FeatureList>) = { id: getID() };
|
||||
choice.effects.push(effect);
|
||||
return effect;
|
||||
};
|
||||
const renderEffect = (option: { text: string; effects: (Partial<FeatureValue | FeatureList>)[] }, effect: Partial<FeatureValue | FeatureList>) => {
|
||||
const { top: _top, bottom: _bottom } = drawByCategory(effect);
|
||||
let element = div('border border-light-30 dark:border-dark-30 col-span-2 row-span-2', [ div('flex justify-between items-stretch', [
|
||||
div('flex flex-row flex-1', [
|
||||
combobox(availableChoices, { defaultValue: match(effect as FeatureItem) as Partial<FeatureValue | FeatureList> | undefined, class: { container: 'bg-light-25 dark:bg-dark-25 w-[300px] -m-px hover:z-10 h-[36px]' }, fill: 'cover', change: (e: Partial<FeatureValue | FeatureList>) => {
|
||||
const idx = option.effects.findIndex(e => e === effect);
|
||||
option.effects[idx] = effect = { ...e, id: effect.id };
|
||||
|
||||
const _element = renderEffect(option, effect);
|
||||
element.replaceWith(_element);
|
||||
element = _element;
|
||||
} }),
|
||||
..._top,
|
||||
]),
|
||||
div('flex', [ tooltip(button(icon('radix-icons:trash'), () => { option.effects = option.effects.filter(e => e === effect); element.remove(); }, 'p-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") ])
|
||||
]), div('flex border-t border-light-35 dark:border-dark-35 max-h-[300px] min-h-[36px] overflow-y-auto overflow-x-hidden', _bottom) ]);
|
||||
|
||||
return element;
|
||||
}
|
||||
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)));
|
||||
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();
|
||||
(buffer as FeatureChoice).options = (buffer as FeatureChoice).options.filter(e => e !== 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'), '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 });
|
||||
return _content;
|
||||
}
|
||||
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)) ?? []);
|
||||
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 ];
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
return { top, bottom };
|
||||
}
|
||||
const redraw = () => {
|
||||
const { top, bottom } = drawByCategory(_buffer);
|
||||
return div('border border-light-30 dark:border-dark-30 col-span-2 row-span-2', [ div('flex justify-between items-stretch', [
|
||||
div('flex flex-row flex-1', [
|
||||
combobox(featureChoices, { defaultValue: match(_buffer), class: { container: 'bg-light-25 dark:bg-dark-25 w-[300px] -m-px hover:z-10 h-[36px]' }, fill: 'cover', change: (e) => {
|
||||
_buffer = { id: _buffer.id, ...e } as FeatureItem;
|
||||
const element = redraw();
|
||||
content.replaceWith(element);
|
||||
content = element;
|
||||
} }),
|
||||
...top,
|
||||
]),
|
||||
div('flex', [ tooltip(button(icon('radix-icons:check'), approve, 'p-2 -m-px hover:z-10 border border-light-35 dark:border-dark-35 hover:border-light-50 dark:hover:border-dark-50'), "Valider", "bottom"), tooltip(button(icon('radix-icons:cross-1'), reject, 'p-2 -m-px hover:z-10 border border-light-35 dark:border-dark-35 hover:border-light-50 dark:hover:border-dark-50'), "Annuler", "bottom") ])
|
||||
]), div('flex border-t border-light-35 dark:border-dark-35 max-h-[300px] min-h-[36px] overflow-y-auto overflow-x-hidden', bottom) ]);
|
||||
}
|
||||
|
||||
let content = redraw();
|
||||
return content;
|
||||
} */
|
||||
}
|
||||
|
||||
const featureChoices: Option<Partial<FeatureItem>>[] = [
|
||||
const featureChoices: Option<Partial<FeatureOption>>[] = [
|
||||
{ text: 'PV max', value: { category: 'value', property: 'health', operation: 'add', value: 1 }, },
|
||||
{ text: 'Mana max', value: { category: 'value', property: 'mana', operation: 'add', value: 1 }, },
|
||||
{ text: 'Nombre de sorts maitrisés', value: { category: 'value', property: 'spellslots', operation: 'add', value: 1 }, },
|
||||
|
|
@ -979,8 +890,8 @@ const featureChoices: Option<Partial<FeatureItem>>[] = [
|
|||
{ text: 'Passif', value: { category: 'list', list: 'passive', action: 'add' }, },
|
||||
{ text: 'Choix', value: { category: 'choice', text: '', options: [] }, },
|
||||
];
|
||||
const flattenFeatureChoices = Tree.accumulate(featureChoices, 'value', (item) => Array.isArray(item.value) ? undefined : item.value).filter(e => !!e) as Partial<FeatureItem>[];
|
||||
function textFromEffect(effect: Partial<FeatureItem | FeatureEquipment>): string
|
||||
const flattenFeatureChoices = Tree.accumulate(featureChoices, 'value', (item) => Array.isArray(item.value) ? undefined : item.value).filter(e => !!e) as Partial<FeatureOption>[];
|
||||
function textFromEffect(effect: Partial<FeatureOption>): string
|
||||
{
|
||||
if(effect.category === 'value')
|
||||
{
|
||||
|
|
@ -1000,7 +911,7 @@ function textFromEffect(effect: Partial<FeatureItem | FeatureEquipment>): string
|
|||
case 'speed':
|
||||
return effect.operation === 'add' ? textFromValue(effect.value, { prefix: { positive: '+', text: '+Mod. de ' }, suffix: { truely: ' case(s) de course.' }, falsely: '+0 cases de course' }) : textFromValue(effect.value, { prefix: { truely: 'Vitesse de course de ' }, suffix: { truely: ' case(s).' }, falsely: 'Déplacement impossible.' });
|
||||
case 'capacity':
|
||||
return effect.operation === 'add' ? textFromValue(effect.value, { prefix: { positive: '+', text: '+Mod. de ' }, suffix: { truely: ' unité(s) d\'quipement.' } }) : textFromValue(effect.value, { prefix: { truely: 'Capacité d\'equipement fixé à ' }, suffix: { truely: ' unité(s).' }, falsely: 'Impossible de posséder du materiel.' });
|
||||
return effect.operation === 'add' ? textFromValue(effect.value, { prefix: { positive: '+', text: '+Mod. de ' }, suffix: { truely: ' unité(s) d\'quipement.' } }) : textFromValue(effect.value, { prefix: { truely: 'Capacité d\'équipement fixé à ' }, suffix: { truely: ' unité(s).' }, falsely: 'Impossible de posséder du materiel.' });
|
||||
case 'initiative':
|
||||
return effect.operation === 'add' ? textFromValue(effect.value, { prefix: { positive: '+', text: '+Mod. de ' }, suffix: { truely: ' à l\'itiniative.' } }) : textFromValue(effect.value, { prefix: { truely: 'Initiative fixé à ' }, suffix: { truely: '.' }, falsely: 'Opération interdite (Initiative = interdit).' });
|
||||
case 'training':
|
||||
|
|
@ -1101,13 +1012,13 @@ function textFromEffect(effect: Partial<FeatureItem | FeatureEquipment>): string
|
|||
switch(effect.list)
|
||||
{
|
||||
case 'action':
|
||||
return effect.action === 'add' ? effect.item ? getText((config.action[effect.item]?.description) ?? 'Inconnu') : 'Inconnu' : `Suppression de l'action "${effect.item ? (config.action[effect.item]?.name ?? 'Inconnu') : 'Inconnu'}"`;
|
||||
return effect.action === 'add' ? `Gain de l'action "${effect.item ? (config.action[effect.item]?.name ?? 'Inconnu') : 'Inconnu'}"` : `Suppression de l'action "${effect.item ? (config.action[effect.item]?.name ?? 'Inconnu') : 'Inconnu'}"`;
|
||||
case 'reaction':
|
||||
return effect.action === 'add' ? effect.item ? getText((config.reaction[effect.item]?.description ?? 'Inconnu')) : 'Inconnu' : `Suppression de la réaction "${effect.item ? (config.reaction[effect.item]?.name ?? 'Inconnu') : 'Inconnu'}"`;
|
||||
return effect.action === 'add' ? `Gain de la réaction "${effect.item ? (config.reaction[effect.item]?.name ?? 'Inconnu') : 'Inconnu'}"` : `Suppression de la réaction "${effect.item ? (config.reaction[effect.item]?.name ?? 'Inconnu') : 'Inconnu'}"`;
|
||||
case 'freeaction':
|
||||
return effect.action === 'add' ? effect.item ? getText((config.freeaction[effect.item]?.description ?? 'Inconnu')) : 'Inconnu' : `Suppression de l'action libre "${effect.item ? (config.freeaction[effect.item]?.name ?? 'Inconnu') : 'Inconnu'}"`;
|
||||
return effect.action === 'add' ? `Gain de l'action libre "${effect.item ? (config.freeaction[effect.item]?.name ?? 'Inconnu') : 'Inconnu'}"` : `Suppression de l'action libre "${effect.item ? (config.freeaction[effect.item]?.name ?? 'Inconnu') : 'Inconnu'}"`;
|
||||
case 'passive':
|
||||
return effect.action === 'add' ? effect.item ? getText((config.passive[effect.item]?.description ?? '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':
|
||||
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'}".`;
|
||||
case 'sickness':
|
||||
|
|
|
|||
|
|
@ -145,7 +145,6 @@ export function popper(container: HTMLElement, properties?: PopperProperties)
|
|||
ancestorScroll: false,
|
||||
ancestorResize: false,
|
||||
});
|
||||
console.log("Starting", floater);
|
||||
}
|
||||
state = 'shown';
|
||||
}, properties?.delay ?? 0);
|
||||
|
|
@ -154,7 +153,7 @@ export function popper(container: HTMLElement, properties?: PopperProperties)
|
|||
|
||||
function hide()
|
||||
{
|
||||
if(state !== 'hiding' && state !== 'pinned' && (!properties?.events?.onhide || properties?.events?.onhide() !== false))
|
||||
if(state !== 'hiding' && state !== 'pinned' && (!properties?.events?.onhide || properties?.events?.onhide(state) !== false))
|
||||
{
|
||||
clearTimeout(timeout);
|
||||
state = 'hiding';
|
||||
|
|
@ -162,7 +161,6 @@ export function popper(container: HTMLElement, properties?: PopperProperties)
|
|||
timeout = setTimeout(() => {
|
||||
floater.remove();
|
||||
_stop && _stop();
|
||||
console.log('Stoping', floater);
|
||||
|
||||
floater.setAttribute('data-state', 'closed');
|
||||
floater.classList.toggle('hidden', true);
|
||||
|
|
@ -185,7 +183,6 @@ export function popper(container: HTMLElement, properties?: PopperProperties)
|
|||
ancestorScroll: false,
|
||||
ancestorResize: false,
|
||||
});
|
||||
console.log('Starting', floater);
|
||||
}
|
||||
|
||||
function stop()
|
||||
|
|
@ -193,7 +190,6 @@ export function popper(container: HTMLElement, properties?: PopperProperties)
|
|||
state = 'pinned';
|
||||
floater.toggleAttribute('data-pinned', true);
|
||||
_stop && _stop();
|
||||
console.log('Stoping', floater);
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
|
||||
|
|
@ -206,6 +202,15 @@ export function popper(container: HTMLElement, properties?: PopperProperties)
|
|||
link(floater);
|
||||
|
||||
return { container, content: floater, stop, start, show: () => {
|
||||
if(typeof properties?.content === 'function')
|
||||
properties.content = properties.content();
|
||||
|
||||
if(properties?.content && empty)
|
||||
{
|
||||
content.replaceChildren(...properties!.content.filter(e => !!e));
|
||||
empty = false;
|
||||
}
|
||||
|
||||
if(state !== 'shown')
|
||||
{
|
||||
teleport!.appendChild(floater);
|
||||
|
|
@ -219,7 +224,6 @@ export function popper(container: HTMLElement, properties?: PopperProperties)
|
|||
}, hide: () => {
|
||||
floater.remove();
|
||||
_stop && _stop();
|
||||
console.log('Stoping', floater);
|
||||
|
||||
floater.setAttribute('data-state', 'closed');
|
||||
floater.classList.toggle('hidden', true);
|
||||
|
|
|
|||
|
|
@ -44,11 +44,11 @@ export const a: Prose = {
|
|||
return dom('div', { class: 'w-[600px] h-[600px] group-data-[pinned]:h-full group-data-[pinned]:w-full h-[600px] relative w-[600px] relative' }, [canvas.container]);
|
||||
}
|
||||
return div('');
|
||||
})).current], { position: 'bottom-start', pinned: false, title: properties?.label }) : element;
|
||||
})).current], { position: 'bottom-start', pinned: false, title: properties?.label, href: nav.href }) : element;
|
||||
}
|
||||
}
|
||||
export const preview: Prose = {
|
||||
custom(properties: { href: string, class?: Class, label: string, events? }, children) {
|
||||
custom(properties: { href: string, class?: Class, label: string }, children) {
|
||||
const href = properties.href as string;
|
||||
const { hash, pathname } = parseURL(href);
|
||||
const router = useRouter();
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { MAIN_STATS, ABILITIES, LEVELS, TRAINING_LEVELS, SPELL_TYPES, CATEGORIES, SPELL_ELEMENTS, ALIGNMENTS, RESISTANCES } from "#shared/character.util";
|
||||
import type { MAIN_STATS, ABILITIES, LEVELS, TRAINING_LEVELS, SPELL_TYPES, CATEGORIES, SPELL_ELEMENTS, ALIGNMENTS, RESISTANCES, DAMAGE_TYPES, WEAPON_TYPES } from "#shared/character.util";
|
||||
import type { Localized } from "../types/general";
|
||||
|
||||
export type MainStat = typeof MAIN_STATS[number];
|
||||
|
|
@ -10,6 +10,8 @@ export type Category = typeof CATEGORIES[number];
|
|||
export type SpellElement = typeof SPELL_ELEMENTS[number];
|
||||
export type Alignment = typeof ALIGNMENTS[number];
|
||||
export type Resistance = typeof RESISTANCES[number];
|
||||
export type DamageType = typeof DAMAGE_TYPES[number];
|
||||
export type WeaponType = typeof WEAPON_TYPES[number];
|
||||
|
||||
export type FeatureID = string;
|
||||
export type i18nID = string;
|
||||
|
|
@ -103,8 +105,11 @@ type ArmorConfig = {
|
|||
};
|
||||
type WeaponConfig = {
|
||||
category: 'weapon';
|
||||
type: Array<'classic' | 'light' | 'throw' | 'natural' | 'heavy' | 'twohanded' | 'finesse' | 'reach' | 'projectile' | 'shield'>;
|
||||
damage: string; //Dice formula
|
||||
type: Array<WeaponType>;
|
||||
damage: {
|
||||
value: string; //Dice formula
|
||||
type: DamageType;
|
||||
};
|
||||
};
|
||||
type WondrousConfig = {
|
||||
category: 'wondrous';
|
||||
|
|
@ -120,7 +125,7 @@ export type SpellConfig = {
|
|||
cost: number;
|
||||
speed: "action" | "reaction" | number;
|
||||
elements: Array<SpellElement>;
|
||||
effect: string; //TODO -> TextID
|
||||
description: string; //TODO -> TextID
|
||||
concentration: boolean;
|
||||
range: 'personnal' | number;
|
||||
tags?: string[];
|
||||
|
|
@ -155,7 +160,7 @@ export type FeatureEquipment = {
|
|||
id: FeatureID;
|
||||
category: "value";
|
||||
operation: "add" | "set" | "min";
|
||||
property: 'weapon/damage' | 'armor/health' | 'armor/absorb/flat' | 'armor/absorb/percent';
|
||||
property: 'weapon/damage/value' | 'armor/health' | 'armor/absorb/flat' | 'armor/absorb/percent';
|
||||
value: number | `modifier/${MainStat}` | false;
|
||||
}
|
||||
export type FeatureList = {
|
||||
|
|
|
|||
Loading…
Reference in New Issue