diff --git a/.github/workflows/draft-release.yaml b/.github/workflows/draft-release.yaml
index e8c1a16..ea90482 100644
--- a/.github/workflows/draft-release.yaml
+++ b/.github/workflows/draft-release.yaml
@@ -29,10 +29,6 @@ jobs:
if: ${{ steps.check-tag.outputs.exists == 'true' }}
run: exit 1
- - name: Ensure there are specific files to release
- if: ${{ vars.files_to_release == '' }}
- run: exit 1
-
# Compile the stuff that needs to be compiled
- run: npm run build
- run: node scripts/buildCompendia.mjs
@@ -43,10 +39,10 @@ jobs:
- name: Update the download property in the manifest
id: manifest-update
- run: cat system.temp.json | jq -r --tab '.download = "https://github.com/${{ github.repository }}/releases/download/v${{ steps.version.outputs.version }}/${{ vars.zip_name }}.zip"' > system.json
+ run: cat system.temp.json | jq -r --tab '.download = "https://github.com/${{ github.repository }}/releases/download/v${{ steps.version.outputs.version }}/release.zip"' > system.json
- name: Create the zip
- run: zip -r ${{ vars.zip_name || 'release' }}.zip ${{ vars.files_to_release }}
+ run: zip -r release.zip langs module styles templates system.json README.md
- name: Create the draft release
uses: ncipollo/release-action@v1
diff --git a/.gitignore b/.gitignore
index 1726f87..b22745b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,2 @@
node_modules/
-.styles
\ No newline at end of file
+deprecated
diff --git a/.promo/hjonk-samples.png b/.promo/hjonk-samples.png
new file mode 100644
index 0000000..7151a06
Binary files /dev/null and b/.promo/hjonk-samples.png differ
diff --git a/README.md b/README.md
index f530123..8d4cb08 100644
--- a/README.md
+++ b/README.md
@@ -2,24 +2,3 @@
This is an intentionally bare-bones system that features a text-only character
sheet, allowing the playing of games that may not otherwise have a Foundry system
implementation.
-
-## Features
-There are not too many features included in this system for things like automation
-as it's meant to be used to mostly play rules-light games. However there are some
-special features that can be enabled on a per-world basis using a world script
-to enable feature flags that you want.
-
-### List of Feature Flags
-| Flag | Description
-| - | -
-| `ROLL_MODE_CONTENT` | Allows players, GMs, and macros to send "blind" chat messages where only the GM gets to see the content.
-| `STORABLE_SHEET_SIZE` | Makes it so that certain sheets are able to have their size saved, so that it resumes that size when opened.
-
-### Example Feature Flag
-In order to change these flags, you must make a world script that has something
-like the below code in it:
-
-```js
-// v- this is the name of the flag from the table above
-taf.FEATURES.STORABLE_SHEET_SIZE = true;
-```
\ No newline at end of file
diff --git a/eslint.config.mjs b/eslint.config.mjs
index 544b98e..454af9e 100644
--- a/eslint.config.mjs
+++ b/eslint.config.mjs
@@ -20,10 +20,6 @@ export default [
Handlebars: `readonly`,
Hooks: `readonly`,
ui: `readonly`,
- Actor: `readonly`,
- Actors: `readonly`,
- Item: `readonly`,
- Items: `readonly`,
ActorSheet: `readonly`,
ItemSheet: `readonly`,
foundry: `readonly`,
@@ -31,7 +27,8 @@ export default [
ActiveEffect: `readonly`,
Dialog: `readonly`,
renderTemplate: `readonly`,
- TextEditor: `readonly`,
+ fromUuid: `readonly`,
+ fromUuidSync: `readonly`,
},
},
},
diff --git a/langs/en-ca.json b/langs/en-ca.json
new file mode 100644
index 0000000..99de0b3
--- /dev/null
+++ b/langs/en-ca.json
@@ -0,0 +1,18 @@
+{
+ "TYPES": {
+ "Actor": {
+ "player": "Player"
+ }
+ },
+ "taf": {
+ "settings": {
+ "canPlayersManageAttributes": {
+ "name": "Players Can Manage Attributes",
+ "hint": "This allows players who have edit access to a document to be able to edit what attributes those characters have via the attribute editor"
+ }
+ },
+ "sheet-names": {
+ "PlayerSheet": "Player Sheet"
+ }
+ }
+}
diff --git a/module/apps/AttributeManager.mjs b/module/apps/AttributeManager.mjs
new file mode 100644
index 0000000..105b5bf
--- /dev/null
+++ b/module/apps/AttributeManager.mjs
@@ -0,0 +1,171 @@
+import { __ID__, filePath } from "../consts.mjs";
+import { Logger } from "../utils/Logger.mjs";
+import { toID } from "../utils/toID.mjs";
+
+const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
+const { deepClone, diffObject, randomID, setProperty } = foundry.utils;
+
+export class AttributeManager extends HandlebarsApplicationMixin(ApplicationV2) {
+
+ // #region Options
+ static DEFAULT_OPTIONS = {
+ tag: `form`,
+ classes: [
+ __ID__,
+ `AttributeManager`,
+ ],
+ position: {
+ width: 400,
+ height: 350,
+ },
+ window: {
+ resizable: true,
+ },
+ form: {
+ submitOnChange: false,
+ closeOnSubmit: true,
+ handler: this.#onSubmit,
+ },
+ actions: {
+ addNew: this.#addNew,
+ removeAttribute: this.#remove,
+ },
+ };
+
+ static PARTS = {
+ attributes: {
+ template: filePath(`templates/AttributeManager/attribute-list.hbs`),
+ },
+ controls: {
+ template: filePath(`templates/AttributeManager/controls.hbs`),
+ },
+ };
+ // #endregion Options
+
+ // #region Instance Data
+ /** @type {string | null} */
+ #doc = null;
+
+ #attributes;
+
+ constructor({ document , ...options } = {}) {
+ super(options);
+ this.#doc = document;
+ this.#attributes = deepClone(document.system.attr);
+ };
+
+ get title() {
+ return `Attributes: ${this.#doc.name}`;
+ };
+ // #endregion Instance Data
+
+ // #region Lifecycle
+ async _onRender(context, options) {
+ await super._onRender(context, options);
+
+ const elements = this.element
+ .querySelectorAll(`[data-bind]`);
+ for (const input of elements) {
+ input.addEventListener(`change`, this.#bindListener.bind(this));
+ };
+ };
+ // #endregion Lifecycle
+
+ // #region Data Prep
+ async _preparePartContext(partId) {
+ const ctx = {};
+
+ ctx.actor = this.#doc;
+
+ switch (partId) {
+ case `attributes`: {
+ await this._prepareAttributeContext(ctx);
+ };
+ };
+
+ return ctx;
+ };
+
+ async _prepareAttributeContext(ctx) {
+ const attrs = [];
+ for (const [id, data] of Object.entries(this.#attributes)) {
+ if (data == null) { continue };
+ attrs.push({
+ id,
+ name: data.name,
+ isRange: data.isRange,
+ isNew: data.isNew ?? false,
+ });
+ };
+ ctx.attrs = attrs;
+ };
+ // #endregion Data Prep
+
+ // #region Actions
+ /**
+ * @param {Event} event
+ */
+ async #bindListener(event) {
+ const target = event.target;
+ const data = target.dataset;
+ const binding = data.bind;
+
+ let value = target.value;
+ switch (target.type) {
+ case `checkbox`: {
+ value = target.checked;
+ };
+ };
+
+ Logger.debug(`Updating ${binding} value to ${value}`);
+ setProperty(this.#attributes, binding, value);
+ await this.render();
+ };
+
+ /** @this {AttributeManager} */
+ static async #addNew() {
+ const id = randomID();
+ this.#attributes[id] = {
+ name: ``,
+ isRange: false,
+ isNew: true,
+ };
+ await this.render({ parts: [ `attributes` ]});
+ };
+
+ /** @this {AttributeManager} */
+ static async #remove($e, element) {
+ const attribute = element.closest(`[data-attribute]`)?.dataset.attribute;
+ if (!attribute) { return };
+ delete this.#attributes[attribute];
+ this.#attributes[`-=${attribute}`] = null;
+ await this.render({ parts: [ `attributes` ] });
+ };
+
+ /** @this {AttributeManager} */
+ static async #onSubmit() {
+ const entries = Object.entries(this.#attributes)
+ .map(([id, attr]) => {
+ if (attr == null) {
+ return [ id, attr ];
+ };
+
+ if (attr.isNew) {
+ delete attr.isNew;
+ return [ toID(attr.name), attr ];
+ };
+
+ return [ id, attr ];
+ });
+ const data = Object.fromEntries(entries);
+
+ const diff = diffObject(
+ this.#doc.system.attr,
+ data,
+ { inner: false, deletionKeys: true },
+ );
+
+ await this.#doc.update({ "system.attr": diff });
+ };
+ // #endregion Actions
+};
diff --git a/module/apps/PlayerSheet.mjs b/module/apps/PlayerSheet.mjs
new file mode 100644
index 0000000..dfcef25
--- /dev/null
+++ b/module/apps/PlayerSheet.mjs
@@ -0,0 +1,111 @@
+import { __ID__, filePath } from "../consts.mjs";
+import { AttributeManager } from "./AttributeManager.mjs";
+
+const { HandlebarsApplicationMixin } = foundry.applications.api;
+const { ActorSheetV2 } = foundry.applications.sheets;
+
+export class PlayerSheet extends HandlebarsApplicationMixin(ActorSheetV2) {
+
+ // #region Options
+ static DEFAULT_OPTIONS = {
+ classes: [
+ __ID__,
+ `PlayerSheet`,
+ ],
+ position: {
+ width: 575,
+ height: 740,
+ },
+ window: {
+ resizable: true,
+ },
+ form: {
+ submitOnChange: true,
+ closeOnSubmit: false,
+ },
+ actions: {
+ manageAttributes: this.#manageAttributes,
+ },
+ };
+
+ static PARTS = {
+ header: { template: filePath(`templates/PlayerSheet/header.hbs`) },
+ attributes: { template: filePath(`templates/PlayerSheet/attributes.hbs`) },
+ content: { template: filePath(`templates/PlayerSheet/content.hbs`) },
+ };
+ // #endregion Options
+
+ // #region Lifecycle
+ _getHeaderControls() {
+ const controls = super._getHeaderControls();
+
+ controls.push({
+ icon: `fa-solid fa-at`,
+ label: `Manage Attributes`,
+ action: `manageAttributes`,
+ visible: () => {
+ const isGM = game.user.isGM;
+ const allowPlayerEdits = game.settings.get(__ID__, `canPlayersManageAttributes`);
+ const editable = this.isEditable;
+ return isGM || (allowPlayerEdits && editable);
+ },
+ });
+
+ return controls;
+ };
+ // #endregion Lifecycle
+
+ // #region Data Prep
+ async _preparePartContext(partID) {
+ let ctx = {
+ actor: this.actor,
+ system: this.actor.system,
+ editable: this.isEditable,
+ };
+
+ switch (partID) {
+ case `attributes`: {
+ await this._prepareAttributes(ctx);
+ break;
+ };
+ case `content`: {
+ await this._prepareContent(ctx);
+ break;
+ };
+ };
+
+ return ctx;
+ };
+
+ async _prepareAttributes(ctx) {
+ ctx.hasAttributes = this.actor.system.hasAttributes;
+
+ const attrs = [];
+ for (const [id, data] of Object.entries(this.actor.system.attr)) {
+ attrs.push({
+ ...data,
+ id,
+ path: `system.attr.${id}`,
+ });
+ };
+ ctx.attrs = attrs.toSorted((a, b) => a.name.localeCompare(b.name));
+ };
+
+ async _prepareContent(ctx) {
+ const TextEditor = foundry.applications.ux.TextEditor.implementation;
+ ctx.enriched = {
+ system: {
+ content: await TextEditor.enrichHTML(this.actor.system.content),
+ },
+ };
+ };
+ // #endregion Data Prep
+
+ // #region Actions
+ /** @this {PlayerSheet} */
+ static async #manageAttributes() {
+ const app = new AttributeManager({ document: this.actor });
+ await app.render({ force: true });
+ };
+ // #endregion Actions
+};
diff --git a/module/consts.mjs b/module/consts.mjs
new file mode 100644
index 0000000..0d33f25
--- /dev/null
+++ b/module/consts.mjs
@@ -0,0 +1,9 @@
+export const __ID__ = `taf`;
+
+// MARK: filePath
+export function filePath(path) {
+ if (path.startsWith(`/`)) {
+ path = path.slice(1);
+ };
+ return `systems/${__ID__}/${path}`;
+};
diff --git a/module/data/Player.mjs b/module/data/Player.mjs
new file mode 100644
index 0000000..9b449a1
--- /dev/null
+++ b/module/data/Player.mjs
@@ -0,0 +1,29 @@
+export class PlayerData extends foundry.abstract.TypeDataModel {
+ static defineSchema() {
+ const fields = foundry.data.fields;
+ return {
+ content: new fields.HTMLField({
+ blank: true,
+ trim: true,
+ initial: ``,
+ }),
+ attr: new fields.TypedObjectField(
+ new fields.SchemaField({
+ name: new fields.StringField({ blank: false, trim: true }),
+ value: new fields.NumberField({ min: 0, initial: 0, integer: true, nullable: false }),
+ max: new fields.NumberField({ min: 0, initial: null, integer: true, nullable: true }),
+ isRange: new fields.BooleanField({ initial: false, nullable: false }),
+ }),
+ {
+ initial: {},
+ nullable: false,
+ required: true,
+ },
+ ),
+ };
+ };
+
+ get hasAttributes() {
+ return Object.keys(this.attr).length > 0;
+ };
+};
diff --git a/module/documents/Actor.mjs b/module/documents/Actor.mjs
new file mode 100644
index 0000000..292cdf3
--- /dev/null
+++ b/module/documents/Actor.mjs
@@ -0,0 +1,28 @@
+import { Logger } from "../utils/Logger.mjs";
+
+const { Actor } = foundry.documents;
+
+export class TAFActor extends Actor {
+ async modifyTokenAttribute(attribute, value, isDelta = false, isBar = true) {
+ Logger.table({ attribute, value, isDelta, isBar });
+ const attr = foundry.utils.getProperty(this.system, attribute);
+ const current = isBar ? attr.value : attr;
+ const update = isDelta ? current + value : value;
+ if ( update === current ) {
+ return this;
+ };
+
+ // Determine the updates to make to the actor data
+ let updates;
+ if (isBar) {
+ updates = {[`system.${attribute}.value`]: Math.clamp(update, 0, attr.max)};
+ } else {
+ updates = {[`system.${attribute}`]: update};
+ };
+
+ // Allow a hook to override these changes
+ const allowed = Hooks.call(`modifyTokenAttribute`, {attribute, value, isDelta, isBar}, updates, this);
+
+ return allowed !== false ? this.update(updates) : this;
+ }
+};
diff --git a/module/documents/Token.mjs b/module/documents/Token.mjs
new file mode 100644
index 0000000..b6baf2d
--- /dev/null
+++ b/module/documents/Token.mjs
@@ -0,0 +1,111 @@
+import { Logger } from "../utils/Logger.mjs";
+
+const { TokenDocument } = foundry.documents;
+const { getProperty, getType, hasProperty, isSubclass } = foundry.utils;
+
+export class TAFTokenDocument extends TokenDocument {
+
+ /**
+ * @override
+ * This override's purpose is to make it so that Token attributes and bars can
+ * be accessed from the data model's values directly instead of relying on only
+ * the schema, which doesn't account for my TypedObjectField of attributes.
+ */
+ static getTrackedAttributes(data, _path = []) {
+
+ // Case 1 - Infer attributes from schema structure.
+ if ( (data instanceof foundry.abstract.DataModel) || isSubclass(data, foundry.abstract.DataModel) ) {
+ return this._getTrackedAttributesFromObject(data, _path);
+ }
+ if ( data instanceof foundry.data.fields.SchemaField ) {
+ return this._getTrackedAttributesFromSchema(data, _path);
+ }
+
+ // Case 2 - Infer attributes from object structure.
+ if ( [`Object`, `Array`].includes(getType(data)) ) {
+ return this._getTrackedAttributesFromObject(data, _path);
+ }
+
+ // Case 3 - Retrieve explicitly configured attributes.
+ if ( !data || (typeof data === `string`) ) {
+ const config = this._getConfiguredTrackedAttributes(data);
+ if ( config ) {
+ return config;
+ }
+ data = undefined;
+ }
+
+ // Track the path and record found attributes
+ if ( data !== undefined ) {
+ return {bar: [], value: []};
+ }
+
+ // Case 4 - Infer attributes from system template.
+ const bar = new Set();
+ const value = new Set();
+ for ( const [type, model] of Object.entries(game.model.Actor) ) {
+ const dataModel = CONFIG.Actor.dataModels?.[type];
+ const inner = this.getTrackedAttributes(dataModel ?? model, _path);
+ inner.bar.forEach(attr => bar.add(attr.join(`.`)));
+ inner.value.forEach(attr => value.add(attr.join(`.`)));
+ }
+
+ return {
+ bar: Array.from(bar).map(attr => attr.split(`.`)),
+ value: Array.from(value).map(attr => attr.split(`.`)),
+ };
+ };
+
+ /**
+ * @override
+ */
+ getBarAttribute(barName, {alternative} = {}) {
+ const attribute = alternative || this[barName]?.attribute;
+ Logger.log(barName, attribute);
+ if (!attribute || !this.actor) {
+ return null;
+ };
+ const system = this.actor.system;
+
+ // Get the current attribute value
+ const data = getProperty(system, attribute);
+ if (data == null) {
+ return null;
+ };
+
+ if (Number.isNumeric(data)) {
+ let editable = hasProperty(system, attribute);
+ return {
+ type: `value`,
+ attribute,
+ value: Number(data),
+ editable,
+ };
+ };
+
+ if (`value` in data && `max` in data) {
+ let editable = hasProperty(system, `${attribute}.value`);
+ const isRange = getProperty(system, `${attribute}.isRange`);
+ if (isRange) {
+ return {
+ type: `bar`,
+ attribute,
+ value: parseInt(data.value || 0),
+ max: parseInt(data.max || 0),
+ editable,
+ };
+ } else {
+ return {
+ type: `value`,
+ attribute: `${attribute}.value`,
+ value: Number(data.value),
+ editable,
+ };
+ };
+ };
+
+ // Otherwise null
+ return null;
+ };
+
+};
diff --git a/module/hooks/init.mjs b/module/hooks/init.mjs
new file mode 100644
index 0000000..442d916
--- /dev/null
+++ b/module/hooks/init.mjs
@@ -0,0 +1,36 @@
+// Apps
+import { PlayerSheet } from "../apps/PlayerSheet.mjs";
+
+// Data Models
+import { PlayerData } from "../data/Player.mjs";
+
+// Documents
+import { TAFActor } from "../documents/Actor.mjs";
+import { TAFTokenDocument } from "../documents/Token.mjs";
+
+// Settings
+import { registerWorldSettings } from "../settings/world.mjs";
+
+// Utils
+import { __ID__ } from "../consts.mjs";
+import { Logger } from "../utils/Logger.mjs";
+
+Hooks.on(`init`, () => {
+ Logger.debug(`Initializing`);
+
+ CONFIG.Token.documentClass = TAFTokenDocument;
+ CONFIG.Actor.documentClass = TAFActor;
+
+ CONFIG.Actor.dataModels.player = PlayerData;
+
+ foundry.documents.collections.Actors.registerSheet(
+ __ID__,
+ PlayerSheet,
+ {
+ makeDefault: true,
+ label: `taf.sheet-names.PlayerSheet`,
+ },
+ );
+
+ registerWorldSettings();
+});
diff --git a/module/main.mjs b/module/main.mjs
new file mode 100644
index 0000000..8f3b5cc
--- /dev/null
+++ b/module/main.mjs
@@ -0,0 +1 @@
+import "./hooks/init.mjs";
diff --git a/module/settings/world.mjs b/module/settings/world.mjs
new file mode 100644
index 0000000..78462d7
--- /dev/null
+++ b/module/settings/world.mjs
@@ -0,0 +1,12 @@
+import { __ID__ } from "../consts.mjs";
+
+export function registerWorldSettings() {
+ game.settings.register(__ID__, `canPlayersManageAttributes`, {
+ name: `taf.settings.canPlayersManageAttributes.name`,
+ hint: `taf.settings.canPlayersManageAttributes.hint`,
+ config: true,
+ type: Boolean,
+ default: false,
+ scope: `world`,
+ });
+};
diff --git a/src/utils/logger.mjs b/module/utils/Logger.mjs
similarity index 71%
rename from src/utils/logger.mjs
rename to module/utils/Logger.mjs
index 5f9b37e..70c6481 100644
--- a/src/utils/logger.mjs
+++ b/module/utils/Logger.mjs
@@ -12,10 +12,10 @@ const augmentedProps = new Set([
]);
/** @type {Console} */
-globalThis.Logger = new Proxy(console, {
+export const Logger = new Proxy(console, {
get(target, prop, _receiver) {
if (augmentedProps.has(prop)) {
- return (...args) => target[prop](game.system.id, `|`, ...args);
+ return target[prop].bind(target, game.system.id, `|`);
};
return target[prop];
},
diff --git a/module/utils/toID.mjs b/module/utils/toID.mjs
new file mode 100644
index 0000000..384ed22
--- /dev/null
+++ b/module/utils/toID.mjs
@@ -0,0 +1,13 @@
+/**
+ * A helper method that converts an arbitrary string into a format that can be
+ * used as an object key easily.
+ *
+ * @param {string} text The text to convert
+ * @returns The converted ID
+ */
+export function toID(text) {
+ return text
+ .toLowerCase()
+ .replace(/\s/g, `_`)
+ .replace(/\W/g, ``);
+};
diff --git a/scripts/macros/rollDice.mjs b/scripts/macros/rollDice.mjs
deleted file mode 100644
index b2d99b2..0000000
--- a/scripts/macros/rollDice.mjs
+++ /dev/null
@@ -1,77 +0,0 @@
-async function rollDice() {
- const sidesOnDice = 6;
-
- const answers = await DialogManager.ask({
- id: `eat-the-reich-dice-pool`,
- question: `Set up your dice pool:`,
- inputs: [
- {
- key: `statBase`,
- inputType: `number`,
- defaultValue: 2,
- label: `Number of Dice`,
- autofocus: true,
- },
- {
- key: `successThreshold`,
- inputType: `number`,
- defaultValue: 3,
- label: `Success Threshold (d${sidesOnDice} > X)`,
- },
- {
- key: `critsEnabled`,
- inputType: `checkbox`,
- defaultValue: true,
- label: `Enable Criticals`,
- },
- ],
- });
- const { statBase, successThreshold, critsEnabled } = answers;
- let rollMode = game.settings.get(`core`, `rollMode`);
-
-
-
- let successes = 0;
- let critsOnly = 0;
- const results = [];
- for (let i = statBase; i > 0; i--) {
- let r = new Roll(`1d${sidesOnDice}`);
- await r.evaluate();
- let classes = `roll die d6`;
-
- // Determine the success count and class modifications for the chat
- if (r.total > successThreshold) {
- successes++;
- }
- else {
- classes += ` failure`
- }
- if (r.total === sidesOnDice && critsEnabled) {
- successes++;
- critsOnly++;
- classes += ` success`;
- }
-
- results.push(`
${r.total} `);
- }
-
- let content = `Rolls: Successes: ${successes} Crits: ${critsOnly}`;
-
-
- if (rollMode === CONST.DICE_ROLL_MODES.BLIND) {
- ui.notifications.warn(`Cannot make a blind roll from the macro, rolling with mode "Private GM Roll" instead`);
- rollMode = CONST.DICE_ROLL_MODES.PRIVATE;
- }
-
- const chatData = ChatMessage.applyRollMode(
- {
- title: `Dice Pool`,
- content,
- },
- rollMode,
- );
-
- await ChatMessage.implementation.create(chatData);
-}
-
-rollDice()
\ No newline at end of file
diff --git a/src/components/_index.mjs b/src/components/_index.mjs
deleted file mode 100644
index 44133d9..0000000
--- a/src/components/_index.mjs
+++ /dev/null
@@ -1,32 +0,0 @@
-import { SystemIcon } from "./icon.mjs";
-import { SystemIncrementer } from "./incrementer.mjs";
-import { SystemRange } from "./range.mjs";
-
-/**
- * A list of element classes to register, expects all of them to have a static
- * property of "elementName" that is the namespaced name that the component will
- * be registered under. Any elements that are formAssociated have their name added
- * to the "CONFIG.CACHE.componentListeners" array and should be listened to for
- * "change" events in sheets.
- */
-const components = [
- SystemIcon,
- SystemIncrementer,
- SystemRange,
-];
-
-export function registerCustomComponents() {
- (CONFIG.CACHE ??= {}).componentListeners ??= [];
- for (const component of components) {
- if (!window.customElements.get(component.elementName)) {
- console.debug(`${game.system.id} | Registering component "${component.elementName}"`);
- window.customElements.define(
- component.elementName,
- component,
- );
- if (component.formAssociated) {
- CONFIG.CACHE.componentListeners.push(component.elementName);
- }
- };
- };
-};
diff --git a/src/components/icon.mjs b/src/components/icon.mjs
deleted file mode 100644
index 430076d..0000000
--- a/src/components/icon.mjs
+++ /dev/null
@@ -1,125 +0,0 @@
-import { StyledShadowElement } from "./mixins/Styles.mjs";
-
-/**
-Attributes:
-@property {string} name - The name of the icon, takes precedence over the path
-@property {string} path - The path of the icon file
-*/
-export class SystemIcon extends StyledShadowElement(HTMLElement) {
- static elementName = `dd-icon`;
- static formAssociated = false;
-
- /* Stuff for the mixin to use */
- static _stylePath = ``;
-
-
- static _cache = new Map();
- #container;
- /** @type {null | string} */
- _name;
- /** @type {null | string} */
- _path;
-
- /* Stored IDs for all of the hooks that are in this component */
- #svgHmr;
-
- constructor() {
- super();
- // this._shadow = this.attachShadow({ mode: `open`, delegatesFocus: true });
-
- this.#container = document.createElement(`div`);
- this._shadow.appendChild(this.#container);
- };
-
- _mounted = false;
- async connectedCallback() {
- super.connectedCallback();
- if (this._mounted) { return }
-
- this._name = this.getAttribute(`name`);
- this._path = this.getAttribute(`path`);
-
- /*
- This converts all of the double-dash prefixed properties on the element to
- CSS variables so that they don't all need to be provided by doing style=""
- */
- for (const attrVar of this.attributes) {
- if (attrVar.name?.startsWith(`var:`)) {
- const prop = attrVar.name.replace(`var:`, ``);
- this.style.setProperty(`--` + prop, attrVar.value);
- };
- };
-
- /*
- Try to retrieve the icon if it isn't present, try the path then default to
- the slot content, as then we can have a default per-icon usage
- */
- let content;
- if (this._name) {
- content = await this.#getIcon(`./systems/dotdungeon/assets/${this._name}.svg`);
- };
-
- if (this._path && !content) {
- content = await this.#getIcon(this._path);
- };
-
- if (content) {
- this.#container.appendChild(content.cloneNode(true));
- };
-
- /*
- This is so that when we get an HMR event from Foundry we can appropriately
- handle it using our logic to update the component and the icon cache.
- */
- if (game.settings.get(game.system.id, `devMode`)) {
- this.#svgHmr = Hooks.on(`${game.system.id}-hmr:svg`, (iconName, data) => {
- if (this._name === iconName || this._path?.endsWith(data.path)) {
- const svg = this.#parseSVG(data.content);
- this.constructor._cache.set(iconName, svg);
- this.#container.replaceChildren(svg.cloneNode(true));
- };
- });
- };
-
- this._mounted = true;
- };
-
- disconnectedCallback() {
- super.disconnectedCallback();
- if (!this._mounted) { return }
-
- Hooks.off(`${game.system.id}-hmr:svg`, this.#svgHmr);
-
- this._mounted = false;
- };
-
- async #getIcon(path) {
- // Cache hit!
- if (this.constructor._cache.has(path)) {
- Logger.debug(`Icon ${path} cache hit`);
- return this.constructor._cache.get(path);
- };
-
- const r = await fetch(path);
- switch (r.status) {
- case 200:
- case 201:
- break;
- default:
- Logger.error(`Failed to fetch icon: ${path}`);
- return;
- };
-
- Logger.debug(`Adding icon ${path} to the cache`);
- const svg = this.#parseSVG(await r.text());
- this.constructor._cache.set(path, svg);
- return svg;
- };
-
- /** Takes an SVG string and returns it as a DOM node */
- #parseSVG(content) {
- const temp = document.createElement(`div`);
- temp.innerHTML = content;
- return temp.querySelector(`svg`);
- };
-};
diff --git a/src/components/incrementer.mjs b/src/components/incrementer.mjs
deleted file mode 100644
index cde1cec..0000000
--- a/src/components/incrementer.mjs
+++ /dev/null
@@ -1,153 +0,0 @@
-import { StyledShadowElement } from "./mixins/Styles.mjs";
-import { SystemIcon } from "./icon.mjs";
-
-/**
-Attributes:
-@property {string} name - The path to the value to update
-@property {number} value - The actual value of the input
-@property {number} min - The minimum value of the input
-@property {number} max - The maximum value of the input
-@property {number?} smallStep - The step size used for the buttons and arrow keys
-@property {number?} largeStep - The step size used for the buttons + Ctrl and page up / down
-
-Styling:
-- `--height`: Controls the height of the element + the width of the buttons (default: 1.25rem)
-- `--width`: Controls the width of the number input (default 50px)
-*/
-export class SystemIncrementer extends StyledShadowElement(HTMLElement) {
- static elementName = `dd-incrementer`;
- static formAssociated = true;
-
- static _stylePath = `v1/components/incrementer.scss`;
-
- _internals;
- #input;
-
- _min;
- _max;
- _smallStep;
- _largeStep;
-
- constructor() {
- super();
-
- // Form internals
- this._internals = this.attachInternals();
- this._internals.role = `spinbutton`;
- };
-
- get form() {
- return this._internals.form;
- }
-
- get name() {
- return this.getAttribute(`name`);
- }
- set name(value) {
- this.setAttribute(`name`, value);
- }
-
- get value() {
- return this.getAttribute(`value`);
- };
- set value(value) {
- this.setAttribute(`value`, value);
- };
-
- get type() {
- return `number`;
- }
-
- connectedCallback() {
- super.connectedCallback();
- this.replaceChildren();
-
- // Attribute parsing / registration
- const value = this.getAttribute(`value`);
- this._min = parseInt(this.getAttribute(`min`) ?? 0);
- this._max = parseInt(this.getAttribute(`max`) ?? 0);
- this._smallStep = parseInt(this.getAttribute(`smallStep`) ?? 1);
- this._largeStep = parseInt(this.getAttribute(`largeStep`) ?? 5);
-
- this._internals.ariaValueMin = this._min;
- this._internals.ariaValueMax = this._max;
-
- const container = document.createElement(`div`);
-
- // The input that the user can see / modify
- const input = document.createElement(`input`);
- this.#input = input;
- input.type = `number`;
- input.ariaHidden = true;
- input.min = this.getAttribute(`min`);
- input.max = this.getAttribute(`max`);
- input.addEventListener(`change`, this.#updateValue.bind(this));
- input.value = value;
-
- // plus button
- const increment = document.createElement(SystemIcon.elementName);
- increment.setAttribute(`name`, `ui/plus`);
- increment.setAttribute(`var:size`, `0.75rem`);
- increment.setAttribute(`var:fill`, `currentColor`);
- increment.ariaHidden = true;
- increment.classList.value = `increment`;
- increment.addEventListener(`mousedown`, this.#increment.bind(this));
-
- // minus button
- const decrement = document.createElement(SystemIcon.elementName);
- decrement.setAttribute(`name`, `ui/minus`);
- decrement.setAttribute(`var:size`, `0.75rem`);
- decrement.setAttribute(`var:fill`, `currentColor`);
- decrement.ariaHidden = true;
- decrement.classList.value = `decrement`;
- decrement.addEventListener(`mousedown`, this.#decrement.bind(this));
-
- // Construct the DOM
- container.appendChild(decrement);
- container.appendChild(input);
- container.appendChild(increment);
- this._shadow.appendChild(container);
-
- /*
- This converts all of the namespace prefixed properties on the element to
- CSS variables so that they don't all need to be provided by doing style=""
- */
- for (const attrVar of this.attributes) {
- if (attrVar.name?.startsWith(`var:`)) {
- const prop = attrVar.name.replace(`var:`, ``);
- this.style.setProperty(`--` + prop, attrVar.value);
- };
- };
- };
-
- #updateValue() {
- let value = parseInt(this.#input.value);
- if (this.getAttribute(`min`)) {
- value = Math.max(this._min, value);
- }
- if (this.getAttribute(`max`)) {
- value = Math.min(this._max, value);
- }
- this.#input.value = value;
- this.value = value;
- this.dispatchEvent(new Event(`change`, { bubbles: true }));
- };
-
- /** @param {Event} $e */
- #increment($e) {
- $e.preventDefault();
- let value = parseInt(this.#input.value);
- value += $e.ctrlKey ? this._largeStep : this._smallStep;
- this.#input.value = value;
- this.#updateValue();
- };
-
- /** @param {Event} $e */
- #decrement($e) {
- $e.preventDefault();
- let value = parseInt(this.#input.value);
- value -= $e.ctrlKey ? this._largeStep : this._smallStep;
- this.#input.value = value;
- this.#updateValue();
- };
-};
diff --git a/src/components/mixins/Styles.mjs b/src/components/mixins/Styles.mjs
deleted file mode 100644
index b76dd29..0000000
--- a/src/components/mixins/Styles.mjs
+++ /dev/null
@@ -1,80 +0,0 @@
-/**
- * @param {HTMLElement} Base
- */
-export function StyledShadowElement(Base) {
- return class extends Base {
- /**
- * The path to the CSS that is loaded
- * @type {string}
- */
- static _stylePath;
-
- /**
- * The stringified CSS to use
- * @type {string}
- */
- static _styles;
-
- /**
- * The HTML element of the stylesheet
- * @type {HTMLStyleElement}
- */
- _style;
-
- /** @type {ShadowRoot} */
- _shadow;
-
- /**
- * The hook ID for this element's CSS hot reload
- * @type {number}
- */
- #cssHmr;
-
- constructor() {
- super();
-
- this._shadow = this.attachShadow({ mode: `open` });
- this._style = document.createElement(`style`);
- this._shadow.appendChild(this._style);
- };
-
- #mounted = false;
- connectedCallback() {
- if (this.#mounted) { return }
-
- this._getStyles();
-
- if (game.settings.get(`dotdungeon`, `devMode`)) {
- this.#cssHmr = Hooks.on(`dd-hmr:css`, (data) => {
- if (data.path.endsWith(this.constructor._stylePath)) {
- this._style.innerHTML = data.content;
- };
- });
- };
-
- this.#mounted = true;
- };
-
- disconnectedCallback() {
- if (!this.#mounted) { return }
- if (this.#cssHmr != null) {
- Hooks.off(`dd-hmr:css`, this.#cssHmr);
- this.#cssHmr = null;
- };
- this.#mounted = false;
- };
-
- _getStyles() {
- if (this.constructor._styles) {
- this._style.innerHTML = this.constructor._styles;
- } else {
- fetch(`./systems/dotdungeon/.styles/${this.constructor._stylePath}`)
- .then(r => r.text())
- .then(t => {
- this.constructor._styles = t;
- this._style.innerHTML = t;
- });
- }
- };
- };
-};
diff --git a/src/components/range.mjs b/src/components/range.mjs
deleted file mode 100644
index f40b985..0000000
--- a/src/components/range.mjs
+++ /dev/null
@@ -1,138 +0,0 @@
-import { StyledShadowElement } from "./mixins/Styles.mjs";
-
-/**
-Attributes:
-@property {string} name - The path to the value to update in the datamodel
-@property {number} value - The actual value of the input
-@property {number} max - The maximum value that this range has
-
-@extends {HTMLElement}
-*/
-export class SystemRange
- extends StyledShadowElement(
- HTMLElement,
- { mode: `open`, delegatesFocus: true },
- ) {
- static elementName = `dd-range`;
- static formAssociated = true;
-
- static observedAttributes = [`max`];
-
- static _stylePath = `v3/components/range.css`;
-
- _internals;
- #input;
-
- constructor() {
- super();
-
- // Form internals
- this._internals = this.attachInternals();
- this._internals.role = `spinbutton`;
- };
-
- get form() {
- return this._internals.form;
- };
-
- get name() {
- return this.getAttribute(`name`);
- };
- set name(value) {
- this.setAttribute(`name`, value);
- };
-
- get value() {
- try {
- return parseInt(this.getAttribute(`value`));
- } catch {
- throw new Error(`Failed to parse attribute: "value" - Make sure it's an integer`);
- };
- };
- set value(value) {
- this.setAttribute(`value`, value);
- };
-
- get max() {
- try {
- return parseInt(this.getAttribute(`max`));
- } catch {
- throw new Error(`Failed to parse attribute: "max" - Make sure it's an integer`);
- };
- };
- set max(value) {
- this.setAttribute(`max`, value);
- };
-
- get type() {
- return `number`;
- };
-
- connectedCallback() {
- super.connectedCallback();
-
- // Attribute validation
- if (!this.hasAttribute(`max`)) {
- throw new Error(`dotdungeon | Cannot have a range without a maximum value`);
- };
-
- // Keyboard accessible input for the thing
- this.#input = document.createElement(`input`);
- this.#input.type = `number`;
- this.#input.min = 0;
- this.#input.max = this.max;
- this.#input.value = this.value;
- this.#input.addEventListener(`change`, () => {
- const inputValue = parseInt(this.#input.value);
- if (inputValue === this.value) { return };
- this._updateValue.bind(this)(Math.sign(this.value - inputValue));
- this._updateValue(Math.sign(this.value - inputValue));
- });
- this._shadow.appendChild(this.#input);
-
- // Shadow-DOM construction
- this._elements = new Array(this.max);
- const container = document.createElement(`div`);
- container.classList.add(`container`);
-
- // Creating the node for filled content
- const filledContainer = document.createElement(`div`);
- filledContainer.classList.add(`range-increment`, `filled`);
- const filledNode = this.querySelector(`[slot="filled"]`);
- if (filledNode) { filledContainer.appendChild(filledNode) };
-
- const emptyContainer = document.createElement(`div`);
- emptyContainer.classList.add(`range-increment`, `empty`);
- const emptyNode = this.querySelector(`[slot="empty"]`);
- if (emptyNode) { emptyContainer.appendChild(emptyNode) };
-
- this._elements.fill(filledContainer, 0, this.value);
- this._elements.fill(emptyContainer, this.value);
- container.append(...this._elements.map((slot, i) => {
- const node = slot.cloneNode(true);
- node.setAttribute(`data-index`, i + 1);
- node.addEventListener(`click`, () => {
- const filled = node.classList.contains(`filled`);
- this._updateValue(filled ? -1 : 1);
- });
- return node;
- }));
- this._shadow.appendChild(container);
-
- /*
- This converts all of the namespace prefixed properties on the element to
- CSS variables so that they don't all need to be provided by doing style=""
- */
- for (const attrVar of this.attributes) {
- if (attrVar.name?.startsWith(`var:`)) {
- const prop = attrVar.name.replace(`var:`, ``);
- this.style.setProperty(`--` + prop, attrVar.value);
- };
- };
- };
-
- _updateValue(delta) {
- this.value += delta;
- this.dispatchEvent(new Event(`change`, { bubbles: true }));
- };
-};
diff --git a/src/consts.mjs b/src/consts.mjs
deleted file mode 100644
index e69de29..0000000
diff --git a/src/documents/ActiveEffect/_proxy.mjs b/src/documents/ActiveEffect/_proxy.mjs
deleted file mode 100644
index 6f75118..0000000
--- a/src/documents/ActiveEffect/_proxy.mjs
+++ /dev/null
@@ -1,11 +0,0 @@
-import { createDocumentProxy } from "../../utils/createDocumentProxy.mjs";
-
-/**
- * An object of Foundry-types to in-code Document classes.
- */
-const classes = {};
-
-/** The class that will be used if no type-specific class is defined */
-const defaultClass = ActiveEffect;
-
-export const ActiveEffectProxy = createDocumentProxy(defaultClass, classes);
diff --git a/src/documents/Actor/Player/Document.mjs b/src/documents/Actor/Player/Document.mjs
deleted file mode 100644
index 287e30f..0000000
--- a/src/documents/Actor/Player/Document.mjs
+++ /dev/null
@@ -1,5 +0,0 @@
-export class Player extends Actor {
- getRollData() {
- return this.system;
- };
-};
diff --git a/src/documents/Actor/Player/Model.mjs b/src/documents/Actor/Player/Model.mjs
deleted file mode 100644
index b141450..0000000
--- a/src/documents/Actor/Player/Model.mjs
+++ /dev/null
@@ -1,12 +0,0 @@
-export class PlayerData extends foundry.abstract.TypeDataModel {
- static defineSchema() {
- const fields = foundry.data.fields;
- return {
- content: new fields.HTMLField({
- blank: true,
- trim: true,
- initial: ``,
- }),
- };
- };
-};
diff --git a/src/documents/Actor/_proxy.mjs b/src/documents/Actor/_proxy.mjs
deleted file mode 100644
index 6e7eb18..0000000
--- a/src/documents/Actor/_proxy.mjs
+++ /dev/null
@@ -1,11 +0,0 @@
-import { createDocumentProxy } from "../../utils/createDocumentProxy.mjs";
-
-/**
- * An object of Foundry-types to in-code Document classes.
- */
-const classes = {};
-
-/** The class that will be used if no type-specific class is defined */
-const defaultClass = Actor;
-
-export const ActorProxy = createDocumentProxy(defaultClass, classes);
diff --git a/src/documents/ChatMessage/_proxy.mjs b/src/documents/ChatMessage/_proxy.mjs
deleted file mode 100644
index 44b44c7..0000000
--- a/src/documents/ChatMessage/_proxy.mjs
+++ /dev/null
@@ -1,11 +0,0 @@
-import { createDocumentProxy } from "../../utils/createDocumentProxy.mjs";
-
-/**
- * An object of Foundry-types to in-code Document classes.
- */
-const classes = {};
-
-/** The class that will be used if no type-specific class is defined */
-const defaultClass = ChatMessage;
-
-export const ChatMessageProxy = createDocumentProxy(defaultClass, classes);
diff --git a/src/documents/Item/_proxy.mjs b/src/documents/Item/_proxy.mjs
deleted file mode 100644
index 8f01bc6..0000000
--- a/src/documents/Item/_proxy.mjs
+++ /dev/null
@@ -1,11 +0,0 @@
-import { createDocumentProxy } from "../../utils/createDocumentProxy.mjs";
-
-/**
- * An object of Foundry-types to in-code Document classes.
- */
-const classes = {};
-
-/** The class that will be used if no type-specific class is defined */
-const defaultClass = Item;
-
-export const ItemProxy = createDocumentProxy(defaultClass, classes);
diff --git a/src/helpers/_index.mjs b/src/helpers/_index.mjs
deleted file mode 100644
index 8d5ed41..0000000
--- a/src/helpers/_index.mjs
+++ /dev/null
@@ -1,18 +0,0 @@
-import { handlebarsLocalizer, localizer } from "../utils/localizer.mjs";
-import { options } from "./options.mjs";
-
-export function registerHandlebarsHelpers() {
- const helperPrefix = game.system.id;
-
- return {
- // MARK: Complex helpers
- [`${helperPrefix}-i18n`]: handlebarsLocalizer,
- [`${helperPrefix}-options`]: options,
-
- // MARK: Simple helpers
- [`${helperPrefix}-stringify`]: v => JSON.stringify(v, null, ` `),
- [`${helperPrefix}-empty`]: v => v.length == 0,
- [`${helperPrefix}-set-has`]: (s, k) => s.has(k),
- [`${helperPrefix}-empty-state`]: (v) => v ?? localizer(`${game.system.id}.common.empty`),
- };
-};
diff --git a/src/helpers/options.mjs b/src/helpers/options.mjs
deleted file mode 100644
index b0df8d0..0000000
--- a/src/helpers/options.mjs
+++ /dev/null
@@ -1,35 +0,0 @@
-import { localizer } from "../utils/localizer.mjs";
-
-/**
- * @typedef {object} Option
- * @property {string} [label]
- * @property {string|number} value
- * @property {boolean} [disabled]
- */
-
-/**
- * @param {string | number} selected
- * @param {Array} opts
- */
-export function options(selected, opts, meta) {
- const { localize = false } = meta.hash;
- selected = Handlebars.escapeExpression(selected);
- const htmlOptions = [];
-
- for (let opt of opts) {
- if (foundry.utils.getType(opt) === `string`) {
- opt = { label: opt, value: opt };
- };
- opt.value = Handlebars.escapeExpression(opt.value);
- htmlOptions.push(
- `
- ${localize ? localizer(opt.label) : opt.label}
- `,
- );
- };
- return htmlOptions.join(`\n`);
-};
diff --git a/src/hooks/hotReload.mjs b/src/hooks/hotReload.mjs
deleted file mode 100644
index 0320dd8..0000000
--- a/src/hooks/hotReload.mjs
+++ /dev/null
@@ -1,18 +0,0 @@
-const loaders = {
- svg(data) {
- const iconName = data.path.split(`/`).slice(-1)[0].slice(0, -4);
- Logger.debug(`hot-reloading icon: ${iconName}`);
- Hooks.call(`${game.system.id}-hmr:svg`, iconName, data);
- },
- js() {window.location.reload()},
- mjs() {window.location.reload()},
- css(data) {
- Logger.debug(`Hot-reloading CSS: ${data.path}`);
- Hooks.call(`${game.system.id}-hmr:css`, data);
- },
-};
-
-Hooks.on(`hotReload`, async (data) => {
- if (!loaders[data.extension]) {return}
- return loaders[data.extension](data);
-});
diff --git a/src/hooks/renderChatMessage.mjs b/src/hooks/renderChatMessage.mjs
deleted file mode 100644
index 496a135..0000000
--- a/src/hooks/renderChatMessage.mjs
+++ /dev/null
@@ -1,18 +0,0 @@
-Hooks.on(`renderChatMessage`, (msg, html) => {
-
- // Short-Circuit when the flag isn't set for the message
- if (msg.getFlag(`taf`, `rollModedContent`)) {
- return;
- }
-
- const featureFlagEnabled = taf.FEATURES.ROLL_MODE_CONTENT;
-
- const contentElement = html.find(`.message-content`)[0];
- let content = contentElement.innerHTML;
- if (featureFlagEnabled && msg.blind && !game.user.isGM) {
- content = content.replace(/-=.*?=-/gm, `??`);
- } else {
- content = content.replace(/-=|=-/gm, ``);
- }
- contentElement.innerHTML = content;
-});
diff --git a/src/main.mjs b/src/main.mjs
deleted file mode 100644
index 0314ffe..0000000
--- a/src/main.mjs
+++ /dev/null
@@ -1,64 +0,0 @@
-// Document Imports
-import { ActiveEffectProxy } from "./documents/ActiveEffect/_proxy.mjs";
-import { ActorProxy } from "./documents/Actor/_proxy.mjs";
-import { ChatMessageProxy } from "./documents/ChatMessage/_proxy.mjs";
-import { ItemProxy } from "./documents/Item/_proxy.mjs";
-
-// DataModel Imports
-import { PlayerData } from "./documents/Actor/Player/Model.mjs";
-
-// Hook Imports
-import "./hooks/renderChatMessage.mjs";
-import "./hooks/hotReload.mjs";
-
-// Misc Imports
-import "./utils/globalTaf.mjs";
-import "./utils/logger.mjs";
-import "./utils/DialogManager.mjs";
-import { registerCustomComponents } from "./components/_index.mjs";
-import { registerHandlebarsHelpers } from "./helpers/_index.mjs";
-import { registerSettings } from "./settings/_index.mjs";
-import { registerSheets } from "./sheets/_index.mjs";
-
-// MARK: init hook
-Hooks.once(`init`, () => {
- Logger.info(`Initializing`);
- CONFIG.ActiveEffect.legacyTransferral = false;
-
- registerSettings();
-
- // Data Models
- CONFIG.Actor.dataModels.player = PlayerData;
-
- // Update document classes
- CONFIG.Actor.documentClass = ActorProxy;
- CONFIG.Item.documentClass = ItemProxy;
- CONFIG.ActiveEffect.documentClass = ActiveEffectProxy;
- CONFIG.ChatMessage.documentClass = ChatMessageProxy;
- registerSheets();
-
- registerHandlebarsHelpers();
-
- registerCustomComponents();
-});
-
-
-// MARK: ready hook
-Hooks.once(`ready`, () => {
- Logger.info(`Ready`);
-
- let defaultTab = game.settings.get(game.system.id, `defaultTab`);
- if (defaultTab) {
- if (!ui.sidebar?.tabs?.[defaultTab]) {
- Logger.error(`Couldn't find a sidebar tab with ID:`, defaultTab);
- } else {
- Logger.debug(`Switching sidebar tab to:`, defaultTab);
- ui.sidebar.tabs[defaultTab].activate();
- };
- };
-
- if (game.settings.get(game.system.id, `devMode`)) {
- console.log(`%cFeature Flags:`, `color: #00aa00; font-style: bold; font-size: 1.5rem;`);
- Logger.table(taf.FEATURES);
- };
-});
diff --git a/src/settings/_index.mjs b/src/settings/_index.mjs
deleted file mode 100644
index 0f65987..0000000
--- a/src/settings/_index.mjs
+++ /dev/null
@@ -1,10 +0,0 @@
-import { registerClientSettings } from "./client_settings.mjs";
-import { registerDevSettings } from "./dev_settings.mjs";
-import { registerWorldSettings } from "./world_settings.mjs";
-
-export function registerSettings() {
- Logger.debug(`Registering settings`);
- registerClientSettings();
- registerWorldSettings();
- registerDevSettings();
-};
diff --git a/src/settings/client_settings.mjs b/src/settings/client_settings.mjs
deleted file mode 100644
index 7dd2708..0000000
--- a/src/settings/client_settings.mjs
+++ /dev/null
@@ -1,2 +0,0 @@
-export function registerClientSettings() {
-};
diff --git a/src/settings/dev_settings.mjs b/src/settings/dev_settings.mjs
deleted file mode 100644
index 80235b1..0000000
--- a/src/settings/dev_settings.mjs
+++ /dev/null
@@ -1,25 +0,0 @@
-export function registerDevSettings() {
- const isLocalhost = window.location.hostname === `localhost`;
-
- game.settings.register(game.system.id, `devMode`, {
- name: `Dev Mode?`,
- scope: `client`,
- type: Boolean,
- config: isLocalhost,
- default: false,
- requiresReload: false,
- });
-
- game.settings.register(game.system.id, `defaultTab`, {
- name: `Default Sidebar Tab`,
- scope: `client`,
- type: String,
- config: isLocalhost,
- requiresReload: false,
- onChange(value) {
- if (!ui.sidebar.tabs[value]) {
- ui.notifications.warn(`"${value}" cannot be found in the sidebar tabs, it may not work at reload.`);
- }
- },
- });
-};
diff --git a/src/settings/world_settings.mjs b/src/settings/world_settings.mjs
deleted file mode 100644
index eb3aad7..0000000
--- a/src/settings/world_settings.mjs
+++ /dev/null
@@ -1,2 +0,0 @@
-export function registerWorldSettings() {
-};
diff --git a/src/sheets/Player/v1.mjs b/src/sheets/Player/v1.mjs
deleted file mode 100644
index 8e775f1..0000000
--- a/src/sheets/Player/v1.mjs
+++ /dev/null
@@ -1,28 +0,0 @@
-import { SizeStorable } from "../mixins/SizeStorable.mjs";
-
-export class PlayerSheetv1 extends SizeStorable(ActorSheet) {
- static get defaultOptions() {
- let opts = foundry.utils.mergeObject(
- super.defaultOptions,
- {
- template: `systems/${game.system.id}/templates/Player/v1/main.hbs`,
- classes: [],
- },
- );
- opts.classes = [`actor--player`, `style-v1`];
- return opts;
- };
-
- async getData() {
- const ctx = {};
-
- ctx.editable = this.isEditable;
-
- const actor = ctx.actor = this.actor;
- ctx.system = actor.system;
- ctx.enriched = { system: {} };
- ctx.enriched.system.content = await TextEditor.enrichHTML(actor.system.content);
-
- return ctx;
- };
-}
diff --git a/src/sheets/_index.mjs b/src/sheets/_index.mjs
deleted file mode 100644
index 073a018..0000000
--- a/src/sheets/_index.mjs
+++ /dev/null
@@ -1,11 +0,0 @@
-import { PlayerSheetv1 } from "./Player/v1.mjs";
-
-export function registerSheets() {
- Logger.debug(`Registering sheets`);
-
- Actors.registerSheet(game.system.id, PlayerSheetv1, {
- makeDefault: true,
- types: [`player`],
- label: `Hello`,
- });
-};
diff --git a/src/sheets/mixins/SizeStorable.mjs b/src/sheets/mixins/SizeStorable.mjs
deleted file mode 100644
index 38ba887..0000000
--- a/src/sheets/mixins/SizeStorable.mjs
+++ /dev/null
@@ -1,125 +0,0 @@
-import { DialogManager } from "../../utils/DialogManager.mjs";
-
-/**
- * This mixin allows making a class so that it can store the width/height data
- * to the sheet or localhost in order to make using the text sheets a lil nicer.
- *
- * @param {ActorSheet|ItemSheet} cls The Sheet class to augment
- * @returns The augmented class
- */
-export function SizeStorable(cls) {
-
- // Don't augment class when the feature isn't enabled
- if (!taf.FEATURES.STORABLE_SHEET_SIZE) {
- return cls;
- }
-
- return class SizeStorableClass extends cls {
- constructor(doc, opts) {
-
- /*
- Find the saved size of the sheet, it takes the following order of precedence
- from highest to lowest:
- - Locally saved
- - Default values on actor
- - Default values from constructor
- */
- /** @type {string|undefined} */
- let size = localStorage.getItem(`${game.system.id}.size:${doc.uuid}`);
- size ??= doc.getFlag(game.system.id, `size`);
-
- // Apply the saved value to the options
- if (size) {
- const [ width, height ] = size.split(`,`);
- opts.width = width;
- opts.height = height;
- };
-
- super(doc, opts);
- };
-
- get hasLocalSize() {
- return localStorage.getItem(`${game.system.id}.size:${this.object.uuid}`) != null;
- };
-
- get hasGlobalSize() {
- return this.object.getFlag(game.system.id, `size`) != null;
- };
-
- _getHeaderButtons() {
- return [
- {
- class: `size-save`,
- icon: `fa-solid fa-floppy-disk`,
- label: `Save Size`,
- onclick: () => {
-
- const buttons = {
- saveGlobal: {
- label: `Save Global Size`,
- callback: () => {
- this.object.setFlag(
- game.system.id,
- `size`,
- `${this.position.width},${this.position.height}`,
- );
- },
- },
- saveLocal: {
- label: `Save For Me Only`,
- callback: () => {
- localStorage.setItem(
- `${game.system.id}.size:${this.object.uuid}`,
- `${this.position.width},${this.position.height}`,
- );
- },
- },
- };
-
- // Add resets if there is a size already
- if (this.hasGlobalSize) {
- buttons.resetGlobal = {
- label: `Reset Global Size`,
- callback: () => {
- this.object.unsetFlag(game.system.id, `size`);
- },
- };
- };
-
- if (this.hasLocalSize) {
- buttons.resetLocal = {
- label: `Reset Size For Me Only`,
- callback: () => {
- localStorage.removeItem(`${game.system.id}.size:${this.object.uuid}`);
- },
- };
- };
-
- // When a non-GM is using this system, we only want to save local sizes
- if (!game.user.isGM) {
- delete buttons.saveGlobal;
- delete buttons.resetGlobal;
- };
-
- DialogManager.createOrFocus(
- `${this.object.uuid}:size-save`,
- {
- title: `Save size of sheet: ${this.title}`,
- content: `Saving the size of this sheet will cause it to open at the size it is when you press the save button`,
- buttons,
- render: (html) => {
- const el = html[2];
- el.style = `display: grid; grid-template-columns: 1fr 1fr; gap: 8px;`;
- },
- },
- {
- jQuery: true,
- },
- );
- },
- },
- ...super._getHeaderButtons(),
- ];
- };
- };
-};
diff --git a/src/utils/DialogManager.mjs b/src/utils/DialogManager.mjs
deleted file mode 100644
index 6413abf..0000000
--- a/src/utils/DialogManager.mjs
+++ /dev/null
@@ -1,178 +0,0 @@
-import { localizer } from "./localizer.mjs";
-
-/**
- * A utility class that allows managing Dialogs that are created for various
- * purposes such as deleting items, help popups, etc. This is a singleton class
- * that upon instantiating after the first time will just return the first instance
- */
-export class DialogManager {
-
- /** @type {Map} */
- static #dialogs = new Map();
-
- /**
- * Focuses a dialog if it already exists, or creates a new one and renders it.
- *
- * @param {string} dialogId The ID to associate with the dialog, should be unique
- * @param {object} data The data to pass to the Dialog constructor
- * @param {DialogOptions} opts The options to pass to the Dialog constructor
- * @returns {Dialog} The Dialog instance
- */
- static async createOrFocus(dialogId, data, opts = {}) {
- if (DialogManager.#dialogs.has(dialogId)) {
- const dialog = DialogManager.#dialogs.get(dialogId);
- dialog.bringToTop();
- return dialog;
- };
-
- /*
- This makes sure that if I provide a close function as a part of the data,
- that the dialog still gets removed from the set once it's closed, otherwise
- it could lead to dangling references that I don't care to keep. Or if I don't
- provide the close function, it just sets the function as there isn't anything
- extra that's needed to be called.
- */
- if (data?.close) {
- const provided = data.close;
- data.close = () => {
- DialogManager.#dialogs.delete(dialogId);
- provided();
- };
- } else {
- data.close = () => DialogManager.#dialogs.delete(dialogId);
- };
-
- // Create the Dialog with the modified data
- const dialog = new Dialog(data, opts);
- DialogManager.#dialogs.set(dialogId, dialog);
- dialog.render(true);
- return dialog;
- };
-
- /**
- * Closes a dialog if it is rendered
- *
- * @param {string} dialogId The ID of the dialog to close
- */
- static async close(dialogId) {
- const dialog = DialogManager.#dialogs.get(dialogId);
- dialog?.close();
- };
-
- static async helpDialog(
- helpId,
- helpContent,
- helpTitle = `dotdungeon.common.help`,
- localizationData = {},
- ) {
- DialogManager.createOrFocus(
- helpId,
- {
- title: localizer(helpTitle, localizationData),
- content: localizer(helpContent, localizationData),
- buttons: {},
- },
- { resizable: true },
- );
- };
-
- /**
- * Asks the user to provide a simple piece of information, this is primarily
- * intended to be used within macros so that it can have better info gathering
- * as needed. This returns an object of input keys/labels to the value the user
- * input for that label, if there is only one input, this will return the value
- * without an object wrapper, allowing for easier access.
- */
- static async ask(data, opts = {}) {
- if (!data.id) {
- throw new Error(`Asking the user for input must contain an ID`);
- }
- if (!data.inputs.length) {
- throw new Error(`Must include at least one input specification when prompting the user`);
- }
-
- let autofocusClaimed = false;
- for (const i of data.inputs) {
- i.id ??= foundry.utils.randomID(16);
- i.inputType ??= `text`;
-
- // Only ever allow one input to claim autofocus
- i.autofocus &&= !autofocusClaimed;
- autofocusClaimed ||= i.autofocus;
-
- // Set the value's attribute name if it isn't specified explicitly
- if (!i.valueAttribute) {
- switch (i.inputType) {
- case `checkbox`:
- i.valueAttribute = `checked`;
- break;
- default:
- i.valueAttribute = `value`;
- };
- };
- };
-
- opts.jQuery = true;
- data.default ??= `confirm`;
- data.title ??= `System Question`;
-
- data.content = await renderTemplate(
- `systems/${game.system.id}/templates/Dialogs/ask.hbs`,
- data,
- );
-
- return new Promise((resolve, reject) => {
- DialogManager.createOrFocus(
- data.id,
- {
- ...data,
- buttons: {
- confirm: {
- label: `Confirm`,
- callback: (html) => {
- const answers = {};
-
- /*
- Retrieve the answer for every input provided using the ID
- determined during initial data prep, and assign the value
- to the property of the label in the object.
- */
- for (const i of data.inputs) {
- const element = html.find(`#${i.id}`)[0];
- let value = element.value;
- switch (i.inputType) {
- case `number`:
- value = parseFloat(value);
- break;
- case `checkbox`:
- value = element.checked;
- break;
- }
- Logger.debug(`Ask response: ${value} (type: ${typeof value})`);
- answers[i.key ?? i.label] = value;
- if (data.inputs.length === 1) {
- resolve(value);
- return;
- }
- }
-
- resolve(answers);
- },
- },
- cancel: {
- label: `Cancel`,
- callback: () => reject(`User cancelled the prompt`),
- },
- },
- },
- opts,
- );
- });
- };
-
- static get size() {
- return DialogManager.#dialogs.size;
- }
-};
-
-globalThis.DialogManager = DialogManager;
diff --git a/src/utils/createDocumentProxy.mjs b/src/utils/createDocumentProxy.mjs
deleted file mode 100644
index 08af7e6..0000000
--- a/src/utils/createDocumentProxy.mjs
+++ /dev/null
@@ -1,39 +0,0 @@
-export function createDocumentProxy(defaultClass, classes) {
- // eslint-disable-next-line func-names
- return new Proxy(function () {}, {
- construct(_target, args) {
- const [data] = args;
-
- if (!classes[data.type]) {
- return new defaultClass(...args);
- }
-
- return new classes[data.type](...args);
- },
- get(_target, prop, _receiver) {
-
- if ([`create`, `createDocuments`].includes(prop)) {
- return (data, options) => {
- if (data.constructor === Array) {
- return data.map(i => this.constructor.create(i, options));
- }
-
- if (!classes[data.type]) {
- return defaultClass.create(data, options);
- }
-
- return classes[data.type].create(data, options);
- };
- };
-
- if (prop == Symbol.hasInstance) {
- return (instance) => {
- if (instance instanceof defaultClass) {return true}
- return Object.values(classes).some(i => instance instanceof i);
- };
- };
-
- return defaultClass[prop];
- },
- });
-};
diff --git a/src/utils/feature_flags/rollModeMessageContent.mjs b/src/utils/feature_flags/rollModeMessageContent.mjs
deleted file mode 100644
index 67ed613..0000000
--- a/src/utils/feature_flags/rollModeMessageContent.mjs
+++ /dev/null
@@ -1,7 +0,0 @@
-export function hideMessageText(content) {
- const hideContent = taf.FEATURES.ROLL_MODE_CONTENT;
- if (hideContent) {
- return `-=${content}=-`;
- }
- return content;
-};
diff --git a/src/utils/globalTaf.mjs b/src/utils/globalTaf.mjs
deleted file mode 100644
index 899328e..0000000
--- a/src/utils/globalTaf.mjs
+++ /dev/null
@@ -1,18 +0,0 @@
-import { hideMessageText } from "./feature_flags/rollModeMessageContent.mjs";
-
-Object.defineProperty(
- globalThis,
- `taf`,
- {
- value: Object.freeze({
- utils: Object.freeze({
- hideMessageText,
- }),
- FEATURES: Object.preventExtensions({
- ROLL_MODE_CONTENT: false,
- STORABLE_SHEET_SIZE: false,
- }),
- }),
- writable: false,
- },
-);
diff --git a/src/utils/localizer.mjs b/src/utils/localizer.mjs
deleted file mode 100644
index 916e54d..0000000
--- a/src/utils/localizer.mjs
+++ /dev/null
@@ -1,45 +0,0 @@
-/** A handlebars helper that utilizes the recursive localizer */
-export function handlebarsLocalizer(key, ...args) {
- let data = args[0];
- if (args.length === 1) { data = args[0].hash }
- if (key instanceof Handlebars.SafeString) {key = key.toString()}
- const localized = localizer(key, data);
- return localized;
-};
-
-/**
- * A localizer that allows recursively localizing strings so that localized strings
- * that want to use other localized strings can.
- *
- * @param {string} key The localization key to retrieve
- * @param {object?} args The arguments provided to the localizer for replacement
- * @param {number?} depth The current depth of the localizer
- * @returns The localized string
- */
-export function localizer(key, args = {}, depth = 0) {
- /** @type {string} */
- let localized = game.i18n.format(key, args);
- const subkeys = localized.matchAll(/@(?[a-zA-Z.]+)/gm);
-
- // Short-cut to help prevent infinite recursion
- if (depth > 10) {
- return localized;
- };
-
- /*
- Helps prevent localization on the same key so that we aren't doing excess work.
- */
- const localizedSubkeys = new Map();
- for (const match of subkeys) {
- const subkey = match.groups.key;
- if (localizedSubkeys.has(subkey)) {continue}
- localizedSubkeys.set(subkey, localizer(subkey, args, depth + 1));
- };
-
- return localized.replace(
- /@(?[a-zA-Z.]+)/gm,
- (_fullMatch, subkey) => {
- return localizedSubkeys.get(subkey);
- },
- );
-};
diff --git a/styles/Apps/AttributeManager.css b/styles/Apps/AttributeManager.css
new file mode 100644
index 0000000..a36d35c
--- /dev/null
+++ b/styles/Apps/AttributeManager.css
@@ -0,0 +1,33 @@
+.taf.AttributeManager {
+ .attributes {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ }
+
+ .attribute {
+ display: grid;
+ grid-template-columns: 1fr auto auto;
+ align-items: center;
+ gap: 8px;
+ padding: 8px;
+ border: 1px solid rebeccapurple;
+ border-radius: 4px;
+
+ label {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ }
+ }
+
+ .controls {
+ display: flex;
+ flex-direction: row;
+ gap: 8px;
+
+ button {
+ flex-grow: 1;
+ }
+ }
+}
diff --git a/styles/Apps/PlayerSheet.css b/styles/Apps/PlayerSheet.css
new file mode 100644
index 0000000..cb81dbd
--- /dev/null
+++ b/styles/Apps/PlayerSheet.css
@@ -0,0 +1,57 @@
+.taf.PlayerSheet {
+ .sheet-header, fieldset, .content {
+ border-radius: 8px;
+ border: 1px solid rebeccapurple;
+ }
+
+ .sheet-header {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ gap: 0.5rem;
+ padding: 4px;
+
+ img {
+ border-radius: 4px;
+ }
+ }
+
+ .attributes {
+ display: flex;
+ flex-direction: row;
+ flex-wrap: wrap;
+ justify-content: space-around;
+ gap: 0.5rem;
+ }
+
+ .attr-range {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ gap: 4px;
+ width: 100px;
+
+ > input {
+ text-align: center;
+ }
+ }
+
+ .content {
+ flex-grow: 1;
+ overflow: hidden;
+ --table-row-color-odd: var(--table-header-bg-color);
+
+ &:not(:has(> prose-mirror)) {
+ padding: 0.5rem;
+ }
+ }
+
+ prose-mirror {
+ height: 100%;
+
+ menu {
+ border-bottom-left-radius: 0;
+ border-bottom-right-radius: 0;
+ }
+ }
+}
diff --git a/styles/Apps/common.css b/styles/Apps/common.css
new file mode 100644
index 0000000..ce56fdd
--- /dev/null
+++ b/styles/Apps/common.css
@@ -0,0 +1,8 @@
+.taf {
+ > .window-content {
+ padding: 0.5rem;
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+ }
+}
diff --git a/styles/elements/headers.css b/styles/elements/headers.css
new file mode 100644
index 0000000..2f59e8c
--- /dev/null
+++ b/styles/elements/headers.css
@@ -0,0 +1,5 @@
+.taf > .window-content {
+ h1, h2, h3, h4, h5, h6 {
+ margin: 0;
+ }
+}
diff --git a/styles/elements/input.css b/styles/elements/input.css
new file mode 100644
index 0000000..9b72811
--- /dev/null
+++ b/styles/elements/input.css
@@ -0,0 +1,6 @@
+.taf > .window-content input {
+ &.large {
+ --input-height: 2.5rem;
+ font-size: 1.75rem;
+ }
+}
diff --git a/styles/elements/p.css b/styles/elements/p.css
new file mode 100644
index 0000000..160b0d0
--- /dev/null
+++ b/styles/elements/p.css
@@ -0,0 +1,9 @@
+.taf > .window-content p {
+ &:first-child {
+ margin-top: 0;
+ }
+
+ &:last-child {
+ margin-bottom: 0;
+ }
+}
diff --git a/styles/elements/prose-mirror.css b/styles/elements/prose-mirror.css
new file mode 100644
index 0000000..8b6189d
--- /dev/null
+++ b/styles/elements/prose-mirror.css
@@ -0,0 +1,12 @@
+.taf > .window-content prose-mirror {
+ background: var(--prosemirror-background);
+
+ .editor-content {
+ padding: 0 8px 8px;
+ }
+
+ .tableWrapper th,
+ .tableWrapper td {
+ border-color: rebeccapurple;
+ }
+}
diff --git a/styles/main.css b/styles/main.css
new file mode 100644
index 0000000..a07aa4f
--- /dev/null
+++ b/styles/main.css
@@ -0,0 +1,16 @@
+@layer resets, themes, elements, components, partials, apps, exceptions;
+
+/* Themes */
+@import url("./themes/dark.css") layer(themes);
+@import url("./themes/light.css") layer(themes);
+
+/* Elements */
+@import url("./elements/headers.css") layer(elements);
+@import url("./elements/input.css") layer(elements);
+@import url("./elements/p.css") layer(elements);
+@import url("./elements/prose-mirror.css") layer(elements);
+
+/* Apps */
+@import url("./Apps/common.css") layer(apps);
+@import url("./Apps/PlayerSheet.css") layer(apps);
+@import url("./Apps/AttributeManager.css") layer(apps);
diff --git a/styles/resets/inputs.css b/styles/resets/inputs.css
new file mode 100644
index 0000000..aa6e6b7
--- /dev/null
+++ b/styles/resets/inputs.css
@@ -0,0 +1,5 @@
+.taf > .window-content {
+ button, input {
+ all: initial;
+ }
+}
diff --git a/styles/root.scss b/styles/root.scss
deleted file mode 100644
index 423849d..0000000
--- a/styles/root.scss
+++ /dev/null
@@ -1 +0,0 @@
-@use "./v1/index.scss";
\ No newline at end of file
diff --git a/styles/themes/dark.css b/styles/themes/dark.css
new file mode 100644
index 0000000..85103f7
--- /dev/null
+++ b/styles/themes/dark.css
@@ -0,0 +1,3 @@
+.theme-dark {
+ --prosemirror-background: var(--color-cool-5);
+}
diff --git a/styles/themes/light.css b/styles/themes/light.css
new file mode 100644
index 0000000..fee3812
--- /dev/null
+++ b/styles/themes/light.css
@@ -0,0 +1,3 @@
+.theme-light {
+ --prosemirror-background: white;
+}
diff --git a/styles/v1/Dialog.scss b/styles/v1/Dialog.scss
deleted file mode 100644
index 28f133f..0000000
--- a/styles/v1/Dialog.scss
+++ /dev/null
@@ -1,12 +0,0 @@
-.dialog-content:not(:only-child) {
- margin-bottom: 8px;
-}
-
-.dialog-content {
- p {
- margin: 0;
- }
- .prompt {
- margin-top: 8px;
- }
-}
diff --git a/styles/v1/components/common.scss b/styles/v1/components/common.scss
deleted file mode 100644
index 59f812d..0000000
--- a/styles/v1/components/common.scss
+++ /dev/null
@@ -1,7 +0,0 @@
-// Disclaimer: This CSS is used by a custom web component and is scoped to JUST
-// the corresponding web component. This should only be imported by web component
-// style files.
-
-:host {
- display: inline-block;
-}
diff --git a/styles/v1/components/icon.scss b/styles/v1/components/icon.scss
deleted file mode 100644
index 59a68a9..0000000
--- a/styles/v1/components/icon.scss
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
-Disclaimer: This CSS is used by a custom web component and is scoped to JUST
-the corresponding web component. Importing this into other files is forbidden
-*/
-
-$default-size: 1rem;
-
-@use "./common.scss";
-
-div {
- display: flex;
- justify-content: center;
- align-items: center;
- width: 100%;
- height: 100%;
-}
-
-svg {
- width: var(--size, $default-size);
- height: var(--size, $default-size);
- fill: var(--fill);
- stroke: var(--stroke);
-}
\ No newline at end of file
diff --git a/styles/v1/components/incrementer.scss b/styles/v1/components/incrementer.scss
deleted file mode 100644
index fee073a..0000000
--- a/styles/v1/components/incrementer.scss
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
-Disclaimer: This CSS is used by a custom web component and is scoped to JUST
-the corresponding web component. Importing this into other files is forbidden
-*/
-
-$default-border-radius: 4px;
-$default-height: 1.5rem;
-
-@use "./common.scss";
-
-div {
- display: grid;
- grid-template-columns: var(--height, $default-height) var(--width, 50px) var(--height, $default-height);
- grid-template-rows: var(--height, 1fr);
- border-radius: var(--border-radius, $default-border-radius);
-}
-
-span, input {
- border: none;
- outline: none;
- background: none;
- color: inherit;
-}
-
-input {
- font-family: var(--font-family, inherit);
- text-align: center;
- font-size: var(--font-size, inherit);
- padding: 2px 4px;
-
- &::-webkit-inner-spin-button, &::-webkit-outer-spin-button {
- -webkit-appearance: none;
- -moz-appearance: none;
- appearance: none;
- margin: 0
- }
-}
-
-.increment, .decrement {
- aspect-ratio: 1 / 1;
- padding: 0;
- display: flex;
- justify-content: center;
- align-items: center;
- cursor: pointer;
-}
-
-.increment {
- border-radius: 0 var(--border-radius, $default-border-radius) var(--border-radius, 4px) 0;
-}
-.decrement {
- border-radius: var(--border-radius, $default-border-radius) 0 0 var(--border-radius, $default-border-radius);
-}
diff --git a/styles/v1/index.scss b/styles/v1/index.scss
deleted file mode 100644
index c4a7503..0000000
--- a/styles/v1/index.scss
+++ /dev/null
@@ -1,5 +0,0 @@
-// Styling version 1
-
-@use "./Dialog.scss";
-
-@use "./player/root.scss";
diff --git a/styles/v1/player/root.scss b/styles/v1/player/root.scss
deleted file mode 100644
index d2df5d9..0000000
--- a/styles/v1/player/root.scss
+++ /dev/null
@@ -1,42 +0,0 @@
-.actor--player.style-v1 {
- --header-size: 75px;
-
- form {
- display: flex;
- flex-direction: column;
- gap: 8px;
- }
-
- .header-row {
- display: flex;
- flex-direction: row;
- border-radius: 4px;
- border: 1px solid var(--color-underline-header);
- }
-
- .avatar {
- --size: var(--header-size);
- width: var(--size);
- height: var(--size);
- border: none;
- border-right: 1px solid var(--color-underline-header);
- }
-
- .actor-name {
- height: var(--header-size);
- padding: 8px 1rem;
- font-size: clamp(1rem, 2rem, calc(var(--header-size) - 16px));
- border: none;
- }
-
- prose-mirror {
- --menu-background: rgba(0, 0, 0, 0.1);
- flex-grow: 1;
- border: 1px solid var(--color-underline-header);
- border-radius: 4px;
-
- .editor-container {
- height: auto;
- }
- }
-}
\ No newline at end of file
diff --git a/system.json b/system.json
index d1561a6..024c8a6 100644
--- a/system.json
+++ b/system.json
@@ -1,15 +1,15 @@
{
"id": "taf",
"title": "Text-Based Actors",
- "description": "",
+ "description": "An intentionally minimalist system that enables you to play rules-light games without any hassle!",
"version": "2.0.0",
- "download": "https://github.com/Oliver-Akins/Text-Actors-Foundry/releases/latest/download/dotdungeon.zip",
+ "download": "https://github.com/Oliver-Akins/Text-Actors-Foundry/releases/latest/download/release.zip",
"manifest": "https://github.com/Oliver-Akins/Text-Actors-Foundry/releases/latest/download/system.json",
"url": "https://github.com/Oliver-Akins/Text-Actors-Foundry",
"compatibility": {
- "minimum": 12,
- "verified": 12,
- "maximum": 12
+ "minimum": 13,
+ "verified": 13,
+ "maximum": 13
},
"authors": [
{
@@ -18,12 +18,21 @@
}
],
"esmodules": [
- "src/main.mjs"
+ "./module/main.mjs"
],
"styles": [
- ".styles/root.css"
+ {
+ "src": "./styles/main.css",
+ "layer": "system"
+ }
+ ],
+ "languages": [
+ {
+ "lang": "en",
+ "name": "English (Canadian)",
+ "path": "langs/en-ca.json"
+ }
],
- "packs": [],
"documentTypes": {
"Actor": {
"player": {
diff --git a/taf.lock b/taf.lock
new file mode 100644
index 0000000..82ef623
--- /dev/null
+++ b/taf.lock
@@ -0,0 +1 @@
+🔒
\ No newline at end of file
diff --git a/templates/AttributeManager/attribute-list.hbs b/templates/AttributeManager/attribute-list.hbs
new file mode 100644
index 0000000..f15d9a9
--- /dev/null
+++ b/templates/AttributeManager/attribute-list.hbs
@@ -0,0 +1,35 @@
+
diff --git a/templates/AttributeManager/controls.hbs b/templates/AttributeManager/controls.hbs
new file mode 100644
index 0000000..59dbb9b
--- /dev/null
+++ b/templates/AttributeManager/controls.hbs
@@ -0,0 +1,13 @@
+
+
+ Add New Attribute
+
+
+ Save and Close
+
+
diff --git a/templates/Dialogs/ask.hbs b/templates/Dialogs/ask.hbs
deleted file mode 100644
index a37f56e..0000000
--- a/templates/Dialogs/ask.hbs
+++ /dev/null
@@ -1,27 +0,0 @@
-
-
- {{ question }}
-
- {{#each inputs as | i | }}
-
-
- {{ i.label }}
-
-
- {{#if i.details}}
-
- {{{ i.details }}}
-
- {{/if}}
-
- {{/each}}
-
diff --git a/templates/Player/v1/main.hbs b/templates/Player/v1/main.hbs
deleted file mode 100644
index 64e24d8..0000000
--- a/templates/Player/v1/main.hbs
+++ /dev/null
@@ -1,29 +0,0 @@
-
diff --git a/templates/PlayerSheet/attributes.hbs b/templates/PlayerSheet/attributes.hbs
new file mode 100644
index 0000000..80d6085
--- /dev/null
+++ b/templates/PlayerSheet/attributes.hbs
@@ -0,0 +1,32 @@
+{{#if hasAttributes}}
+
+{{else}}
+
+{{/if}}
diff --git a/templates/PlayerSheet/content.hbs b/templates/PlayerSheet/content.hbs
new file mode 100644
index 0000000..4d89fb2
--- /dev/null
+++ b/templates/PlayerSheet/content.hbs
@@ -0,0 +1,15 @@
+
+ {{#if editable}}
+
+ {{{ enriched.system.content }}}
+
+ {{else}}
+ {{{ enriched.system.content }}}
+ {{/if}}
+
\ No newline at end of file
diff --git a/templates/PlayerSheet/header.hbs b/templates/PlayerSheet/header.hbs
new file mode 100644
index 0000000..3e5daf3
--- /dev/null
+++ b/templates/PlayerSheet/header.hbs
@@ -0,0 +1,17 @@
+