Neo-Tokyo Neon Noir design pour fiches items
- Nouvelle palette : #080c14 fond, accents néon par type (#00d4d4 item, #ff3d5a kungfu, #4a9eff spell, #cc44ff supernatural) - Nouveaux composants LESS : .cde-neon-header (clip-path angulaire + accent line), .cde-avatar (clip-path), .cde-stat-grid/.cde-stat-cell (style terminal), .cde-badge (parallélogramme), .cde-neon-tabs (underline néon animé), .cde-check-cell - Fix layout : .cde-sheet width: 100% + height: 100% + overflow: hidden, .cde-tab-body flex: 1 + min-height: 0, .cde-notes-editor flex stretch - Fix positions : DEFAULT_OPTIONS height explicite pour tous les types (item 620x580, spell 660x680, kungfu 720x680, supernatural 560x520) - 4 templates items reécrits avec nouvelles classes et structure épurée Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
104
src/config/constants.js
Normal file
104
src/config/constants.js
Normal file
@@ -0,0 +1,104 @@
|
||||
export const SYSTEM_ID = "fvtt-chroniques-de-l-etrange"
|
||||
|
||||
export const ACTOR_TYPES = {
|
||||
character: "character",
|
||||
npc: "npc",
|
||||
tinji: "tinji",
|
||||
loksyu: "loksyu",
|
||||
}
|
||||
|
||||
export const ITEM_TYPES = {
|
||||
item: "item",
|
||||
kungfu: "kungfu",
|
||||
spell: "spell",
|
||||
supernatural: "supernatural",
|
||||
}
|
||||
|
||||
export const SUBTYPES = {
|
||||
weapon: { id: "weapon", label: "CDE.Weapon" },
|
||||
armor: { id: "armor", label: "CDE.Armor" },
|
||||
sanhei: { id: "sanhei", label: "CDE.Sanhei" },
|
||||
other: { id: "other", label: "CDE.Other" },
|
||||
}
|
||||
|
||||
export const MAGICS = {
|
||||
internalcinnabar: {
|
||||
id: "internalcinnabar",
|
||||
background: "linear-grey",
|
||||
label: "CDE.InternalCinnabar",
|
||||
aspectlabel: "CDE.Metal",
|
||||
speciality: {
|
||||
essence: { label: "CDE.Essence", classicon: "icon-yin", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yin.png", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_metal.png", labelicon: "Yin", labelelement: "CDE.Metal" },
|
||||
mind: { label: "CDE.Mind", classicon: "icon-yin", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yin.png", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_eau.png", labelicon: "Yin", labelelement: "CDE.Water" },
|
||||
purification: { label: "CDE.Purification", classicon: "icon-yinyang", icon: "/systems/fvtt-chroniques-de-l-etrange/images/yin_yang.png", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_terre.png", labelicon: "Yin/Yang", labelelement: "CDE.Earth" },
|
||||
manipulation: { label: "CDE.Manipulation", classicon: "icon-yang", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yang.png", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_feu.png", labelicon: "Yang", labelelement: "CDE.Fire" },
|
||||
aura: { label: "CDE.Aura", classicon: "icon-yang", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yang.png", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_bois.png", labelicon: "Yang", labelelement: "CDE.Wood" },
|
||||
},
|
||||
},
|
||||
alchemy: {
|
||||
id: "alchemy",
|
||||
background: "linear-blue",
|
||||
label: "CDE.Alchemy",
|
||||
aspectlabel: "CDE.Water",
|
||||
speciality: {
|
||||
acupuncture: { label: "CDE.Acupuncture", classicon: "icon-yin", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yin.png", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_metal.png", labelicon: "Yin", labelelement: "CDE.Metal" },
|
||||
elixirs: { label: "CDE.Elixirs", classicon: "icon-yin", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yin.png", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_eau.png", labelicon: "Yin", labelelement: "CDE.Water" },
|
||||
poisons: { label: "CDE.Poisons", classicon: "icon-yinyang", icon: "/systems/fvtt-chroniques-de-l-etrange/images/yin_yang.png", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_terre.png", labelicon: "Yin/Yang", labelelement: "CDE.Earth" },
|
||||
arsenal: { label: "CDE.Arsenal", classicon: "icon-yang", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yang.png", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_feu.png", labelicon: "Yang", labelelement: "CDE.Fire" },
|
||||
potions: { label: "CDE.Potions", classicon: "icon-yang", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yang.png", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_bois.png", labelicon: "Yang", labelelement: "CDE.Wood" },
|
||||
},
|
||||
},
|
||||
masteryoftheway: {
|
||||
id: "masteryoftheway",
|
||||
background: "linear-brown",
|
||||
label: "CDE.MasteryOfTheWay",
|
||||
aspectlabel: "CDE.Earth",
|
||||
speciality: {
|
||||
curse: { label: "CDE.Curse", classicon: "icon-yin", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yin.png", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_metal.png", labelicon: "Yin", labelelement: "CDE.Metal" },
|
||||
transfiguration: { label: "CDE.Transfiguration", classicon: "icon-yin", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yin.png", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_eau.png", labelicon: "Yin", labelelement: "CDE.Water" },
|
||||
necromancy: { label: "CDE.Necromancy", classicon: "icon-yinyang", icon: "/systems/fvtt-chroniques-de-l-etrange/images/yin_yang.png", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_terre.png", labelicon: "Yin/Yang", labelelement: "CDE.Earth" },
|
||||
climatecontrol: { label: "CDE.ClimateControl", classicon: "icon-yang", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yang.png", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_feu.png", labelicon: "Yang", labelelement: "CDE.Fire" },
|
||||
goldenmagic: { label: "CDE.GoldenMagic", classicon: "icon-yang", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yang.png", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_bois.png", labelicon: "Yang", labelelement: "CDE.Wood" },
|
||||
},
|
||||
},
|
||||
exorcism: {
|
||||
id: "exorcism",
|
||||
background: "linear-red",
|
||||
label: "CDE.Exorcism",
|
||||
aspectlabel: "CDE.Fire",
|
||||
speciality: {
|
||||
invocation: { label: "CDE.Invocation", classicon: "icon-yin", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yin.png", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_metal.png", labelicon: "Yin", labelelement: "CDE.Metal" },
|
||||
tracking: { label: "CDE.Tracking", classicon: "icon-yin", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yin.png", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_eau.png", labelicon: "Yin", labelelement: "CDE.Water" },
|
||||
protection: { label: "CDE.Protection", classicon: "icon-yinyang", icon: "/systems/fvtt-chroniques-de-l-etrange/images/yin_yang.png", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_terre.png", labelicon: "Yin/Yang", labelelement: "CDE.Earth" },
|
||||
punishment: { label: "CDE.Punishment", classicon: "icon-yang", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yang.png", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_feu.png", labelicon: "Yang", labelelement: "CDE.Fire" },
|
||||
domination: { label: "CDE.Domination", classicon: "icon-yang", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yang.png", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_bois.png", labelicon: "Yang", labelelement: "CDE.Wood" },
|
||||
},
|
||||
},
|
||||
geomancy: {
|
||||
id: "geomancy",
|
||||
background: "linear-green",
|
||||
label: "CDE.Geomancy",
|
||||
aspectlabel: "CDE.Wood",
|
||||
speciality: {
|
||||
neutralization: { label: "CDE.Neutralization", classicon: "icon-yin", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yin.png", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_metal.png", labelicon: "Yin", labelelement: "CDE.Metal" },
|
||||
divination: { label: "CDE.Divination", classicon: "icon-yin", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yin.png", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_eau.png", labelicon: "Yin", labelelement: "CDE.Water" },
|
||||
earthlyprayer: { label: "CDE.EarthlyPrayer", classicon: "icon-yinyang", icon: "/systems/fvtt-chroniques-de-l-etrange/images/yin_yang.png", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_terre.png", labelicon: "Yin/Yang", labelelement: "CDE.Earth" },
|
||||
heavenlyprayer: { label: "CDE.HeavenlyPrayer", classicon: "icon-yang", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yang.png", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_feu.png", labelicon: "Yang", labelelement: "CDE.Fire" },
|
||||
fungseoi: { label: "CDE.Fungseoi", classicon: "icon-yang", icon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_yang.png", elementicon: "/systems/fvtt-chroniques-de-l-etrange/images/cde_bois.png", labelicon: "Yang", labelelement: "CDE.Wood" },
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const TEMPLATE_PARTIALS = [
|
||||
"systems/fvtt-chroniques-de-l-etrange/templates/actor/parts/cde-character-skills.html",
|
||||
"systems/fvtt-chroniques-de-l-etrange/templates/actor/parts/cde-character-magics.html",
|
||||
"systems/fvtt-chroniques-de-l-etrange/templates/actor/parts/cde-character-nghang.html",
|
||||
"systems/fvtt-chroniques-de-l-etrange/templates/actor/parts/cde-character-treasures.html",
|
||||
"systems/fvtt-chroniques-de-l-etrange/templates/actor/parts/cde-character-items.html",
|
||||
"systems/fvtt-chroniques-de-l-etrange/templates/actor/parts/cde-character-kungfus.html",
|
||||
"systems/fvtt-chroniques-de-l-etrange/templates/actor/parts/cde-character-spells.html",
|
||||
"systems/fvtt-chroniques-de-l-etrange/templates/actor/parts/cde-npc-supernaturals.html",
|
||||
"systems/fvtt-chroniques-de-l-etrange/templates/actor/parts/cde-npc-spells.html",
|
||||
"systems/fvtt-chroniques-de-l-etrange/templates/actor/parts/cde-npc-kungfus.html",
|
||||
"systems/fvtt-chroniques-de-l-etrange/templates/actor/parts/cde-npc-items.html",
|
||||
]
|
||||
21
src/config/localize.js
Normal file
21
src/config/localize.js
Normal file
@@ -0,0 +1,21 @@
|
||||
import { MAGICS, SUBTYPES } from "./constants.js"
|
||||
|
||||
export function preLocalizeConfig() {
|
||||
const localizeConfigObject = (obj, keys) => {
|
||||
for (const o of Object.values(obj)) {
|
||||
for (const key of keys) {
|
||||
o[key] = game.i18n.localize(o[key])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
localizeConfigObject(SUBTYPES, ["label"])
|
||||
Object.values(MAGICS).forEach((magic) => {
|
||||
magic.label = game.i18n.localize(magic.label)
|
||||
magic.aspectlabel = game.i18n.localize(magic.aspectlabel)
|
||||
Object.values(magic.speciality).forEach((spec) => {
|
||||
spec.label = game.i18n.localize(spec.label)
|
||||
spec.labelelement = game.i18n.localize(spec.labelelement)
|
||||
})
|
||||
})
|
||||
}
|
||||
11
src/config/runtime.js
Normal file
11
src/config/runtime.js
Normal file
@@ -0,0 +1,11 @@
|
||||
export function configureRuntime() {
|
||||
CONFIG.Actor.compendiumBanner = "/systems/fvtt-chroniques-de-l-etrange/images/banners/actor-banner.webp"
|
||||
CONFIG.Adventure.compendiumBanner = "/systems/fvtt-chroniques-de-l-etrange/images/banners/adventure-banner.webp"
|
||||
CONFIG.Cards.compendiumBanner = "ui/banners/cards-banner.webp"
|
||||
CONFIG.Item.compendiumBanner = "/systems/fvtt-chroniques-de-l-etrange/images/banners/item-banner.webp"
|
||||
CONFIG.JournalEntry.compendiumBanner = "/systems/fvtt-chroniques-de-l-etrange/images/banners/journalentry-banner.webp"
|
||||
CONFIG.Macro.compendiumBanner = "ui/banners/macro-banner.webp"
|
||||
CONFIG.Playlist.compendiumBanner = "ui/banners/playlist-banner.webp"
|
||||
CONFIG.RollTable.compendiumBanner = "ui/banners/rolltable-banner.webp"
|
||||
CONFIG.Scene.compendiumBanner = "/systems/fvtt-chroniques-de-l-etrange/images/banners/scene-banner.webp"
|
||||
}
|
||||
178
src/data/actors/character.js
Normal file
178
src/data/actors/character.js
Normal file
@@ -0,0 +1,178 @@
|
||||
export default class CharacterDataModel extends foundry.abstract.TypeDataModel {
|
||||
static defineSchema() {
|
||||
const { fields } = foundry.data
|
||||
const numberField = (initial = 0, extra = {}) => new fields.NumberField({ required: true, nullable: false, integer: true, initial, ...extra })
|
||||
const stringField = (initial = "") => new fields.StringField({ required: true, nullable: false, initial })
|
||||
const boolField = (initial = false) => new fields.BooleanField({ required: true, initial })
|
||||
const htmlField = (initial = "") => new fields.HTMLField({ required: true, nullable: false, initial, textSearch: true })
|
||||
|
||||
const aspectField = (label, chinese) =>
|
||||
new fields.SchemaField({
|
||||
chinese: stringField(chinese),
|
||||
label: stringField(label),
|
||||
value: numberField(15, { min: 0 }),
|
||||
})
|
||||
|
||||
const skillField = (label) =>
|
||||
new fields.SchemaField({
|
||||
label: stringField(label),
|
||||
specialities: stringField(""),
|
||||
value: numberField(0, { min: 0 }),
|
||||
})
|
||||
|
||||
const resourceField = (label) =>
|
||||
new fields.SchemaField({
|
||||
label: stringField(label),
|
||||
specialities: stringField(""),
|
||||
value: numberField(0, { min: 0 }),
|
||||
debt: boolField(false),
|
||||
})
|
||||
|
||||
const componentField = () =>
|
||||
new fields.SchemaField({
|
||||
value: stringField(""),
|
||||
})
|
||||
|
||||
const magicSpecialityField = () =>
|
||||
new fields.SchemaField({
|
||||
check: boolField(false),
|
||||
})
|
||||
|
||||
const magicField = () =>
|
||||
new fields.SchemaField({
|
||||
visible: boolField(true),
|
||||
value: numberField(0, { min: 0 }),
|
||||
speciality: new fields.SchemaField({
|
||||
essence: magicSpecialityField(),
|
||||
mind: magicSpecialityField(),
|
||||
purification: magicSpecialityField(),
|
||||
manipulation: magicSpecialityField(),
|
||||
aura: magicSpecialityField(),
|
||||
acupuncture: magicSpecialityField(),
|
||||
elixirs: magicSpecialityField(),
|
||||
poisons: magicSpecialityField(),
|
||||
arsenal: magicSpecialityField(),
|
||||
potions: magicSpecialityField(),
|
||||
curse: magicSpecialityField(),
|
||||
transfiguration: magicSpecialityField(),
|
||||
necromancy: magicSpecialityField(),
|
||||
climatecontrol: magicSpecialityField(),
|
||||
goldenmagic: magicSpecialityField(),
|
||||
invocation: magicSpecialityField(),
|
||||
tracking: magicSpecialityField(),
|
||||
protection: magicSpecialityField(),
|
||||
punishment: magicSpecialityField(),
|
||||
domination: magicSpecialityField(),
|
||||
neutralization: magicSpecialityField(),
|
||||
divination: magicSpecialityField(),
|
||||
earthlyprayer: magicSpecialityField(),
|
||||
heavenlyprayer: magicSpecialityField(),
|
||||
fungseoi: magicSpecialityField(),
|
||||
}),
|
||||
})
|
||||
|
||||
const treasureBranch = () =>
|
||||
new fields.SchemaField({
|
||||
value: numberField(0, { min: 0 }),
|
||||
max: numberField(0, { min: 0 }),
|
||||
min: numberField(0, { min: 0 }),
|
||||
})
|
||||
|
||||
const treasureLevel = () =>
|
||||
new fields.SchemaField({
|
||||
san: treasureBranch(),
|
||||
zing: treasureBranch(),
|
||||
})
|
||||
|
||||
const schema = {
|
||||
concept: stringField(""),
|
||||
guardian: stringField("0"),
|
||||
initiative: numberField(1, { min: 0 }),
|
||||
anti_initiative: numberField(24, { min: 0 }),
|
||||
description: htmlField(""),
|
||||
prefs: new fields.SchemaField({
|
||||
typeofthrow: new fields.SchemaField({
|
||||
check: boolField(true),
|
||||
choice: stringField("0"),
|
||||
}),
|
||||
}),
|
||||
prompt: new fields.SchemaField({
|
||||
typeofthrow: new fields.SchemaField({
|
||||
check: boolField(true),
|
||||
choice: stringField("0"),
|
||||
}),
|
||||
configure: new fields.SchemaField({
|
||||
numberofdice: numberField(0),
|
||||
aspect: numberField(0),
|
||||
bonus: numberField(0),
|
||||
bonusauspiciousdice: numberField(0),
|
||||
typeofthrow: numberField(0),
|
||||
aspectskill: numberField(0),
|
||||
bonusmalusskill: numberField(0),
|
||||
aspectspeciality: numberField(0),
|
||||
rolldifficulty: numberField(0),
|
||||
bonusmalusspeciality: numberField(0),
|
||||
}),
|
||||
}),
|
||||
aspect: new fields.SchemaField({
|
||||
fire: aspectField("CDE.Fire", "㊋"),
|
||||
earth: aspectField("CDE.Earth", "㊏"),
|
||||
metal: aspectField("CDE.Metal", "㊎"),
|
||||
water: aspectField("CDE.Water", "㊌"),
|
||||
wood: aspectField("CDE.Wood", "㊍"),
|
||||
}),
|
||||
skills: new fields.SchemaField({
|
||||
art: skillField("CDE.Art"),
|
||||
investigation: skillField("CDE.Investigation"),
|
||||
erudition: skillField("CDE.Erudition"),
|
||||
knavery: skillField("CDE.Knavery"),
|
||||
wordliness: skillField("CDE.Wordliness"),
|
||||
prowess: skillField("CDE.Prowess"),
|
||||
sciences: skillField("CDE.Sciences"),
|
||||
technologies: skillField("CDE.Technologies"),
|
||||
kungfu: skillField("CDE.KungFu"),
|
||||
rangedcombat: skillField("CDE.RangedCombat"),
|
||||
}),
|
||||
resources: new fields.SchemaField({
|
||||
supply: resourceField("CDE.Supply"),
|
||||
inquiry: resourceField("CDE.Inquiry"),
|
||||
influence: resourceField("CDE.Influence"),
|
||||
}),
|
||||
component: new fields.SchemaField({
|
||||
one: componentField(),
|
||||
two: componentField(),
|
||||
three: componentField(),
|
||||
four: componentField(),
|
||||
five: componentField(),
|
||||
six: componentField(),
|
||||
seven: componentField(),
|
||||
eight: componentField(),
|
||||
nine: componentField(),
|
||||
zero: componentField(),
|
||||
}),
|
||||
magics: new fields.SchemaField({
|
||||
internalcinnabar: magicField(),
|
||||
alchemy: magicField(),
|
||||
masteryoftheway: magicField(),
|
||||
exorcism: magicField(),
|
||||
geomancy: magicField(),
|
||||
}),
|
||||
threetreasures: new fields.SchemaField({
|
||||
heiyang: new fields.SchemaField({ value: numberField(0, { min: 0 }) }),
|
||||
heiyin: new fields.SchemaField({ value: numberField(0, { min: 0 }) }),
|
||||
dicelevel: new fields.SchemaField({
|
||||
level0d: treasureLevel(),
|
||||
level1d: treasureLevel(),
|
||||
level2d: treasureLevel(),
|
||||
}),
|
||||
}),
|
||||
experience: new fields.SchemaField({
|
||||
value: numberField(0, { min: 0 }),
|
||||
max: numberField(0, { min: 0 }),
|
||||
min: numberField(0, { min: 0 }),
|
||||
}),
|
||||
}
|
||||
|
||||
return schema
|
||||
}
|
||||
}
|
||||
4
src/data/actors/index.js
Normal file
4
src/data/actors/index.js
Normal file
@@ -0,0 +1,4 @@
|
||||
export { default as CharacterDataModel } from "./character.js"
|
||||
export { default as NpcDataModel } from "./npc.js"
|
||||
export { default as TinjiDataModel } from "./tinji.js"
|
||||
export { default as LoksyuDataModel } from "./loksyu.js"
|
||||
22
src/data/actors/loksyu.js
Normal file
22
src/data/actors/loksyu.js
Normal file
@@ -0,0 +1,22 @@
|
||||
export default class LoksyuDataModel extends foundry.abstract.TypeDataModel {
|
||||
static defineSchema() {
|
||||
const { fields } = foundry.data
|
||||
const numberField = (initial = 0, extra = {}) => new fields.NumberField({ required: true, nullable: false, integer: true, initial, ...extra })
|
||||
const htmlField = (initial = "") => new fields.HTMLField({ required: true, nullable: false, initial, textSearch: true })
|
||||
|
||||
const polarity = () =>
|
||||
new fields.SchemaField({
|
||||
yin: new fields.SchemaField({ value: numberField(0, { min: 0 }) }),
|
||||
yang: new fields.SchemaField({ value: numberField(0, { min: 0 }) }),
|
||||
})
|
||||
|
||||
return {
|
||||
fire: polarity(),
|
||||
earth: polarity(),
|
||||
metal: polarity(),
|
||||
water: polarity(),
|
||||
wood: polarity(),
|
||||
description: htmlField(""),
|
||||
}
|
||||
}
|
||||
}
|
||||
46
src/data/actors/npc.js
Normal file
46
src/data/actors/npc.js
Normal file
@@ -0,0 +1,46 @@
|
||||
export default class NpcDataModel extends foundry.abstract.TypeDataModel {
|
||||
static defineSchema() {
|
||||
const { fields } = foundry.data
|
||||
const numberField = (initial = 0, extra = {}) => new fields.NumberField({ required: true, nullable: false, integer: true, initial, ...extra })
|
||||
const stringField = (initial = "") => new fields.StringField({ required: true, nullable: false, initial })
|
||||
const boolField = (initial = false) => new fields.BooleanField({ required: true, initial })
|
||||
const htmlField = (initial = "") => new fields.HTMLField({ required: true, nullable: false, initial, textSearch: true })
|
||||
|
||||
const aptitudeField = () =>
|
||||
new fields.SchemaField({
|
||||
value: numberField(0, { min: 0 }),
|
||||
speciality: stringField(""),
|
||||
})
|
||||
|
||||
const trackedField = () =>
|
||||
new fields.SchemaField({
|
||||
value: numberField(0, { min: 0 }),
|
||||
calcul: numberField(0, { min: 0 }),
|
||||
note: stringField(""),
|
||||
})
|
||||
|
||||
return {
|
||||
type: stringField(""),
|
||||
levelofthreat: numberField(0, { min: 0 }),
|
||||
powerofnuisance: numberField(0, { min: 0 }),
|
||||
initiative: numberField(1, { min: 0 }),
|
||||
anti_initiative: numberField(24, { min: 0 }),
|
||||
aptitudes: new fields.SchemaField({
|
||||
physical: aptitudeField(),
|
||||
martial: aptitudeField(),
|
||||
mental: aptitudeField(),
|
||||
social: aptitudeField(),
|
||||
spiritual: aptitudeField(),
|
||||
}),
|
||||
vitality: trackedField(),
|
||||
hei: trackedField(),
|
||||
description: htmlField(""),
|
||||
prefs: new fields.SchemaField({
|
||||
typeofthrow: new fields.SchemaField({
|
||||
check: boolField(false),
|
||||
choice: stringField("0"),
|
||||
}),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
12
src/data/actors/tinji.js
Normal file
12
src/data/actors/tinji.js
Normal file
@@ -0,0 +1,12 @@
|
||||
export default class TinjiDataModel extends foundry.abstract.TypeDataModel {
|
||||
static defineSchema() {
|
||||
const { fields } = foundry.data
|
||||
const numberField = (initial = 0, extra = {}) => new fields.NumberField({ required: true, nullable: false, integer: true, initial, ...extra })
|
||||
const htmlField = (initial = "") => new fields.HTMLField({ required: true, nullable: false, initial, textSearch: true })
|
||||
|
||||
return {
|
||||
value: numberField(0, { min: 0 }),
|
||||
description: htmlField(""),
|
||||
}
|
||||
}
|
||||
}
|
||||
4
src/data/items/index.js
Normal file
4
src/data/items/index.js
Normal file
@@ -0,0 +1,4 @@
|
||||
export { default as EquipmentDataModel } from "./item.js"
|
||||
export { default as KungfuDataModel } from "./kungfu.js"
|
||||
export { default as SpellDataModel } from "./spell.js"
|
||||
export { default as SupernaturalDataModel } from "./supernatural.js"
|
||||
20
src/data/items/item.js
Normal file
20
src/data/items/item.js
Normal file
@@ -0,0 +1,20 @@
|
||||
export default class EquipmentDataModel extends foundry.abstract.TypeDataModel {
|
||||
static defineSchema() {
|
||||
const { fields } = foundry.data
|
||||
const numberField = (initial = 0, extra = {}) => new fields.NumberField({ required: true, nullable: false, integer: true, initial, ...extra })
|
||||
const stringField = (initial = "") => new fields.StringField({ required: true, nullable: false, initial })
|
||||
const htmlField = (initial = "") => new fields.HTMLField({ required: true, nullable: false, initial, textSearch: true })
|
||||
|
||||
return {
|
||||
subtype: stringField(""),
|
||||
reference: stringField(""),
|
||||
description: htmlField(""),
|
||||
quantity: numberField(1, { min: 0 }),
|
||||
weight: numberField(0, { min: 0 }),
|
||||
protection: stringField(""),
|
||||
damage: stringField(""),
|
||||
range: stringField(""),
|
||||
notes: htmlField(""),
|
||||
}
|
||||
}
|
||||
}
|
||||
32
src/data/items/kungfu.js
Normal file
32
src/data/items/kungfu.js
Normal file
@@ -0,0 +1,32 @@
|
||||
export default class KungfuDataModel extends foundry.abstract.TypeDataModel {
|
||||
static defineSchema() {
|
||||
const { fields } = foundry.data
|
||||
const stringField = (initial = "") => new fields.StringField({ required: true, nullable: false, initial })
|
||||
const htmlField = (initial = "") => new fields.HTMLField({ required: true, nullable: false, initial, textSearch: true })
|
||||
const boolField = (initial = false) => new fields.BooleanField({ required: true, initial })
|
||||
|
||||
const techniqueField = () =>
|
||||
new fields.SchemaField({
|
||||
check: boolField(false),
|
||||
name: stringField(""),
|
||||
activation: stringField(""),
|
||||
technique: htmlField(""),
|
||||
})
|
||||
|
||||
return {
|
||||
reference: stringField(""),
|
||||
description: htmlField(""),
|
||||
orientation: stringField(""),
|
||||
aspect: stringField(""),
|
||||
skill: stringField(""),
|
||||
speciality: stringField(""),
|
||||
style: stringField(""),
|
||||
techniques: new fields.SchemaField({
|
||||
technique1: techniqueField(),
|
||||
technique2: techniqueField(),
|
||||
technique3: techniqueField(),
|
||||
}),
|
||||
notes: htmlField(""),
|
||||
}
|
||||
}
|
||||
}
|
||||
22
src/data/items/spell.js
Normal file
22
src/data/items/spell.js
Normal file
@@ -0,0 +1,22 @@
|
||||
export default class SpellDataModel extends foundry.abstract.TypeDataModel {
|
||||
static defineSchema() {
|
||||
const { fields } = foundry.data
|
||||
const stringField = (initial = "") => new fields.StringField({ required: true, nullable: false, initial })
|
||||
const htmlField = (initial = "") => new fields.HTMLField({ required: true, nullable: false, initial, textSearch: true })
|
||||
|
||||
return {
|
||||
reference: stringField(""),
|
||||
description: htmlField(""),
|
||||
specialityname: stringField(""),
|
||||
associatedelement: stringField(""),
|
||||
hei: stringField(""),
|
||||
realizationtimeritual: stringField(""),
|
||||
realizationtimeaccelerated: stringField(""),
|
||||
flashback: stringField(""),
|
||||
components: htmlField(""),
|
||||
effects: htmlField(""),
|
||||
examples: htmlField(""),
|
||||
notes: htmlField(""),
|
||||
}
|
||||
}
|
||||
}
|
||||
13
src/data/items/supernatural.js
Normal file
13
src/data/items/supernatural.js
Normal file
@@ -0,0 +1,13 @@
|
||||
export default class SupernaturalDataModel extends foundry.abstract.TypeDataModel {
|
||||
static defineSchema() {
|
||||
const { fields } = foundry.data
|
||||
const stringField = (initial = "") => new fields.StringField({ required: true, nullable: false, initial })
|
||||
const htmlField = (initial = "") => new fields.HTMLField({ required: true, nullable: false, initial, textSearch: true })
|
||||
|
||||
return {
|
||||
reference: stringField(""),
|
||||
description: htmlField(""),
|
||||
notes: htmlField(""),
|
||||
}
|
||||
}
|
||||
}
|
||||
22
src/documents/actor.js
Normal file
22
src/documents/actor.js
Normal file
@@ -0,0 +1,22 @@
|
||||
import { ACTOR_TYPES } from "../config/constants.js"
|
||||
|
||||
export class CDEActor extends Actor {
|
||||
getRollData() {
|
||||
const data = this.toObject(false).system
|
||||
return data
|
||||
}
|
||||
|
||||
prepareBaseData() {
|
||||
super.prepareBaseData()
|
||||
|
||||
if (this.type === ACTOR_TYPES.character) {
|
||||
this.system.anti_initiative = 25 - (this.system.initiative ?? 0)
|
||||
}
|
||||
|
||||
if (this.type === ACTOR_TYPES.npc) {
|
||||
this.system.vitality.calcul = (this.system.aptitudes.physical.value ?? 0) * 4
|
||||
this.system.hei.calcul = (this.system.aptitudes.spiritual.value ?? 0) * 4
|
||||
this.system.anti_initiative = 25 - (this.system.initiative ?? 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
47
src/documents/chat-message.js
Normal file
47
src/documents/chat-message.js
Normal file
@@ -0,0 +1,47 @@
|
||||
export class CDEMessage extends ChatMessage {
|
||||
async renderHTML({ canDelete, canClose = false, ...rest } = {}) {
|
||||
const html = await super.renderHTML({ canDelete, canClose, ...rest })
|
||||
this.#enrichChatCard(html)
|
||||
return html
|
||||
}
|
||||
|
||||
getAssociatedActor() {
|
||||
if (this.speaker.scene && this.speaker.token) {
|
||||
const scene = game.scenes.get(this.speaker.scene)
|
||||
const token = scene?.tokens.get(this.speaker.token)
|
||||
if (token) return token.actor
|
||||
}
|
||||
return game.actors.get(this.speaker.actor)
|
||||
}
|
||||
|
||||
#enrichChatCard(html) {
|
||||
const actor = this.getAssociatedActor()
|
||||
|
||||
let img
|
||||
let nameText
|
||||
if (this.isContentVisible) {
|
||||
img = actor?.img ?? this.author.avatar
|
||||
nameText = this.alias
|
||||
} else {
|
||||
img = this.author.avatar
|
||||
nameText = this.author.name
|
||||
}
|
||||
|
||||
const avatar = document.createElement("a")
|
||||
avatar.classList.add("avatar")
|
||||
if (actor) avatar.dataset.uuid = actor.uuid
|
||||
const avatarImg = document.createElement("img")
|
||||
Object.assign(avatarImg, { src: img, alt: nameText })
|
||||
avatar.append(avatarImg)
|
||||
|
||||
const name = document.createElement("span")
|
||||
name.classList.add("name-stacked")
|
||||
const title = document.createElement("span")
|
||||
title.classList.add("title")
|
||||
title.append(nameText)
|
||||
name.append(title)
|
||||
|
||||
const sender = html.querySelector(".message-sender")
|
||||
sender?.replaceChildren(avatar, name)
|
||||
}
|
||||
}
|
||||
17
src/documents/item.js
Normal file
17
src/documents/item.js
Normal file
@@ -0,0 +1,17 @@
|
||||
export class CDEItem extends Item {
|
||||
get isWeapon() {
|
||||
return this.system.subtype === "weapon"
|
||||
}
|
||||
|
||||
get isArmor() {
|
||||
return this.system.subtype === "armor"
|
||||
}
|
||||
|
||||
get isSanhei() {
|
||||
return this.system.subtype === "sanhei"
|
||||
}
|
||||
|
||||
get isOther() {
|
||||
return this.system.subtype === "other"
|
||||
}
|
||||
}
|
||||
105
src/migration.js
Normal file
105
src/migration.js
Normal file
@@ -0,0 +1,105 @@
|
||||
import { SYSTEM_ID } from "./config/constants.js"
|
||||
|
||||
const MIGRATION_VERSION = "3.0.0"
|
||||
|
||||
export function registerSettings() {
|
||||
game.settings.register(SYSTEM_ID, "migrationVersion", {
|
||||
name: "Migration version",
|
||||
scope: "world",
|
||||
config: false,
|
||||
type: String,
|
||||
default: "0.0.0",
|
||||
})
|
||||
}
|
||||
|
||||
export async function migrateIfNeeded() {
|
||||
const current = game.system.version ?? MIGRATION_VERSION
|
||||
const stored = game.settings.get(SYSTEM_ID, "migrationVersion") ?? "0.0.0"
|
||||
if (!isNewerVersion(current, stored)) return
|
||||
|
||||
ui.notifications.info(`CHRONIQUESDELETRANGE | Migration vers ${current} en cours...`, { permanent: true })
|
||||
await migrateActors()
|
||||
await migrateItems()
|
||||
await migrateCompendiumActors()
|
||||
await migrateCompendiumItems()
|
||||
await game.settings.set(SYSTEM_ID, "migrationVersion", current)
|
||||
ui.notifications.info(`CHRONIQUESDELETRANGE | Migration vers ${current} terminée.`)
|
||||
}
|
||||
|
||||
async function migrateActors() {
|
||||
const updates = []
|
||||
for (const actor of game.actors.contents) {
|
||||
const updateData = migrateActorData(actor)
|
||||
if (Object.keys(updateData).length > 0) {
|
||||
updates.push(actor.update(updateData, { enforceTypes: false }))
|
||||
}
|
||||
}
|
||||
await Promise.all(updates)
|
||||
}
|
||||
|
||||
async function migrateCompendiumActors() {
|
||||
const packs = game.packs.filter((p) => p.documentName === "Actor" && p.metadata.system === SYSTEM_ID)
|
||||
for (const pack of packs) {
|
||||
const content = await pack.getDocuments()
|
||||
for (const actor of content) {
|
||||
const updateData = migrateActorData(actor)
|
||||
if (Object.keys(updateData).length > 0) {
|
||||
await actor.update(updateData, { pack: pack.collection, enforceTypes: false })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function migrateItems() {
|
||||
const updates = []
|
||||
for (const item of game.items.contents) {
|
||||
const updateData = migrateItemData(item)
|
||||
if (Object.keys(updateData).length > 0) {
|
||||
updates.push(item.update(updateData, { enforceTypes: false }))
|
||||
}
|
||||
}
|
||||
await Promise.all(updates)
|
||||
}
|
||||
|
||||
async function migrateCompendiumItems() {
|
||||
const packs = game.packs.filter((p) => p.documentName === "Item" && p.metadata.system === SYSTEM_ID)
|
||||
for (const pack of packs) {
|
||||
const content = await pack.getDocuments()
|
||||
for (const item of content) {
|
||||
const updateData = migrateItemData(item)
|
||||
if (Object.keys(updateData).length > 0) {
|
||||
await item.update(updateData, { pack: pack.collection, enforceTypes: false })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function migrateActorData(actor) {
|
||||
const updateData = {}
|
||||
const system = actor.system ?? {}
|
||||
const actorType = actor.type
|
||||
|
||||
// Fix legacy typo: masteryofthway -> masteryoftheway
|
||||
const legacyMagic = system.magics?.masteryofthway
|
||||
if (legacyMagic && !system.magics?.masteryoftheway) {
|
||||
updateData["system.magics.masteryoftheway"] = legacyMagic
|
||||
updateData["system.magics.-=masteryofthway"] = null
|
||||
}
|
||||
|
||||
// Ensure prefs.typeofthrow exists on relevant actor types
|
||||
if ((actorType === "character" || actorType === "npc") && !system.prefs?.typeofthrow) {
|
||||
const defaultCheck = actorType === "character"
|
||||
updateData["system.prefs.typeofthrow"] = { check: defaultCheck, choice: "0" }
|
||||
}
|
||||
|
||||
return updateData
|
||||
}
|
||||
|
||||
function migrateItemData(item) {
|
||||
const updateData = {}
|
||||
const system = item.system ?? {}
|
||||
|
||||
// Add item-specific migrations here as needed
|
||||
|
||||
return updateData
|
||||
}
|
||||
132
src/system.js
Normal file
132
src/system.js
Normal file
@@ -0,0 +1,132 @@
|
||||
import { ACTOR_TYPES, ITEM_TYPES, MAGICS, SUBTYPES, SYSTEM_ID } from "./config/constants.js"
|
||||
import { preLocalizeConfig } from "./config/localize.js"
|
||||
import { configureRuntime } from "./config/runtime.js"
|
||||
import { CharacterDataModel, LoksyuDataModel, NpcDataModel, TinjiDataModel } from "./data/actors/index.js"
|
||||
import { EquipmentDataModel, KungfuDataModel, SpellDataModel, SupernaturalDataModel } from "./data/items/index.js"
|
||||
import { CDEMessage } from "./documents/chat-message.js"
|
||||
import { CDEActor } from "./documents/actor.js"
|
||||
import { CDEItem } from "./documents/item.js"
|
||||
import { registerDice } from "./ui/dice.js"
|
||||
import { registerHandlebarsHelpers } from "./ui/helpers.js"
|
||||
import { preloadPartials } from "./ui/templates.js"
|
||||
import { CDELoksyuSheet, CDECharacterSheet, CDENpcSheet, CDETinjiSheet } from "./ui/sheets/actors/index.js"
|
||||
import { CDEItemSheet, CDEKungfuSheet, CDESpellSheet, CDESupernaturalSheet } from "./ui/sheets/items/index.js"
|
||||
import { migrateIfNeeded, registerSettings } from "./migration.js"
|
||||
|
||||
Hooks.once("i18nInit", preLocalizeConfig)
|
||||
|
||||
Hooks.once("init", async () => {
|
||||
console.info(`CHRONIQUESDELETRANGE | Initializing ${SYSTEM_ID}`)
|
||||
|
||||
registerSettings()
|
||||
|
||||
game.system.CONST = { MAGICS, SUBTYPES }
|
||||
|
||||
CONFIG.Actor.systemDataModels = {
|
||||
[ACTOR_TYPES.character]: CharacterDataModel,
|
||||
[ACTOR_TYPES.npc]: NpcDataModel,
|
||||
[ACTOR_TYPES.tinji]: TinjiDataModel,
|
||||
[ACTOR_TYPES.loksyu]: LoksyuDataModel,
|
||||
}
|
||||
CONFIG.Item.systemDataModels = {
|
||||
[ITEM_TYPES.item]: EquipmentDataModel,
|
||||
[ITEM_TYPES.kungfu]: KungfuDataModel,
|
||||
[ITEM_TYPES.spell]: SpellDataModel,
|
||||
[ITEM_TYPES.supernatural]: SupernaturalDataModel,
|
||||
}
|
||||
|
||||
CONFIG.Actor.documentClass = CDEActor
|
||||
CONFIG.Item.documentClass = CDEItem
|
||||
CONFIG.ChatMessage.documentClass = CDEMessage
|
||||
|
||||
configureRuntime()
|
||||
|
||||
DocumentSheetConfig.unregisterSheet(Actor, "core", ActorSheet)
|
||||
DocumentSheetConfig.unregisterSheet(Item, "core", ItemSheet)
|
||||
|
||||
DocumentSheetConfig.registerSheet(Actor, SYSTEM_ID, CDECharacterSheet, {
|
||||
types: [ACTOR_TYPES.character],
|
||||
makeDefault: true,
|
||||
label: "CDE Character Sheet (V2)",
|
||||
})
|
||||
DocumentSheetConfig.registerSheet(Actor, SYSTEM_ID, CDENpcSheet, {
|
||||
types: [ACTOR_TYPES.npc],
|
||||
makeDefault: true,
|
||||
label: "CDE NPC Sheet (V2)",
|
||||
})
|
||||
DocumentSheetConfig.registerSheet(Actor, SYSTEM_ID, CDETinjiSheet, {
|
||||
types: [ACTOR_TYPES.tinji],
|
||||
makeDefault: true,
|
||||
label: "CDE Tinji Sheet (V2)",
|
||||
})
|
||||
DocumentSheetConfig.registerSheet(Actor, SYSTEM_ID, CDELoksyuSheet, {
|
||||
types: [ACTOR_TYPES.loksyu],
|
||||
makeDefault: true,
|
||||
label: "CDE Loksyu Sheet (V2)",
|
||||
})
|
||||
|
||||
DocumentSheetConfig.registerSheet(Item, SYSTEM_ID, CDEItemSheet, {
|
||||
types: [ITEM_TYPES.item],
|
||||
makeDefault: true,
|
||||
label: "CDE Item Sheet (V2)",
|
||||
})
|
||||
DocumentSheetConfig.registerSheet(Item, SYSTEM_ID, CDEKungfuSheet, {
|
||||
types: [ITEM_TYPES.kungfu],
|
||||
makeDefault: true,
|
||||
label: "CDE KungFu Sheet (V2)",
|
||||
})
|
||||
DocumentSheetConfig.registerSheet(Item, SYSTEM_ID, CDESpellSheet, {
|
||||
types: [ITEM_TYPES.spell],
|
||||
makeDefault: true,
|
||||
label: "CDE Spell Sheet (V2)",
|
||||
})
|
||||
DocumentSheetConfig.registerSheet(Item, SYSTEM_ID, CDESupernaturalSheet, {
|
||||
types: [ITEM_TYPES.supernatural],
|
||||
makeDefault: true,
|
||||
label: "CDE Supernatural Sheet (V2)",
|
||||
})
|
||||
|
||||
await preloadPartials()
|
||||
registerHandlebarsHelpers()
|
||||
registerDice()
|
||||
Hooks.on("renderSettings", (_app, html) => injectCompendiumLink(html))
|
||||
|
||||
console.info(`CHRONIQUESDELETRANGE | Initialized`)
|
||||
})
|
||||
|
||||
Hooks.once("ready", async () => {
|
||||
if (!game.modules.get("lib-wrapper")?.active && game.user.isGM) {
|
||||
ui.notifications.error("System fvtt-chroniques-de-l-etrange requires the 'libWrapper' module. Please install and activate it.")
|
||||
}
|
||||
await migrateIfNeeded()
|
||||
})
|
||||
|
||||
function injectCompendiumLink(html) {
|
||||
const header = html[0]?.querySelector?.("h4.divider")
|
||||
if (!header) return
|
||||
|
||||
const section = document.createElement("section")
|
||||
section.classList.add("settings", "flexcol")
|
||||
section.innerHTML = `
|
||||
<section class="links flexcol">
|
||||
<img class="logo-info" src="systems/fvtt-chroniques-de-l-etrange/images/logo_jeu.png" />
|
||||
<h4 class="divider"> Lien utile <i class="fa-light fa-up-right-from-square"></i> </h4>
|
||||
</section>
|
||||
<section class="settings flexcol">
|
||||
<button type="button" data-action="open-cde-link">
|
||||
<i class="fa fa-download"></i> Compendium pour Les CdE <i class="fa-light fa-up-right-from-square"></i>
|
||||
</button>
|
||||
<details>
|
||||
<summary><small>Guide d'installation</small></summary>
|
||||
<small style="text-align: center;">
|
||||
<p>Rendez-vous sur le site de l'éditeur, téléchargez les PDF contenant les liens vers les compendia, puis ajoutez leurs manifestes dans Foundry.</p>
|
||||
</small>
|
||||
</details>
|
||||
</section>
|
||||
`
|
||||
section.querySelector("button[data-action='open-cde-link']")?.addEventListener("click", () => {
|
||||
window.open("https://antre-monde.com/les-chroniques-de-letrengae/", "_blank")
|
||||
})
|
||||
|
||||
header.parentNode.insertBefore(section, header)
|
||||
}
|
||||
49
src/ui/dice.js
Normal file
49
src/ui/dice.js
Normal file
@@ -0,0 +1,49 @@
|
||||
const DIGIT_LABELS = [
|
||||
"systems/fvtt-chroniques-de-l-etrange/images/dice-so-nice/digit/d10-1.webp",
|
||||
"systems/fvtt-chroniques-de-l-etrange/images/dice-so-nice/digit/d10-2.webp",
|
||||
"systems/fvtt-chroniques-de-l-etrange/images/dice-so-nice/digit/d10-3.webp",
|
||||
"systems/fvtt-chroniques-de-l-etrange/images/dice-so-nice/digit/d10-4.webp",
|
||||
"systems/fvtt-chroniques-de-l-etrange/images/dice-so-nice/digit/d10-5.webp",
|
||||
"systems/fvtt-chroniques-de-l-etrange/images/dice-so-nice/digit/d10-6.webp",
|
||||
"systems/fvtt-chroniques-de-l-etrange/images/dice-so-nice/digit/d10-7.webp",
|
||||
"systems/fvtt-chroniques-de-l-etrange/images/dice-so-nice/digit/d10-8.webp",
|
||||
"systems/fvtt-chroniques-de-l-etrange/images/dice-so-nice/digit/d10-9.webp",
|
||||
"systems/fvtt-chroniques-de-l-etrange/images/dice-so-nice/digit/d10-10.webp",
|
||||
]
|
||||
|
||||
const CLASSIC_LABELS = [
|
||||
"systems/fvtt-chroniques-de-l-etrange/images/dice-so-nice/d10-1.webp",
|
||||
"systems/fvtt-chroniques-de-l-etrange/images/dice-so-nice/d10-2.webp",
|
||||
"systems/fvtt-chroniques-de-l-etrange/images/dice-so-nice/d10-3.webp",
|
||||
"systems/fvtt-chroniques-de-l-etrange/images/dice-so-nice/d10-4.webp",
|
||||
"systems/fvtt-chroniques-de-l-etrange/images/dice-so-nice/d10-5.webp",
|
||||
"systems/fvtt-chroniques-de-l-etrange/images/dice-so-nice/d10-6.webp",
|
||||
"systems/fvtt-chroniques-de-l-etrange/images/dice-so-nice/d10-7.webp",
|
||||
"systems/fvtt-chroniques-de-l-etrange/images/dice-so-nice/d10-8.webp",
|
||||
"systems/fvtt-chroniques-de-l-etrange/images/dice-so-nice/d10-9.webp",
|
||||
"systems/fvtt-chroniques-de-l-etrange/images/dice-so-nice/d10-10.webp",
|
||||
]
|
||||
|
||||
export function registerDice() {
|
||||
Hooks.once("diceSoNiceReady", (dice3d) => {
|
||||
dice3d.addColorset(
|
||||
{
|
||||
name: "cde",
|
||||
description: "CdE",
|
||||
foreground: "#000000",
|
||||
background: "#ffffff",
|
||||
edge: "#ffffff",
|
||||
font: "DeliusUnicase",
|
||||
texture: "ice",
|
||||
material: "plastic",
|
||||
},
|
||||
"preferred",
|
||||
)
|
||||
|
||||
dice3d.addSystem({ id: "fvtt-chroniques-de-l-etrangedigit", name: "Chroniques de l'étrange digits" }, "preferred")
|
||||
dice3d.addDicePreset({ type: "d10", labels: DIGIT_LABELS, system: "fvtt-chroniques-de-l-etrangedigit" })
|
||||
|
||||
dice3d.addSystem({ id: "fvtt-chroniques-de-l-etrange", name: "Chroniques de l'étrange" }, "preferred")
|
||||
dice3d.addDicePreset({ type: "d10", labels: CLASSIC_LABELS, system: "fvtt-chroniques-de-l-etrange" })
|
||||
})
|
||||
}
|
||||
49
src/ui/helpers.js
Normal file
49
src/ui/helpers.js
Normal file
@@ -0,0 +1,49 @@
|
||||
import { MAGICS } from "../config/constants.js"
|
||||
|
||||
export function registerHandlebarsHelpers() {
|
||||
const { Handlebars } = globalThis
|
||||
if (!Handlebars) return
|
||||
|
||||
Handlebars.registerHelper("select", function (selected, options) {
|
||||
const escapedValue = RegExp.escape(Handlebars.escapeExpression(selected))
|
||||
const rgx = new RegExp(` value=["']${escapedValue}["']`)
|
||||
const html = options.fn(this)
|
||||
return html.replace(rgx, "$& selected")
|
||||
})
|
||||
|
||||
Handlebars.registerHelper("getMagicBackground", function (magic) {
|
||||
return game.i18n.localize(MAGICS[magic]?.background ?? "")
|
||||
})
|
||||
|
||||
Handlebars.registerHelper("getMagicLabel", function (magic) {
|
||||
return game.i18n.localize(MAGICS[magic]?.label ?? "")
|
||||
})
|
||||
|
||||
Handlebars.registerHelper("getMagicAspectLabel", function (magic) {
|
||||
return game.i18n.localize(MAGICS[magic]?.aspectlabel ?? "")
|
||||
})
|
||||
|
||||
Handlebars.registerHelper("getMagicSpecialityLabel", function (magic, speciality) {
|
||||
return game.i18n.localize(MAGICS[magic]?.speciality?.[speciality]?.label ?? "")
|
||||
})
|
||||
|
||||
Handlebars.registerHelper("getMagicSpecialityClassIcon", function (magic, speciality) {
|
||||
return MAGICS[magic]?.speciality?.[speciality]?.classicon ?? ""
|
||||
})
|
||||
|
||||
Handlebars.registerHelper("getMagicSpecialityIcon", function (magic, speciality) {
|
||||
return MAGICS[magic]?.speciality?.[speciality]?.icon ?? ""
|
||||
})
|
||||
|
||||
Handlebars.registerHelper("getMagicSpecialityElementIcon", function (magic, speciality) {
|
||||
return MAGICS[magic]?.speciality?.[speciality]?.elementicon ?? ""
|
||||
})
|
||||
|
||||
Handlebars.registerHelper("getMagicSpecialityLabelIcon", function (magic, speciality) {
|
||||
return MAGICS[magic]?.speciality?.[speciality]?.labelicon ?? ""
|
||||
})
|
||||
|
||||
Handlebars.registerHelper("getMagicSpecialityLabelElement", function (magic, speciality) {
|
||||
return game.i18n.localize(MAGICS[magic]?.speciality?.[speciality]?.labelelement ?? "")
|
||||
})
|
||||
}
|
||||
70
src/ui/sheets/actors/base.js
Normal file
70
src/ui/sheets/actors/base.js
Normal file
@@ -0,0 +1,70 @@
|
||||
const { HandlebarsApplicationMixin } = foundry.applications.api
|
||||
|
||||
export class CDEBaseActorSheet extends HandlebarsApplicationMixin(foundry.applications.sheets.ActorSheetV2) {
|
||||
static DEFAULT_OPTIONS = {
|
||||
classes: ["fvtt-chroniques-de-l-etrange", "actor"],
|
||||
position: { width: 920, height: "auto" },
|
||||
window: { resizable: true },
|
||||
form: { submitOnChange: true },
|
||||
dragDrop: [{ dragSelector: ".item, [data-drag='true']", dropSelector: null }],
|
||||
actions: {
|
||||
create: CDEBaseActorSheet.#onItemCreate,
|
||||
edit: CDEBaseActorSheet.#onItemEdit,
|
||||
delete: CDEBaseActorSheet.#onItemDelete,
|
||||
},
|
||||
}
|
||||
|
||||
tabGroups = { primary: "description" }
|
||||
|
||||
async _prepareContext() {
|
||||
const descriptionHTML = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.description ?? "", { async: true })
|
||||
const cssClass = this.options.classes?.join(" ") ?? ""
|
||||
return {
|
||||
actor: this.document,
|
||||
system: this.document.system,
|
||||
systemData: this.document.system,
|
||||
items: this.document.items.contents,
|
||||
descriptionHTML,
|
||||
editable: this.isEditable,
|
||||
cssClass,
|
||||
}
|
||||
}
|
||||
|
||||
async _onFirstRender(context, options) {
|
||||
await super._onFirstRender(context, options)
|
||||
for (const [group, tab] of Object.entries(this.tabGroups)) {
|
||||
this.changeTab(tab, group, { force: true })
|
||||
}
|
||||
}
|
||||
|
||||
_onRender(context, options) {
|
||||
for (const [group, tab] of Object.entries(this.tabGroups)) {
|
||||
this.changeTab(tab, group, { force: true })
|
||||
}
|
||||
}
|
||||
|
||||
static async #onItemCreate(event, target) {
|
||||
const type = target.dataset.type ?? "item"
|
||||
const cls = getDocumentClass("Item")
|
||||
const labels = {
|
||||
item: "CDE.ItemNew",
|
||||
kungfu: "CDE.KFNew",
|
||||
spell: "CDE.SpellNew",
|
||||
supernatural: "CDE.SupernaturalNew",
|
||||
}
|
||||
const name = game.i18n.localize(labels[type] ?? "CDE.ItemNew")
|
||||
return cls.create({ name, type }, { parent: this.document })
|
||||
}
|
||||
|
||||
static #onItemEdit(event, target) {
|
||||
const itemId = target.closest(".item")?.dataset.itemId
|
||||
const item = this.document.items.get(itemId)
|
||||
if (item) item.sheet.render(true)
|
||||
}
|
||||
|
||||
static #onItemDelete(event, target) {
|
||||
const itemId = target.closest(".item")?.dataset.itemId
|
||||
const item = this.document.items.get(itemId)
|
||||
if (item) item.delete()
|
||||
}
|
||||
}
|
||||
112
src/ui/sheets/actors/character.js
Normal file
112
src/ui/sheets/actors/character.js
Normal file
@@ -0,0 +1,112 @@
|
||||
import { MAGICS, SUBTYPES } from "../../../config/constants.js"
|
||||
import { CDEBaseActorSheet } from "./base.js"
|
||||
|
||||
export class CDECharacterSheet extends CDEBaseActorSheet {
|
||||
static DEFAULT_OPTIONS = {
|
||||
classes: ["character"],
|
||||
}
|
||||
|
||||
static PARTS = {
|
||||
main: { template: "systems/fvtt-chroniques-de-l-etrange/templates/actor/cde-character-sheet.html" },
|
||||
}
|
||||
|
||||
tabGroups = { primary: "description" }
|
||||
|
||||
async _prepareContext() {
|
||||
const context = await super._prepareContext()
|
||||
context.equipments = context.items.filter((item) => item.type === "item")
|
||||
context.spells = context.items.filter((item) => item.type === "spell")
|
||||
context.kungfus = context.items.filter((item) => item.type === "kungfu")
|
||||
context.CDE = { MAGICS, SUBTYPES }
|
||||
return context
|
||||
}
|
||||
|
||||
_onRender(context, options) {
|
||||
super._onRender?.(context, options)
|
||||
this.#bindInitiativeControls()
|
||||
this.#bindPrefs()
|
||||
}
|
||||
|
||||
#bindInitiativeControls() {
|
||||
const buttons = this.element?.querySelectorAll(".click-initiative")
|
||||
if (!buttons?.length) return
|
||||
buttons.forEach((button) => {
|
||||
button.addEventListener("click", async () => {
|
||||
const action = button.dataset.libelId
|
||||
let initiative = this.document.system.initiative ?? 1
|
||||
if (action === "plus") {
|
||||
initiative = initiative >= 24 ? 1 : initiative + 1
|
||||
await this.document.update({ "system.initiative": initiative })
|
||||
return
|
||||
}
|
||||
if (action === "minus") {
|
||||
initiative = initiative <= 1 ? 24 : initiative - 1
|
||||
await this.document.update({ "system.initiative": initiative })
|
||||
return
|
||||
}
|
||||
if (action === "create") {
|
||||
const html = `
|
||||
<form class="flexcol">
|
||||
<div class="form-group">
|
||||
<label>${game.i18n.localize("CDE.TurnOrder")}</label>
|
||||
<input type="number" name="initiative" value="${initiative}" min="1" max="24" />
|
||||
</div>
|
||||
</form>`
|
||||
const value = await Dialog.prompt({
|
||||
title: game.i18n.localize("CDE.TurnOrder"),
|
||||
content: html,
|
||||
label: game.i18n.localize("CDE.Validate"),
|
||||
callback: (dlg) => {
|
||||
const input = dlg.querySelector("input[name='initiative']")
|
||||
return Number(input?.value ?? initiative)
|
||||
},
|
||||
})
|
||||
if (Number.isFinite(value)) {
|
||||
const sanitized = foundry.utils.clamp(Number(value), 1, 24)
|
||||
await this.document.update({ "system.initiative": sanitized })
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
#bindPrefs() {
|
||||
const button = this.element?.querySelector(".click-prefs")
|
||||
if (!button) return
|
||||
button.addEventListener("click", async () => {
|
||||
const current = this.document.system.prefs?.typeofthrow ?? { choice: "0", check: true }
|
||||
const html = `
|
||||
<form class="flexcol">
|
||||
<div class="form-group">
|
||||
<label>${game.i18n.localize("CDE.ThrowType")}</label>
|
||||
<select name="choice" value="${current.choice}">
|
||||
<option value="0"${current.choice === "0" ? " selected" : ""}>0</option>
|
||||
<option value="1"${current.choice === "1" ? " selected" : ""}>1</option>
|
||||
<option value="2"${current.choice === "2" ? " selected" : ""}>2</option>
|
||||
<option value="3"${current.choice === "3" ? " selected" : ""}>3</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>${game.i18n.localize("CDE.EnablePrompt")}</label>
|
||||
<input type="checkbox" name="check" ${current.check ? "checked" : ""}/>
|
||||
</div>
|
||||
</form>`
|
||||
const prefs = await Dialog.prompt({
|
||||
title: game.i18n.localize("CDE.Preferences"),
|
||||
content: html,
|
||||
label: game.i18n.localize("CDE.Validate"),
|
||||
callback: (dlg) => {
|
||||
const choice = dlg.querySelector("select[name='choice']")?.value ?? "0"
|
||||
const check = dlg.querySelector("input[name='check']")?.checked ?? false
|
||||
return { choice, check }
|
||||
},
|
||||
})
|
||||
if (prefs) {
|
||||
await this.document.update({
|
||||
"system.prefs.typeofthrow.choice": String(prefs.choice),
|
||||
"system.prefs.typeofthrow.check": !!prefs.check,
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
4
src/ui/sheets/actors/index.js
Normal file
4
src/ui/sheets/actors/index.js
Normal file
@@ -0,0 +1,4 @@
|
||||
export { CDECharacterSheet } from "./character.js"
|
||||
export { CDENpcSheet } from "./npc.js"
|
||||
export { CDETinjiSheet } from "./tinji.js"
|
||||
export { CDELoksyuSheet } from "./loksyu.js"
|
||||
13
src/ui/sheets/actors/loksyu.js
Normal file
13
src/ui/sheets/actors/loksyu.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import { CDEBaseActorSheet } from "./base.js"
|
||||
|
||||
export class CDELoksyuSheet extends CDEBaseActorSheet {
|
||||
static DEFAULT_OPTIONS = {
|
||||
classes: ["loksyu"],
|
||||
}
|
||||
|
||||
static PARTS = {
|
||||
main: { template: "systems/fvtt-chroniques-de-l-etrange/templates/actor/cde-loksyu-sheet.html" },
|
||||
}
|
||||
|
||||
tabGroups = { primary: "loksyu" }
|
||||
}
|
||||
67
src/ui/sheets/actors/npc.js
Normal file
67
src/ui/sheets/actors/npc.js
Normal file
@@ -0,0 +1,67 @@
|
||||
import { CDEBaseActorSheet } from "./base.js"
|
||||
|
||||
export class CDENpcSheet extends CDEBaseActorSheet {
|
||||
static DEFAULT_OPTIONS = {
|
||||
classes: ["npc"],
|
||||
}
|
||||
|
||||
static PARTS = {
|
||||
main: { template: "systems/fvtt-chroniques-de-l-etrange/templates/actor/cde-npc-sheet.html" },
|
||||
}
|
||||
|
||||
tabGroups = { primary: "description" }
|
||||
|
||||
async _prepareContext() {
|
||||
const context = await super._prepareContext()
|
||||
context.supernaturals = context.items.filter((item) => item.type === "supernatural")
|
||||
context.spells = context.items.filter((item) => item.type === "spell")
|
||||
context.kungfus = context.items.filter((item) => item.type === "kungfu")
|
||||
context.equipments = context.items.filter((item) => item.type === "item")
|
||||
return context
|
||||
}
|
||||
|
||||
_onRender(context, options) {
|
||||
super._onRender?.(context, options)
|
||||
this.#bindInitiativeControls()
|
||||
}
|
||||
|
||||
#bindInitiativeControls() {
|
||||
const buttons = this.element?.querySelectorAll(".click-initiative-npc")
|
||||
if (!buttons?.length) return
|
||||
buttons.forEach((button) => {
|
||||
button.addEventListener("click", async () => {
|
||||
const action = button.dataset.libelId
|
||||
let initiative = this.document.system.initiative ?? 1
|
||||
if (action === "plus") {
|
||||
initiative = initiative >= 24 ? 1 : initiative + 1
|
||||
await this.document.update({ "system.initiative": initiative })
|
||||
return
|
||||
}
|
||||
if (action === "minus") {
|
||||
initiative = initiative <= 1 ? 24 : initiative - 1
|
||||
await this.document.update({ "system.initiative": initiative })
|
||||
return
|
||||
}
|
||||
if (action === "create") {
|
||||
const html = `
|
||||
<form class="flexcol">
|
||||
<div class="form-group">
|
||||
<label>${game.i18n.localize("CDE.TurnOrder")}</label>
|
||||
<input type="number" name="initiative" value="${initiative}" min="1" max="24" />
|
||||
</div>
|
||||
</form>`
|
||||
const value = await Dialog.prompt({
|
||||
title: game.i18n.localize("CDE.TurnOrder"),
|
||||
content: html,
|
||||
label: game.i18n.localize("CDE.Validate"),
|
||||
callback: (dlg) => Number(dlg.querySelector("input[name='initiative']")?.value ?? initiative),
|
||||
})
|
||||
if (Number.isFinite(value)) {
|
||||
const sanitized = foundry.utils.clamp(Number(value), 1, 24)
|
||||
await this.document.update({ "system.initiative": sanitized })
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
13
src/ui/sheets/actors/tinji.js
Normal file
13
src/ui/sheets/actors/tinji.js
Normal file
@@ -0,0 +1,13 @@
|
||||
import { CDEBaseActorSheet } from "./base.js"
|
||||
|
||||
export class CDETinjiSheet extends CDEBaseActorSheet {
|
||||
static DEFAULT_OPTIONS = {
|
||||
classes: ["tinji"],
|
||||
}
|
||||
|
||||
static PARTS = {
|
||||
main: { template: "systems/fvtt-chroniques-de-l-etrange/templates/actor/cde-tinji-sheet.html" },
|
||||
}
|
||||
|
||||
tabGroups = { primary: "tinji" }
|
||||
}
|
||||
43
src/ui/sheets/items/base.js
Normal file
43
src/ui/sheets/items/base.js
Normal file
@@ -0,0 +1,43 @@
|
||||
const { HandlebarsApplicationMixin } = foundry.applications.api
|
||||
|
||||
export class CDEBaseItemSheet extends HandlebarsApplicationMixin(foundry.applications.sheets.ItemSheetV2) {
|
||||
static DEFAULT_OPTIONS = {
|
||||
classes: ["fvtt-chroniques-de-l-etrange", "item"],
|
||||
position: { width: 520, height: "auto" },
|
||||
window: { resizable: true },
|
||||
form: { submitOnChange: true },
|
||||
actions: {},
|
||||
}
|
||||
|
||||
tabGroups = { primary: "description" }
|
||||
|
||||
async _prepareContext() {
|
||||
const cssClass = this.options.classes?.join(" ") ?? ""
|
||||
const enrichedDescription = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.description ?? "", { async: true })
|
||||
const enrichedNotes = await foundry.applications.ux.TextEditor.implementation.enrichHTML(this.document.system.notes ?? "", { async: true })
|
||||
return {
|
||||
item: this.document,
|
||||
system: this.document.system,
|
||||
systemData: this.document.system,
|
||||
editable: this.isEditable,
|
||||
cssClass,
|
||||
enrichedDescription,
|
||||
enrichedNotes,
|
||||
descriptionHTML: enrichedDescription,
|
||||
notesHTML: enrichedNotes,
|
||||
}
|
||||
}
|
||||
|
||||
async _onFirstRender(context, options) {
|
||||
await super._onFirstRender(context, options)
|
||||
for (const [group, tab] of Object.entries(this.tabGroups)) {
|
||||
this.changeTab(tab, group, { force: true })
|
||||
}
|
||||
}
|
||||
|
||||
_onRender(context, options) {
|
||||
for (const [group, tab] of Object.entries(this.tabGroups)) {
|
||||
this.changeTab(tab, group, { force: true })
|
||||
}
|
||||
}
|
||||
}
|
||||
5
src/ui/sheets/items/index.js
Normal file
5
src/ui/sheets/items/index.js
Normal file
@@ -0,0 +1,5 @@
|
||||
export { CDEBaseItemSheet } from "./base.js"
|
||||
export { CDEItemSheet } from "./item.js"
|
||||
export { CDEKungfuSheet } from "./kungfu.js"
|
||||
export { CDESpellSheet } from "./spell.js"
|
||||
export { CDESupernaturalSheet } from "./supernatural.js"
|
||||
23
src/ui/sheets/items/item.js
Normal file
23
src/ui/sheets/items/item.js
Normal file
@@ -0,0 +1,23 @@
|
||||
import { SUBTYPES } from "../../../config/constants.js"
|
||||
import { CDEBaseItemSheet } from "./base.js"
|
||||
|
||||
export class CDEItemSheet extends CDEBaseItemSheet {
|
||||
static DEFAULT_OPTIONS = {
|
||||
classes: ["equipment"],
|
||||
position: { width: 620, height: 580 },
|
||||
}
|
||||
|
||||
static PARTS = {
|
||||
main: { template: "systems/fvtt-chroniques-de-l-etrange/templates/item/cde-item-sheet.html" },
|
||||
}
|
||||
|
||||
async _prepareContext() {
|
||||
const context = await super._prepareContext()
|
||||
context.subtypes = SUBTYPES
|
||||
context.isWeapon = this.document.isWeapon
|
||||
context.isArmor = this.document.isArmor
|
||||
context.isSanhei = this.document.isSanhei
|
||||
context.isOther = this.document.isOther
|
||||
return context
|
||||
}
|
||||
}
|
||||
22
src/ui/sheets/items/kungfu.js
Normal file
22
src/ui/sheets/items/kungfu.js
Normal file
@@ -0,0 +1,22 @@
|
||||
import { CDEBaseItemSheet } from "./base.js"
|
||||
|
||||
export class CDEKungfuSheet extends CDEBaseItemSheet {
|
||||
static DEFAULT_OPTIONS = {
|
||||
classes: ["kungfu"],
|
||||
position: { width: 720, height: 680 },
|
||||
}
|
||||
|
||||
static PARTS = {
|
||||
main: { template: "systems/fvtt-chroniques-de-l-etrange/templates/item/cde-kungfu-sheet.html" },
|
||||
}
|
||||
|
||||
async _prepareContext() {
|
||||
const context = await super._prepareContext()
|
||||
const techniques = this.document.system.techniques ?? {}
|
||||
const enrich = (value) => foundry.applications.ux.TextEditor.implementation.enrichHTML(value ?? "", { async: true })
|
||||
context.descriptionTechnique1HTML = await enrich(techniques.technique1?.technique)
|
||||
context.descriptionTechnique2HTML = await enrich(techniques.technique2?.technique)
|
||||
context.descriptionTechnique3HTML = await enrich(techniques.technique3?.technique)
|
||||
return context
|
||||
}
|
||||
}
|
||||
22
src/ui/sheets/items/spell.js
Normal file
22
src/ui/sheets/items/spell.js
Normal file
@@ -0,0 +1,22 @@
|
||||
import { CDEBaseItemSheet } from "./base.js"
|
||||
|
||||
export class CDESpellSheet extends CDEBaseItemSheet {
|
||||
static DEFAULT_OPTIONS = {
|
||||
classes: ["spell"],
|
||||
position: { width: 660, height: 680 },
|
||||
}
|
||||
|
||||
static PARTS = {
|
||||
main: { template: "systems/fvtt-chroniques-de-l-etrange/templates/item/cde-spell-sheet.html" },
|
||||
}
|
||||
|
||||
async _prepareContext() {
|
||||
const context = await super._prepareContext()
|
||||
const enrich = (content) => foundry.applications.ux.TextEditor.implementation.enrichHTML(content ?? "", { async: true })
|
||||
context.spellDescriptionHTML = await enrich(this.document.system.description)
|
||||
context.componentsDescriptionHTML = await enrich(this.document.system.components)
|
||||
context.effectsDescriptionHTML = await enrich(this.document.system.effects)
|
||||
context.examplesDescriptionHTML = await enrich(this.document.system.examples)
|
||||
return context
|
||||
}
|
||||
}
|
||||
12
src/ui/sheets/items/supernatural.js
Normal file
12
src/ui/sheets/items/supernatural.js
Normal file
@@ -0,0 +1,12 @@
|
||||
import { CDEBaseItemSheet } from "./base.js"
|
||||
|
||||
export class CDESupernaturalSheet extends CDEBaseItemSheet {
|
||||
static DEFAULT_OPTIONS = {
|
||||
classes: ["supernatural"],
|
||||
position: { width: 560, height: 520 },
|
||||
}
|
||||
|
||||
static PARTS = {
|
||||
main: { template: "systems/fvtt-chroniques-de-l-etrange/templates/item/cde-supernatural-sheet.html" },
|
||||
}
|
||||
}
|
||||
5
src/ui/templates.js
Normal file
5
src/ui/templates.js
Normal file
@@ -0,0 +1,5 @@
|
||||
import { TEMPLATE_PARTIALS } from "../config/constants.js"
|
||||
|
||||
export async function preloadPartials() {
|
||||
return loadTemplates(TEMPLATE_PARTIALS)
|
||||
}
|
||||
Reference in New Issue
Block a user