diff --git a/.gitignore b/.gitignore index 313026d..4beb05c 100644 --- a/.gitignore +++ b/.gitignore @@ -3,8 +3,10 @@ node_modules/ *.bak.* references.txt references/ -.styles/ -/foundry.js +/.*/ +!/.vscode/ +!/.github/ +/*.ref.* *.lock *.zip diff --git a/.vscode/components.html-data.json b/.vscode/components.html-data.json new file mode 100644 index 0000000..e1c3ebd --- /dev/null +++ b/.vscode/components.html-data.json @@ -0,0 +1,27 @@ +{ + "version": 1.1, + "tags": [ + { + "name": "dd-incrementer", + "description": "A number input that allows more flexible increase/decrease buttons", + "attributes": [ + { "name": "value", "description": "The initial value to put in the input" }, + { "name": "name", "description": "The form name to use when this input is used to submit data" }, + { "name": "min", "description": "The minimum value that this input can contain" }, + { "name": "max", "description": "The maximum value that this input can contain" }, + { "name": "smallStep", "description": "The value that the input is changed by when clicking a delta button or using the up/down arrow key" }, + { "name": "largeStep", "description": "The value that the input is changed by when clicking a delta button with control held or using the page up/ page down arrow key" } + ] + }, + { + "name": "dd-icon", + "description": "Loads an icon asynchronously, caching the result for future uses", + "attributes": [ + { "name": "name", "description": "The name of the icon, this is relative to the assets folder of the dotdungeon system" }, + { "name": "path", "description": "The full path of the icon, this will only be used if `name` isn't provided or fails to fetch." } + ] + } + ], + "globalAttributes": [], + "valueSets": [] +} diff --git a/.vscode/settings.json b/.vscode/settings.json index 36a383b..9cd44b8 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -12,5 +12,8 @@ "node_modules": true, "packs": true, ".gitattributes": true, - } -} \ No newline at end of file + }, + "html.customData": [ + "./.vscode/components.html-data.json" + ] +} diff --git a/assets/caret-down.svg b/assets/caret-down.svg deleted file mode 100644 index 415d3db..0000000 --- a/assets/caret-down.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/assets/caret-right.svg b/assets/caret-right.svg deleted file mode 100644 index 7d1d59b..0000000 --- a/assets/caret-right.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/assets/chat-bubble.svg b/assets/chat-bubble.svg deleted file mode 100644 index 8dde604..0000000 --- a/assets/chat-bubble.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/assets/close.svg b/assets/close.svg deleted file mode 100644 index f6c80ed..0000000 --- a/assets/close.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/assets/dice/d10.svg b/assets/dice/d10.svg index 96a39a1..3debc8e 100644 --- a/assets/dice/d10.svg +++ b/assets/dice/d10.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/assets/dice/d12.svg b/assets/dice/d12.svg index dac2e4c..df2787c 100644 --- a/assets/dice/d12.svg +++ b/assets/dice/d12.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/assets/dice/d20.svg b/assets/dice/d20.svg index 82cf8b3..a829cdf 100644 --- a/assets/dice/d20.svg +++ b/assets/dice/d20.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/assets/dice/d4.svg b/assets/dice/d4.svg index 3388bda..f31809b 100644 --- a/assets/dice/d4.svg +++ b/assets/dice/d4.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/assets/dice/d6.svg b/assets/dice/d6.svg index bea7528..00dfed7 100644 --- a/assets/dice/d6.svg +++ b/assets/dice/d6.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/assets/dice/d8.svg b/assets/dice/d8.svg index ca3b00b..7731f96 100644 --- a/assets/dice/d8.svg +++ b/assets/dice/d8.svg @@ -1,3 +1 @@ - - - + \ No newline at end of file diff --git a/assets/edit.svg b/assets/edit.svg deleted file mode 100644 index 7cc344b..0000000 --- a/assets/edit.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/assets/garbage-bin.svg b/assets/garbage-bin.svg deleted file mode 100644 index b9268a5..0000000 --- a/assets/garbage-bin.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/assets/minus.svg b/assets/minus.svg deleted file mode 100644 index 171613e..0000000 --- a/assets/minus.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/assets/sheet.svg b/assets/sheet.svg deleted file mode 100644 index eaf555b..0000000 --- a/assets/sheet.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/assets/sources.txt b/assets/sources.txt index b002fc2..2896d5f 100644 --- a/assets/sources.txt +++ b/assets/sources.txt @@ -1,12 +1,15 @@ +Disclaimer: + All icons included in this repo have been scaled and optimized as needed. + Amer Alamer - caret-right.svg (https://thenounproject.com/icon/arrow-caret-right-1143917/) - caret-down.svg (https://thenounproject.com/icon/arrow-caret-down-1143911/) + ui/caret/right.svg (https://thenounproject.com/icon/arrow-caret-right-1143917/) + ui/caret/down.svg (https://thenounproject.com/icon/arrow-caret-down-1143911/) Alice Design: - garbage-bin.svg (https://thenounproject.com/icon/garbage-2025492/) + ui/garbage-bin.svg (https://thenounproject.com/icon/garbage-2025492/) zapesicon: - chat-bubble.svg (https://thenounproject.com/icon/chat-6423186/) + ui/chat-bubble.svg (https://thenounproject.com/icon/chat-6423186/) Fritz Duggan: dice/d4.svg (https://thenounproject.com/icon/d4-4570604/) @@ -17,21 +20,19 @@ Fritz Duggan: dice/d20.svg (https://thenounproject.com/icon/d20-4570607/) Landan Lloyd: - create.svg (https://thenounproject.com/icon/create-1447560/) + ui/plus.svg (https://thenounproject.com/icon/create-1447560/) Bismillah - minus.svg (https://thenounproject.com/icon/minus-1727966/) + ui/minus.svg (https://thenounproject.com/icon/minus-1727966/) Rokhman Kharis: - close.svg (https://thenounproject.com/icon/close-4996834/) + ui/close.svg (https://thenounproject.com/icon/close-4996834/) Athok: - sheet.svg (https://thenounproject.com/icon/sheet-5939348/) + ui/sheet.svg (https://thenounproject.com/icon/sheet-5939348/) Icon Depot: - edit.svg (https://thenounproject.com/icon/edit-1489252/) + ui/pencil.svg (https://thenounproject.com/icon/edit-1489252/) -Oliver Akins: - chat-bubble.svg : Scaling - create.svg : Scaling, Optimization - sheet.svg : Scaling \ No newline at end of file +Muhammad Ahsanu Nadia: + ui/help.svg (https://thenounproject.com/icon/help-6778522/) diff --git a/assets/ui/caret/down.svg b/assets/ui/caret/down.svg new file mode 100644 index 0000000..5c15836 --- /dev/null +++ b/assets/ui/caret/down.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/ui/caret/right.svg b/assets/ui/caret/right.svg new file mode 100644 index 0000000..3b19a49 --- /dev/null +++ b/assets/ui/caret/right.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/ui/chat-bubble.svg b/assets/ui/chat-bubble.svg new file mode 100644 index 0000000..a9182c1 --- /dev/null +++ b/assets/ui/chat-bubble.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/ui/close.svg b/assets/ui/close.svg new file mode 100644 index 0000000..3082802 --- /dev/null +++ b/assets/ui/close.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/ui/garbage-bin.svg b/assets/ui/garbage-bin.svg new file mode 100644 index 0000000..dd96a44 --- /dev/null +++ b/assets/ui/garbage-bin.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/ui/help.svg b/assets/ui/help.svg new file mode 100644 index 0000000..bd06071 --- /dev/null +++ b/assets/ui/help.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/ui/minus.svg b/assets/ui/minus.svg new file mode 100644 index 0000000..d1d3e94 --- /dev/null +++ b/assets/ui/minus.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/ui/pencil.svg b/assets/ui/pencil.svg new file mode 100644 index 0000000..455379f --- /dev/null +++ b/assets/ui/pencil.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/create.svg b/assets/ui/plus.svg similarity index 100% rename from assets/create.svg rename to assets/ui/plus.svg diff --git a/assets/ui/sheet.svg b/assets/ui/sheet.svg new file mode 100644 index 0000000..32a3268 --- /dev/null +++ b/assets/ui/sheet.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/augments.d.ts b/augments.d.ts new file mode 100644 index 0000000..df16590 --- /dev/null +++ b/augments.d.ts @@ -0,0 +1,9 @@ +interface Actor { + /** The system-specific data */ + system: any; +}; + +interface Item { + /** The system-specific data */ + system: any; +}; diff --git a/jsconfig.json b/jsconfig.json new file mode 100644 index 0000000..2304d94 --- /dev/null +++ b/jsconfig.json @@ -0,0 +1,7 @@ +{ + "compilerOptions": { + "types": [ + "./augments.d.ts" + ] + } +} \ No newline at end of file diff --git a/langs/en-ca.2.json b/langs/en-ca.2.json index 460fbaf..dce0efa 100644 --- a/langs/en-ca.2.json +++ b/langs/en-ca.2.json @@ -68,13 +68,39 @@ "rare": "Rare", "legendary": "Legendary" }, + "location": { + "unknown": "@dotdungeon.common.empty", + "inventory": "Inventory", + "equipped": "Equipped", + "storage": "Storage" + }, "default": { "name": "(Unnamed @TYPES.{document}.{type})" }, "common": { "send-to-chat": "Send to Chat", "edit": "Edit", - "delete": "Delete" + "delete": "Delete", + "reset": "Reset", + "empty": "---", + "help": "Help", + "gm": "Server", + "view-larger": "View Larger" + }, + "sheet-names": { + "*DataSheet": "Data Sheet" + }, + "help-tooltips": { + "calculated-capacity": { + "title": "What is Calculated Capacity?", + "content": "

The calculated capacity is how much space in your inventory that the item will take up, the way it is calculated is determined by the item. Usually the main thing that affects the capacity is the item's quantity, but this can be turned off by the @dotdungeon.common.gm, which means that no matter the quantity it will only use up one capacity. The @dotdungeon.common.gm can also entirely disable capacity usage which will make the used capacity always be zero.

" + } + }, + "delete": { + "ActiveEffect": { + "title": "Delete Effect", + "content": "

Are you sure you would like to delete the active effect: {name}

" + } } }, "TYPES": { @@ -95,6 +121,9 @@ "legendaryItem": "Legendary Item", "spell": "Spell", "untyped": "Custom" + }, + "ActiveEffect": { + "base": "Effect" } } } \ No newline at end of file diff --git a/module/components/icon.mjs b/module/components/icon.mjs new file mode 100644 index 0000000..8c70d40 --- /dev/null +++ b/module/components/icon.mjs @@ -0,0 +1,125 @@ +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 DotDungeonIcon extends StyledShadowElement(HTMLElement) { + static elementName = `dd-icon`; + static formAssociated = false; + + /* Stuff for the mixin to use */ + static _stylePath = `v3/components/icon.css`; + + + 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(`dotdungeon`, `devMode`)) { + this.#svgHmr = Hooks.on(`dd-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(`dd-hmr:svg`, this.#svgHmr); + + this._mounted = false; + }; + + async #getIcon(path) { + // Cache hit! + if (this.constructor._cache.has(path)) { + console.debug(`.dungeon | Icon ${path} cache hit`); + return this.constructor._cache.get(path); + }; + + const r = await fetch(path); + switch (r.status) { + case 200: + case 201: + break; + default: + console.error(`.dungeon | Failed to fetch icon: ${path}`); + return; + }; + + console.debug(`.dungeon | 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/module/components/incrementer.mjs b/module/components/incrementer.mjs new file mode 100644 index 0000000..68e426a --- /dev/null +++ b/module/components/incrementer.mjs @@ -0,0 +1,149 @@ +import { DotDungeonIcon } from "./icon.mjs"; +import { StyledShadowElement } from "./mixins/Styles.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 DotDungeonIncrementer extends StyledShadowElement(HTMLElement) { + static elementName = `dd-incrementer`; + static formAssociated = true; + + static _stylePath = `v3/components/incrementer.css`; + + _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(DotDungeonIcon.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(DotDungeonIcon.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/module/components/index.mjs b/module/components/index.mjs new file mode 100644 index 0000000..f4d39e9 --- /dev/null +++ b/module/components/index.mjs @@ -0,0 +1,23 @@ +import { DotDungeonIncrementer } from "./incrementer.mjs"; +import { DotDungeonIcon } from "./icon.mjs"; + +const components = [ + DotDungeonIcon, + DotDungeonIncrementer, +]; + +export function registerCustomComponents() { + (CONFIG.CACHE ??= {}).componentListeners ??= []; + for (const component of components) { + if (!window.customElements.get(component.elementName)) { + console.debug(`.dungeon | Registering component "${component.elementName}"`); + window.customElements.define( + component.elementName, + component + ); + if (component.formAssociated) { + CONFIG.CACHE.componentListeners.push(component.elementName); + } + }; + } +}; diff --git a/module/components/mixins/Styles.mjs b/module/components/mixins/Styles.mjs new file mode 100644 index 0000000..33d5eb5 --- /dev/null +++ b/module/components/mixins/Styles.mjs @@ -0,0 +1,80 @@ +/** + * @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/module/config.mjs b/module/config.mjs index 591fd84..56a0fb4 100644 --- a/module/config.mjs +++ b/module/config.mjs @@ -34,8 +34,10 @@ export const skills = { export const defaultItemTier = `simple`; export const itemTiers = [ - `simple`, `greater`, - `rare`, `legendary` + { value: `simple`, label: `dotdungeon.rarity.simple` }, + { value: `greater`, label: `dotdungeon.rarity.greater` }, + { value: `rare`, label: `dotdungeon.rarity.rare` }, + { value: `legendary`, label: `dotdungeon.rarity.legendary` }, ]; export const syncMilestones = [ diff --git a/module/dialogs/DiceList.mjs b/module/dialogs/DiceList.mjs index f9793de..3c305c8 100644 --- a/module/dialogs/DiceList.mjs +++ b/module/dialogs/DiceList.mjs @@ -12,7 +12,7 @@ export class DiceList extends GenericDialog { }; static get defaultOptions() { - const opts = mergeObject({ + const opts = foundry.utils.mergeObject({ ...super.defaultOptions, template: `systems/dotdungeon/templates/dialogs/diceList.hbs`, width: 275, diff --git a/module/dialogs/PopoutTextEditor.mjs b/module/dialogs/PopoutTextEditor.mjs deleted file mode 100644 index fc74b62..0000000 --- a/module/dialogs/PopoutTextEditor.mjs +++ /dev/null @@ -1,51 +0,0 @@ -export class PopoutTextEditor extends FormApplication { - - /** - * Creates a form - */ - static create(value) {}; - - /** @override */ - constructor(actor, property, options = {}) { - super(actor, options); - this.property = property; - }; - - static get defaultOptions() { - const opts = mergeObject({ - ...super.defaultOptions, - title: `Rich Text Editor`, - template: `systems/dotdungeon/templates/dialogs/text-editor.hbs`, - width: 500, - height: 600, - submitOnClose: false, - resizable: true, - }); - return opts; - }; - - async getData() { - const ctx = await super.getData(); - - ctx.editor = { - engine: `prosemirror`, - collaborate: true, - content: await TextEditor.enrichHTML( - await getProperty(this.document, this.property), - { - relativeTo: this.object, - secrets: this.object.isOwner, - async: true - } - ), - target: this.property, - }; - - return ctx; - }; - - async _updateObject(_event, formData) { - console.log(formData); - this.document.update({ [this.property]: formData["text"] }) - }; -}; diff --git a/module/documents/ActiveEffect/GenericActiveEffect.mjs b/module/documents/ActiveEffect/GenericActiveEffect.mjs new file mode 100644 index 0000000..8ee70f3 --- /dev/null +++ b/module/documents/ActiveEffect/GenericActiveEffect.mjs @@ -0,0 +1,7 @@ +export class DotDungeonActiveEffect extends ActiveEffect { + + // Invert the logic of the disabled property so it's easier to modify via + // embedded controls + get enabled() { return !this.disabled }; + set enabled(newValue) { this.disabled = !newValue }; +}; diff --git a/module/documents/ActiveEffect/_proxy.mjs b/module/documents/ActiveEffect/_proxy.mjs new file mode 100644 index 0000000..4b51b54 --- /dev/null +++ b/module/documents/ActiveEffect/_proxy.mjs @@ -0,0 +1,42 @@ +import { DotDungeonActiveEffect } from "./GenericActiveEffect.mjs"; + +const classes = {}; + +const defaultClass = DotDungeonActiveEffect; + +export const ActiveEffectProxy = new Proxy(function () {}, { + construct(target, args) { + const [data] = args; + + if (!classes.hasOwnProperty(data.type)) { + return new defaultClass(...args); + } + + return new classes[data.type](...args); + }, + get(target, prop, receiver) { + + if (["create", "createDocuments"].includes(prop)) { + return function (data, options) { + if (data.constructor === Array) { + return data.map(i => ActiveEffectProxy.create(i, options)) + } + + if (!classes.hasOwnProperty(data.type)) { + return defaultClass.create(data, options); + } + + return classes[data.type].create(data, options); + }; + }; + + if (prop == Symbol.hasInstance) { + return function (instance) { + if (instance instanceof defaultClass) return true; + return Object.values(classes).some(i => instance instanceof i); + }; + }; + + return defaultClass[prop]; + }, +}); diff --git a/module/documents/Actor/GenericActor.mjs b/module/documents/Actor/GenericActor.mjs index d52b185..171af63 100644 --- a/module/documents/Actor/GenericActor.mjs +++ b/module/documents/Actor/GenericActor.mjs @@ -1,4 +1,15 @@ export class DotDungeonActor extends Actor { + + /* + Using this to take a "snapshot" of the system data prior to applying AE's so + that the inputs can still have the non-modified value in them, while we still + provide all that data to AE's without needing to disable any inputs. + */ + prepareEmbeddedDocuments() { + this.preAE = foundry.utils.deepClone(this.system); + super.prepareEmbeddedDocuments(); + }; + async createEmbeddedItem(defaults, opts = {}) { let items = await this.createEmbeddedDocuments(`Item`, defaults); if (!Array.isArray(items)) items = items ? [items] : []; diff --git a/module/documents/Actor/Player.mjs b/module/documents/Actor/Player.mjs index ff54481..2c5840d 100644 --- a/module/documents/Actor/Player.mjs +++ b/module/documents/Actor/Player.mjs @@ -1,8 +1,22 @@ import { DotDungeonActor } from "./GenericActor.mjs"; -import { DotDungeonItem } from "../Item/GenericItem.mjs"; export class Player extends DotDungeonActor { + applyActiveEffects() { + super.applyActiveEffects(); + + /* + These are the (groups of) fields that ActiveEffects may modify safely and + remain editable in the sheet. This needs to be done because of default + Foundry behaviour that otherwise prevents these fields from being edited. + The deletes must use optional chaining otherwise they can cause issues + during the document preparation lifecycle as an actor with no AE's affecting + anything in one of these areas will result in these paths being undefined. + */ + delete this.overrides.system?.stats; + delete this.overrides.system?.skills; + }; + async createCustomPet() { const body = new URLSearchParams({ number: 1, diff --git a/module/documents/Actor/Sync.mjs b/module/documents/Actor/Sync.mjs index 1542b3e..171a67c 100644 --- a/module/documents/Actor/Sync.mjs +++ b/module/documents/Actor/Sync.mjs @@ -1,4 +1,5 @@ import { DotDungeonActor } from "./GenericActor.mjs"; +import { syncMilestones } from "../../config.mjs"; export class Sync extends DotDungeonActor { async useRestDie() { diff --git a/module/documents/Item/GenericItem.mjs b/module/documents/Item/GenericItem.mjs index b52ddcd..786824e 100644 --- a/module/documents/Item/GenericItem.mjs +++ b/module/documents/Item/GenericItem.mjs @@ -6,13 +6,19 @@ export class DotDungeonItem extends Item { }; get usedCapacity() { - let capacity = 0; - if (this.system.uses_inventory_slot && this.system.quantity > 0) { - capacity = 1; + if (!this.system.uses_inventory_slot) return 0; + if (!this.system.quantity_affects_used_capacity) { + return 1; }; - if (this.system.quantity_affects_used_capacity) { - capacity = this.system.quantity; - }; - return capacity; + return this.system.quantity; + }; + + get availableLocations() { + return [ + { value: null, label: `dotdungeon.location.unknown` }, + { value: `inventory`, label: `dotdungeon.location.inventory` }, + { value: `equipped`, label: `dotdungeon.location.equipped` }, + { value: `storage`, label: `dotdungeon.location.storage` }, + ]; }; }; diff --git a/module/documents/Item/Material.mjs b/module/documents/Item/Material.mjs index 15d2a58..c6c5b72 100644 --- a/module/documents/Item/Material.mjs +++ b/module/documents/Item/Material.mjs @@ -5,4 +5,11 @@ export class Material extends DotDungeonItem { let affects = game.settings.get(`dotdungeon`, `materialsAffectCapacity`); return affects ? super.usedCapacity : 0; }; + + get availableLocations() { + return [ + { value: null, label: `dotdungeon.location.unknown` }, + { value: `inventory`, label: `dotdungeon.location.inventory` }, + ]; + }; }; diff --git a/module/dotdungeon.mjs b/module/dotdungeon.mjs index b4c597f..8731314 100644 --- a/module/dotdungeon.mjs +++ b/module/dotdungeon.mjs @@ -10,6 +10,7 @@ import { SyncData } from "./models/Actor/Sync.mjs"; import { MobData } from "./models/Actor/Mob.mjs"; // Main Documents +import { ActiveEffectProxy } from "./documents/ActiveEffect/_proxy.mjs"; import { ActorProxy } from "./documents/Actor/_proxy.mjs"; import { ItemProxy } from "./documents/Item/_proxy.mjs"; @@ -32,12 +33,15 @@ import * as hbs from "./handlebars.mjs"; import "./hooks/hotReload.mjs"; // Misc Imports +import { registerCustomComponents } from "./components/index.mjs"; import loadSettings from "./settings/index.mjs"; +import { devInit } from "./hooks/devInit.mjs"; import DOTDUNGEON from "./config.mjs"; Hooks.once(`init`, async () => { console.debug(`.dungeon | Initializing`); + CONFIG.ActiveEffect.legacyTransferral = false; loadSettings(); @@ -53,6 +57,7 @@ Hooks.once(`init`, async () => { CONFIG.Item.dataModels.pet = PetItemData; CONFIG.Actor.documentClass = ActorProxy; CONFIG.Item.documentClass = ItemProxy; + CONFIG.ActiveEffect.documentClass = ActiveEffectProxy; CONFIG.DOTDUNGEON = DOTDUNGEON; @@ -101,12 +106,13 @@ Hooks.once(`init`, async () => { label: "dotdungeon.sheet-names.PetSheet" }); + if (true || game.settings.get(`dotdungeon`, `devMode`)) { + devInit(); + }; hbs.registerHandlebarsHelpers(); hbs.preloadHandlebarsTemplates(); - - CONFIG.CACHE = {}; - CONFIG.CACHE.icons = await hbs.preloadIcons(); + registerCustomComponents(); }); diff --git a/module/handlebars.mjs b/module/handlebars.mjs index 596f9dd..cdaa1b7 100644 --- a/module/handlebars.mjs +++ b/module/handlebars.mjs @@ -30,31 +30,19 @@ export const partials = [ `actors/char-sheet/v2/partials/inventory/items/untyped.v2.pc.hbs`, `actors/char-sheet/v2/partials/inventory/items/aspect.v2.pc.hbs`, `actors/char-sheet/v2/partials/inventory/items/weapon.v2.pc.hbs`, + `actors/char-sheet/v2/partials/inventory/items/pet.v2.pc.hbs`, + + // The v2 Untyped sheet partials + `items/untyped/v2/tabs/general.v2.untyped.hbs`, + `items/untyped/v2/tabs/details.v2.untyped.hbs`, + `items/untyped/v2/tabs/effects.v2.untyped.hbs`, + `items/untyped/v2/tabs/settings.v2.untyped.hbs`, ]; export const preAliasedPartials = { "dotdungeon.pc.v2.foil": "actors/char-sheet/v2/partials/inventory/items/untyped.v2.pc.hbs", }; -export const icons = [ - `caret-right.svg`, - `caret-down.svg`, - `garbage-bin.svg`, - `chat-bubble.svg`, - `dice/d4.svg`, - `dice/d6.svg`, - `dice/d8.svg`, - `dice/d10.svg`, - `dice/d12.svg`, - `dice/d20.svg`, - `create.svg`, - `close.svg`, - `edit.svg`, - `sheet.svg`, - `minus.svg`, -]; - - export async function registerHandlebarsHelpers() { Handlebars.registerHelper(helpers); }; @@ -90,38 +78,3 @@ export async function preloadHandlebarsTemplates() { console.groupEnd(); return loadTemplates(paths); }; - -/** - * Loads all of the icons that are needed in the handlebars templating to make - * the sheet look nicer. - * - * @returns An object containing icon names to the corresponding HTML data for - * displaying the icon - */ -export async function preloadIcons() { - const pathPrefix = `systems/dotdungeon/assets/` - const parsedIcons = {}; - - for (const icon of icons) { - const iconName = icon.split(`/`).slice(-1)[0].slice(0, -4); - if (icon.endsWith(`.svg`)) { - try { - const response = await fetchWithTimeout(`${pathPrefix}${icon}`); - if (response.status !== 200) { continue }; - const svgData = await response.text(); - parsedIcons[iconName] = svgData; - } catch { - console.error(`.dungeon | Failed to fetch/parse icon: ${icon}`); - continue; - }; - } - else if (icon.endsWith(`.png`)) { - parsedIcons[iconName] = ``; - } - else { - console.warn(`.dungeon | Icon "${icon}" failed to be handled by a loader`) - }; - }; - - return parsedIcons; -}; diff --git a/module/helpers/index.mjs b/module/helpers/index.mjs index ee573e7..b48baa3 100644 --- a/module/helpers/index.mjs +++ b/module/helpers/index.mjs @@ -2,7 +2,7 @@ import { schemaOptions } from "./schemaOptions.mjs"; import { createArray } from "./createArray.mjs"; import { detailsExpanded } from "./detailsExpanded.mjs"; import { objectValue } from "./objectValue.mjs"; -import { handlebarsLocalizer } from "../utils/localizer.mjs"; +import { handlebarsLocalizer, localizer } from "../utils/localizer.mjs"; import { options } from "./options.mjs"; export default { @@ -19,7 +19,7 @@ export default { "dd-stringify": v => JSON.stringify(v, null, ` `), "dd-empty": v => v.length == 0, "dd-set-has": (s, k) => s.has(k), - "dd-empty-state": (v) => v ?? `--`, + "dd-empty-state": (v) => v ?? localizer(`dotdungeon.common.empty`), // Logic helpers "eq": (a, b) => a == b, diff --git a/module/hooks/devInit.mjs b/module/hooks/devInit.mjs new file mode 100644 index 0000000..b7ef326 --- /dev/null +++ b/module/hooks/devInit.mjs @@ -0,0 +1,27 @@ +/* +Initialization of dev-specific features for the init hook, this is primarily +used to register all of the data sheets of various entity types. +*/ + +import { GroupDataSheet } from "../sheets/Datasheets/GroupDataSheet.mjs"; +import { UntypedDataSheet } from "../sheets/Datasheets/UntypedDataSheet.mjs"; + +export function devInit() { + Items.registerSheet( + `dotdungeon`, + UntypedDataSheet, + { + types: [`untyped`, `foil`], + label: `dotdungeon.sheet-names.*DataSheet`, + } + ); + + Actors.registerSheet( + `dotdungeon`, + GroupDataSheet, + { + types: [`sync`], + label: `dotdungeon.sheet-names.*DataSheet`, + } + ); +}; diff --git a/module/hooks/hotReload.mjs b/module/hooks/hotReload.mjs index b183196..8c44a46 100644 --- a/module/hooks/hotReload.mjs +++ b/module/hooks/hotReload.mjs @@ -3,8 +3,8 @@ import * as hbs from "../handlebars.mjs"; const loaders = { svg(data) { const iconName = data.path.split(`/`).slice(-1)[0].slice(0, -4); - console.log(`.dungeon | hot-reloading icon: ${iconName}`); - CONFIG.CACHE.icons[iconName] = data.content; + console.debug(`.dungeon | hot-reloading icon: ${iconName}`); + Hooks.call(`dd-hmr:svg`, iconName, data); }, hbs(data) { if (!hbs.partials.some(p => data.path.endsWith(p))) { @@ -35,6 +35,10 @@ const loaders = { }, js() {window.location.reload()}, mjs() {window.location.reload()}, + css(data) { + console.debug(`.dungeon | Hot-reloading CSS: ${data.path}`); + Hooks.call(`dd-hmr:css`, data); + }, }; Hooks.on(`hotReload`, async (data) => { diff --git a/module/models/Actor/Player.mjs b/module/models/Actor/Player.mjs index 3d75709..244c887 100644 --- a/module/models/Actor/Player.mjs +++ b/module/models/Actor/Player.mjs @@ -1,4 +1,3 @@ -import { MappingField } from "../fields/MappingField.mjs"; import DOTDUNGEON from "../../config.mjs"; function diceChoiceField() { @@ -21,22 +20,6 @@ function trainingLevelField() { }); }; -function weaponDamageTypeField() { - return new foundry.data.fields.StringField({ - initial: ``, - blank: true, - options: [ ``, ...DOTDUNGEON.damageTypes ], - }); -}; - -function ammoTypeField() { - return new foundry.data.fields.StringField({ - initial: ``, - blank: true, - options: [ ``, ...DOTDUNGEON.ammoTypes ], - }); -}; - export class PlayerData extends foundry.abstract.TypeDataModel { static defineSchema() { const fields = foundry.data.fields; @@ -92,65 +75,18 @@ export class PlayerData extends foundry.abstract.TypeDataModel { piloting: trainingLevelField(), }) }), - aspect: new fields.SchemaField({ - name: new fields.StringField({ blank: true, trim: true }), - description: new fields.StringField({ blank: true, trim: true }), - deactivateAfter: new fields.NumberField({ min: 0, integer: true}), - used: new fields.BooleanField(), - }), + // ! Delete roles: new fields.SchemaField({ r1: new fields.StringField({ blank: true, trim: true }), r2: new fields.StringField({ blank: true, trim: true }), r3: new fields.StringField({ blank: true, trim: true }), r4: new fields.StringField({ blank: true, trim: true }), }), - weapon: new fields.SchemaField({ - mainHand: new fields.SchemaField({ - name: new fields.StringField({ initial: ``, blank: true, trim: true }), - damage: weaponDamageTypeField(), - ranged: new fields.BooleanField({ initial: false }), - scope: new fields.BooleanField({ initial: false }), - ammo: ammoTypeField(), - }), - offHand: new fields.SchemaField({ - name: new fields.StringField({ initial: ``, blank: true, trim: true }), - damage: weaponDamageTypeField(), - ranged: new fields.BooleanField({ initial: false }), - scope: new fields.BooleanField({ initial: false }), - ammo: ammoTypeField(), - }), - ammo: new fields.SchemaField({ - quivers: new fields.NumberField({ min: 0, max: 10, integer: true }), - mags: new fields.NumberField({ min: 0, max: 10, integer: true }), - cells: new fields.NumberField({ min: 0, max: 10, integer: true }), - }), - }), supplies: new fields.NumberField({ initial: 0, min: 0, integer: true }), - materials: new fields.NumberField({ - initial: 0, - min: 0, - integer: true - }), - pet: new fields.SchemaField({ - name: new fields.StringField(), - info: new fields.StringField(), - }), - transport: new fields.SchemaField({ - name: new fields.StringField(), - upkeep: new fields.NumberField({ min: 0, integer: true }), - info: new fields.StringField(), - }), - spells: new MappingField( - new fields.SchemaField({ - name: new fields.StringField({ initial: ``, blank: true, trim: true }), - cost: new fields.NumberField({ initial: 0, min: 0 }), - info: new fields.StringField({ initial: ``, blank: true, trim: true }), - }) - ), respawns: new fields.SchemaField({ r1: new fields.BooleanField(), r2: new fields.BooleanField(), @@ -161,7 +97,6 @@ export class PlayerData extends foundry.abstract.TypeDataModel { integer: true, initial: 0, }), - inventoryString: new fields.StringField({ blank: true, trim: true }), }; }; }; diff --git a/module/models/Item/Aspect.mjs b/module/models/Item/Aspect.mjs index eb1ffed..92b3eac 100644 --- a/module/models/Item/Aspect.mjs +++ b/module/models/Item/Aspect.mjs @@ -10,7 +10,7 @@ export class AspectItemData extends DescribedItemData { delete parentSchema.quantity_affects_used_capacity; delete parentSchema.usage_cost; - return mergeObject(parentSchema, { + return foundry.utils.mergeObject(parentSchema, { used: new fields.BooleanField({ initial: false }), /** The number of seconds that the effect of the aspect stays */ deactivateAfter: new fields.NumberField({ nullable: true }), diff --git a/module/models/Item/CommonItemData.mjs b/module/models/Item/CommonItemData.mjs index 82b72bf..9e11800 100644 --- a/module/models/Item/CommonItemData.mjs +++ b/module/models/Item/CommonItemData.mjs @@ -31,19 +31,19 @@ export class CommonItemData extends foundry.abstract.TypeDataModel { tier: new fields.StringField({ initial: DOTDUNGEON.defaultItemTier, nullable: false, - choices: DOTDUNGEON.itemTiers, + choices: DOTDUNGEON.itemTiers.map(tier => tier.value), + }), + /* + If this property is set to true, the item will be shown in the combat tab + list of items. This is shown whether or not the item is marked as "equipped". + */ + combat_relevant: new fields.BooleanField({ + initial: false, + nullable: false, }), location: new fields.StringField({ - initial: null, - nullable: true, - /* - "equipped" = on player, actively having an effect (e.g. armour - is worn, weapon is held), not all items have an equipped state - "inventory" = on player, equivalent to being put in a backpack - "storage" = not on player at all, in a chest in their house or - smth, these items should be displayed in the storage tab - */ - choices: ["equipped", "inventory", "storage"], + initial: "", + nullable: false, }), }; }; diff --git a/module/models/Item/DescribedItemData.mjs b/module/models/Item/DescribedItemData.mjs index ebfce48..eb913a0 100644 --- a/module/models/Item/DescribedItemData.mjs +++ b/module/models/Item/DescribedItemData.mjs @@ -3,7 +3,7 @@ import { CommonItemData } from "./CommonItemData.mjs"; export class DescribedItemData extends CommonItemData { static defineSchema() { const fields = foundry.data.fields; - return mergeObject(super.defineSchema(), { + return foundry.utils.mergeObject(super.defineSchema(), { description: new fields.StringField({ initial: ``, blank: true, diff --git a/module/models/Item/Equipment.mjs b/module/models/Item/Equipment.mjs index 82b58de..309fd3b 100644 --- a/module/models/Item/Equipment.mjs +++ b/module/models/Item/Equipment.mjs @@ -3,6 +3,6 @@ import { DescribedItemData } from "./DescribedItemData.mjs"; export class EquipmentItemData extends DescribedItemData { static defineSchema() { const fields = foundry.data.fields; - return mergeObject(super.defineSchema(), {}); + return foundry.utils.mergeObject(super.defineSchema(), {}); }; }; diff --git a/module/models/Item/Pet.mjs b/module/models/Item/Pet.mjs index 7d46e94..34c6de5 100644 --- a/module/models/Item/Pet.mjs +++ b/module/models/Item/Pet.mjs @@ -3,7 +3,13 @@ import { DescribedItemData } from "./DescribedItemData.mjs"; export class PetItemData extends DescribedItemData { static defineSchema() { const fields = foundry.data.fields; - return mergeObject(super.defineSchema(), { + const parentSchema = super.defineSchema(); + + delete parentSchema.quantity; + delete parentSchema.quantity_affects_used_capacity; + delete parentSchema.usage_cost; + + return foundry.utils.mergeObject(parentSchema, { upkeep: new fields.NumberField({ initial: null, nullable: true }), pokeballd: new fields.BooleanField({ initial: true }), }); diff --git a/module/models/Item/Spell.mjs b/module/models/Item/Spell.mjs index 6bd90f6..f96381f 100644 --- a/module/models/Item/Spell.mjs +++ b/module/models/Item/Spell.mjs @@ -4,7 +4,7 @@ import DOTDUNGEON from "../../config.mjs"; export class SpellItemData extends DescribedItemData { static defineSchema() { const fields = foundry.data.fields; - return mergeObject(super.defineSchema(), { + return foundry.utils.mergeObject(super.defineSchema(), { skill: new fields.StringField({ initial: ``, blank: true, diff --git a/module/models/Item/Weapon.mjs b/module/models/Item/Weapon.mjs index 5b9cf67..20db6b7 100644 --- a/module/models/Item/Weapon.mjs +++ b/module/models/Item/Weapon.mjs @@ -4,7 +4,7 @@ import DOTDUNGEON from "../../config.mjs"; export class WeaponItemData extends DescribedItemData { static defineSchema() { const fields = foundry.data.fields; - return mergeObject(super.defineSchema(), { + return foundry.utils.mergeObject(super.defineSchema(), { damage: new fields.StringField({ initial: null, nullable: true, diff --git a/module/models/template.mjs b/module/models/template.mjs index f3cea2a..77340ee 100644 --- a/module/models/template.mjs +++ b/module/models/template.mjs @@ -3,7 +3,7 @@ import { DescribedItemData } from "./DescribedItemData.mjs"; export class TemplateData extends DescribedItemData { static defineSchema() { const fields = foundry.data.fields; - return mergeObject(super.defineSchema(), { + return foundry.utils.mergeObject(super.defineSchema(), { }); }; }; diff --git a/module/sheets/Actors/PC/PlayerSheetV2.mjs b/module/sheets/Actors/PC/PlayerSheetV2.mjs index 63fe44a..ac1a378 100644 --- a/module/sheets/Actors/PC/PlayerSheetV2.mjs +++ b/module/sheets/Actors/PC/PlayerSheetV2.mjs @@ -2,10 +2,11 @@ import { GenericActorSheet } from "../../GenericActorSheet.mjs"; import DOTDUNGEON from "../../../config.mjs"; import { localizer } from "../../../utils/localizer.mjs"; import { modifierToString } from "../../../utils/modifierToString.mjs"; +import { GenericContextMenu } from "../../../utils/GenericContextMenu.mjs"; export class PlayerSheetv2 extends GenericActorSheet { static get defaultOptions() { - let opts = mergeObject( + let opts = foundry.utils.mergeObject( super.defaultOptions, { template: `systems/dotdungeon/templates/actors/char-sheet/v2/sheet.hbs`, @@ -25,6 +26,7 @@ export class PlayerSheetv2 extends GenericActorSheet { ], } ); + opts.classes.push(`style-v3`); return opts; }; @@ -37,9 +39,8 @@ export class PlayerSheetv2 extends GenericActorSheet { html.find(`.create-ae`).on(`click`, async ($e) => { console.debug(`Creating an ActiveEffect?`); - ActiveEffect.implementation.create({ - name: "Default AE", - }, { parent: this.actor, renderSheet: true }); + const ae = this.actor.createEmbeddedDocuments(`ActiveEffect`, [{name: "Default AE"}]); + ae.sheet.render(true); }); html.find(`[data-filter-toggle]`).on(`change`, ($e) => { const target = $e.delegateTarget; @@ -47,6 +48,24 @@ export class PlayerSheetv2 extends GenericActorSheet { this.toggleItemFilter(filter); this._renderInner(); }); + + // Make materials be able to be edited/deleted + new GenericContextMenu(html, `.material`, [ + { + name: localizer(`dotdungeon.common.edit`), + callback: (html) => { + const data = html[0].dataset; + this.openEmbeddedSheet.bind(this)(data.embeddedId); + }, + }, + { + name: localizer(`dotdungeon.common.delete`), + callback: (html) => { + const data = html[0].dataset; + this.genericEmbeddedDelete.bind(this)(data.embeddedId); + }, + }, + ]); }; async getData() { @@ -54,6 +73,7 @@ export class PlayerSheetv2 extends GenericActorSheet { /** @type {ActorHandler} */ const actor = this.actor; + ctx.preAE = actor.preAE; ctx.system = actor.system; ctx.flags = actor.flags; ctx.items = this.actor.itemTypes; @@ -77,6 +97,7 @@ export class PlayerSheetv2 extends GenericActorSheet { const stat = { key: statName, name: localizer(`dotdungeon.stat.${statName}`), + original: this.actor.preAE.stats[statName], value: this.actor.system.stats[statName], }; @@ -91,7 +112,7 @@ export class PlayerSheetv2 extends GenericActorSheet { return { value: die, label: localizer(`dotdungeon.die.${die}`, { stat: statName }), - disabled: usedDice.has(die) && this.actor.system.stats[statName] !== die, + disabled: usedDice.has(die) && this.actor.preAE.stats[statName] !== die, }; }) ]; @@ -107,8 +128,9 @@ export class PlayerSheetv2 extends GenericActorSheet { key: skill, name: game.i18n.format(`dotdungeon.skills.${skill}`), value, + original: this.actor.preAE.skills[statName][skill], formula: `1` + stat.value + modifierToString(value, { spaces: true }), - rollDisabled: value === -1, + rollDisabled: this.actor.preAE.skills[statName][skill] === -1, }); }; @@ -117,7 +139,7 @@ export class PlayerSheetv2 extends GenericActorSheet { return stats; }; - _itemTypesHidden = new Set([`pet`, `armour`, `equipment`, `structure`, `service`]); + _itemTypesHidden = new Set([`armour`, `equipment`, `structure`, `service`]); toggleItemFilter(filterName) { if (this._itemTypesHidden.has(filterName)) { this._itemTypesHidden.delete(filterName); @@ -147,9 +169,4 @@ export class PlayerSheetv2 extends GenericActorSheet { max: this.actor.system.inventory_slots, }; }; - - _updateObject(...args) { - console.log(args) - super._updateObject(...args); - }; -} \ No newline at end of file +} diff --git a/module/sheets/Datasheets/GroupDataSheet.mjs b/module/sheets/Datasheets/GroupDataSheet.mjs new file mode 100644 index 0000000..6c9527a --- /dev/null +++ b/module/sheets/Datasheets/GroupDataSheet.mjs @@ -0,0 +1,31 @@ +export class GroupDataSheet extends ActorSheet { + static get defaultOptions() { + let opts = foundry.utils.mergeObject( + super.defaultOptions, + { + template: `systems/dotdungeon/templates/datasheets/actor/group.hbs`, + width: 200, + height: 275 + }, + ); + opts.classes.push(`dotdungeon`, `style-v3`); + return opts; + }; + + async getData() { + const ctx = {}; + + ctx.actor = this.actor; + ctx.system = this.actor.system; + + ctx.computed = { + milestones_hit_viewable: [...this.actor.system.milestones_hit.values()].join(`, `) + } + + ctx.meta = { + idp: this.actor.uuid, + }; + + return ctx; + }; +}; diff --git a/module/sheets/Datasheets/UntypedDataSheet.mjs b/module/sheets/Datasheets/UntypedDataSheet.mjs new file mode 100644 index 0000000..c3d7702 --- /dev/null +++ b/module/sheets/Datasheets/UntypedDataSheet.mjs @@ -0,0 +1,27 @@ +export class UntypedDataSheet extends ItemSheet { + static get defaultOptions() { + let opts = foundry.utils.mergeObject( + super.defaultOptions, + { + template: `systems/dotdungeon/templates/datasheets/untyped.hbs`, + width: 650, + height: 700 + }, + ); + opts.classes.push(`dotdungeon`, `style-v3`); + return opts; + }; + + async getData() { + const ctx = {}; + + ctx.item = this.item; + ctx.system = this.item.system; + + ctx.meta = { + idp: this.item.uuid, + }; + + return ctx; + }; +}; diff --git a/module/sheets/GenericActorSheet.mjs b/module/sheets/GenericActorSheet.mjs index 660233e..2010304 100644 --- a/module/sheets/GenericActorSheet.mjs +++ b/module/sheets/GenericActorSheet.mjs @@ -3,7 +3,7 @@ import DOTDUNGEON from "../config.mjs"; export class GenericActorSheet extends ActorSheet { static get defaultOptions() { - let opts = mergeObject( + let opts = foundry.utils.mergeObject( super.defaultOptions, { scrollY: [`.scrollable`], @@ -40,7 +40,7 @@ export class GenericActorSheet extends ActorSheet { ctx.actor = this.actor; ctx.config = DOTDUNGEON; - ctx.icons = CONFIG.CACHE.icons; + ctx.icons = {}; return ctx; }; @@ -52,6 +52,17 @@ export class GenericActorSheet extends ActorSheet { if (!this.isEditable) return; console.debug(`.dungeon | Generic sheet adding listeners`); + /* + Custom element event listeners because Foundry doesn't listen to them by + default. + */ + html.find( + CONFIG.CACHE.componentListeners.map(n => `${n}[name]`).join(`,`) + ).on(`change`, () => this._onChangeInput.bind(this)); + + /* + Utility event listeners that apply + */ html.find(`[data-collapse-id]`).on(`click`, this._handleSummaryToggle.bind(this)); html.find(`[data-roll-formula]`).on(`click`, this._handleRoll.bind(this)); html.find(`[data-embedded-update-on="change"]`) @@ -59,13 +70,19 @@ export class GenericActorSheet extends ActorSheet { html.find(`[data-embedded-update-on="blur"]`) .on(`blur`, this.genericEmbeddedUpdate.bind(this)); html.find(`[data-embedded-delete]`) - .on(`click`, this.genericEmbeddedDelete.bind(this)); + .on(`click`, ($e) => { + const id = $e.currentTarget.dataset.embeddedDelete; + this.genericEmbeddedDelete.bind(this)(id); + }); html.find(`[data-embedded-create]`) .on(`click`, this.genericEmbeddedCreate.bind(this)); html.find(`[data-message-type]`) .on(`click`, this.genericSendToChat.bind(this)); html.find(`[data-embedded-edit]`) - .on(`click`, this.openEmbeddedSheet.bind(this)); + .on(`click`, ($e) => { + const id = $e.currentTarget.dataset.embeddedEdit; + this.openEmbeddedSheet.bind(this)(id); + }) html.find(`button[data-increment]`) .on(`click`, this._incrementValue.bind(this)); html.find(`button[data-decrement]`) @@ -129,10 +146,8 @@ export class GenericActorSheet extends ActorSheet { }; }; - async openEmbeddedSheet($event) { - const data = $event.currentTarget.dataset; - let item = await fromUuid(data.embeddedEdit); - console.log(data) + async openEmbeddedSheet(item_id) { + let item = await fromUuid(item_id); item?.sheet.render(true); }; @@ -186,9 +201,8 @@ export class GenericActorSheet extends ActorSheet { await item?.update({ [data.embeddedDecrement]: value - 1 }); }; - async genericEmbeddedDelete($event) { - let data = $event.currentTarget.dataset; - let item = await fromUuid(data.embeddedId); + async genericEmbeddedDelete(item_uuid) { + let item = await fromUuid(item_uuid); if (!item) { ui.notifications.error( diff --git a/module/sheets/Items/AspectSheet.mjs b/module/sheets/Items/AspectSheet.mjs index c43a01c..2bf8638 100644 --- a/module/sheets/Items/AspectSheet.mjs +++ b/module/sheets/Items/AspectSheet.mjs @@ -2,7 +2,7 @@ import { GenericItemSheet } from "./GenericItemSheet.mjs"; export class AspectSheet extends GenericItemSheet { static get defaultOptions() { - let opts = mergeObject( + let opts = foundry.utils.mergeObject( super.defaultOptions, { template: `systems/dotdungeon/templates/items/aspect.hbs`, diff --git a/module/sheets/Items/GenericItemSheet.mjs b/module/sheets/Items/GenericItemSheet.mjs index ab67779..936bd6c 100644 --- a/module/sheets/Items/GenericItemSheet.mjs +++ b/module/sheets/Items/GenericItemSheet.mjs @@ -1,3 +1,4 @@ +import { DialogManager } from "../../utils/DialogManager.mjs"; import DOTDUNGEON from "../../config.mjs"; export class GenericItemSheet extends ItemSheet { @@ -10,13 +11,6 @@ export class GenericItemSheet extends ItemSheet { `resourcesOrSupplies`, ]; - activateListeners(html) { - super.activateListeners(html); - - if (!this.isEditable) return; - console.debug(`.dungeon | Adding event listeners for Generic Item: ${this.id}`); - }; - async getData() { const ctx = {}; @@ -36,10 +30,57 @@ export class GenericItemSheet extends ItemSheet { ctx.item = this.item; ctx.system = this.item.system; ctx.flags = this.item.flags; + ctx.effects = this.item.effects; ctx.config = DOTDUNGEON; - ctx.icons = CONFIG.CACHE.icons; + ctx.icons = {}; return ctx; }; + + activateListeners(html) { + super.activateListeners(html); + + if (!this.isEditable) return; + console.debug(`.dungeon | Adding event listeners for Generic Item: ${this.id}`); + html.find(`button[data-increment]`) + .on(`click`, this._incrementValue.bind(this)); + html.find(`button[data-decrement]`) + .on(`click`, this._decrementValue.bind(this)); + + + html.find(`[data-help-id]`) + .on(`click`, this._helpPopup.bind(this)); + }; + + async _incrementValue($e) { + const target = $e.currentTarget; + const data = target.dataset; + const value = getProperty(this.actor, data.increment); + if (typeof value != "number") { + return; + }; + this.actor.update({ [data.increment]: value + 1 }); + }; + + async _decrementValue($e) { + const target = $e.currentTarget; + const data = target.dataset; + const value = getProperty(this.actor, data.decrement); + if (typeof value != "number") { + return; + }; + this.actor.update({ [data.decrement]: value - 1 }); + }; + + async _helpPopup($e) { + const target = $e.currentTarget; + const data = target.dataset; + if (!data.helpId) return; + DialogManager.helpDialog( + data.helpId, + data.helpContent, + data.helpTitle + ); + }; }; diff --git a/module/sheets/Items/PetSheet.mjs b/module/sheets/Items/PetSheet.mjs index 169516c..a2db3b9 100644 --- a/module/sheets/Items/PetSheet.mjs +++ b/module/sheets/Items/PetSheet.mjs @@ -2,7 +2,7 @@ import { GenericItemSheet } from "./GenericItemSheet.mjs"; export class PetSheet extends GenericItemSheet { static get defaultOptions() { - let opts = mergeObject( + let opts = foundry.utils.mergeObject( super.defaultOptions, { template: `systems/dotdungeon/templates/items/pet.hbs`, diff --git a/module/sheets/Items/SpellSheet.mjs b/module/sheets/Items/SpellSheet.mjs index cabf2db..8e92831 100644 --- a/module/sheets/Items/SpellSheet.mjs +++ b/module/sheets/Items/SpellSheet.mjs @@ -2,7 +2,7 @@ import { GenericItemSheet } from "./GenericItemSheet.mjs"; export class SpellSheet extends GenericItemSheet { static get defaultOptions() { - let opts = mergeObject( + let opts = foundry.utils.mergeObject( super.defaultOptions, { template: `systems/dotdungeon/templates/items/spell.hbs`, diff --git a/module/sheets/Items/UntypedItemSheet.mjs b/module/sheets/Items/UntypedItemSheet.mjs index f1562c9..5ae0f24 100644 --- a/module/sheets/Items/UntypedItemSheet.mjs +++ b/module/sheets/Items/UntypedItemSheet.mjs @@ -1,21 +1,116 @@ +import { GenericContextMenu } from "../../utils/GenericContextMenu.mjs"; +import { DialogManager } from "../../utils/DialogManager.mjs"; import { GenericItemSheet } from "./GenericItemSheet.mjs"; +import { localizer } from "../../utils/localizer.mjs"; export class UntypedItemSheet extends GenericItemSheet { static get defaultOptions() { - let opts = mergeObject( + let opts = foundry.utils.mergeObject( super.defaultOptions, { - template: `systems/dotdungeon/templates/items/custom.hbs`, - width: 280, + template: `systems/dotdungeon/templates/items/untyped/v2/index.hbs`, + width: 300, height: 340, + tabs: [ + { + group: `page`, + navSelector: `nav.page`, + contentSelector: `.page-content`, + initial: `general`, + }, + ], } ); - opts.classes.push(`dotdungeon`); + opts.classes.push(`dotdungeon`, `style-v3`); return opts; }; + activateListeners(html) { + super.activateListeners(html); + + new GenericContextMenu(html, `.photo.panel`, [ + { + name: localizer(`dotdungeon.common.view-larger`), + callback: () => { + (new ImagePopout(this.item.img)).render(true); + }, + }, + { + name: localizer(`dotdungeon.common.edit`), + condition: () => this.isEditable, + callback: () => { + const fp = new FilePicker({ + callback: (path) => { + this.item.update({"img": path}); + }, + }); + fp.render(true); + }, + }, + { + name: localizer(`dotdungeon.common.reset`), + condition: () => this.isEditable, + callback: () => { + console.log(`.dungeon | Reset Item Image`) + }, + } + ]); + + if (!this.isEditable) return; + console.debug(`.dungeon | Adding event listeners for Untyped Item: ${this.item.id}`); + + html.find(`.create-ae`).on(`click`, async () => { + await this.item.createEmbeddedDocuments( + `ActiveEffect`, + [{name: localizer(`dotdungeon.default.name`, { document: `ActiveEffect`, type: `base` })}], + { renderSheet: true } + ); + }); + + new GenericContextMenu(html, `.effect.panel`, [ + { + name: localizer(`dotdungeon.common.edit`), + callback: async (html) => { + (await fromUuid(html.closest(`.effect`)[0].dataset.embeddedId))?.sheet.render(true); + }, + }, + { + name: localizer(`dotdungeon.common.delete`), + callback: async (html) => { + const target = html.closest(`.effect`)[0]; + const data = target.dataset; + const id = data.embeddedId; + const doc = await fromUuid(id); + DialogManager.createOrFocus( + `${doc.uuid}-delete`, + { + title: localizer(`dotdungeon.delete.ActiveEffect.title`, doc), + content: localizer(`dotdungeon.delete.ActiveEffect.content`, doc), + buttons: { + yes: { + label: localizer(`Yes`), + callback() { + doc.delete(); + }, + }, + no: { + label: localizer(`No`), + } + } + } + ); + }, + } + ]); + }; + async getData() { const ctx = await super.getData(); + + ctx.meta.showSettingsTab = ctx.isGM || this.item.isOwned; + ctx.meta.isEmbedded = this.item.isOwned; + ctx.meta.isEditable = this.isEditable; + return ctx; }; }; diff --git a/module/sheets/MVPPCSheet.mjs b/module/sheets/MVPPCSheet.mjs index 3572b25..a5f05f5 100644 --- a/module/sheets/MVPPCSheet.mjs +++ b/module/sheets/MVPPCSheet.mjs @@ -5,7 +5,7 @@ export class MVPPCSheet extends GenericActorSheet { /** @override {ActorHandler} actor */ static get defaultOptions() { - let opts = mergeObject( + let opts = foundry.utils.mergeObject( super.defaultOptions, { template: `systems/dotdungeon/templates/actors/char-sheet-mvp/sheet.hbs` diff --git a/module/sheets/MobSheet.mjs b/module/sheets/MobSheet.mjs index cb6412c..b2f2213 100644 --- a/module/sheets/MobSheet.mjs +++ b/module/sheets/MobSheet.mjs @@ -1,10 +1,9 @@ import { GenericActorSheet } from "./GenericActorSheet.mjs"; import { DiceList } from "../dialogs/DiceList.mjs"; -import { PopoutTextEditor } from "../dialogs/PopoutTextEditor.mjs"; export class MobSheet extends GenericActorSheet { static get defaultOptions() { - let opts = mergeObject( + let opts = foundry.utils.mergeObject( super.defaultOptions, { template: `systems/dotdungeon/templates/actors/mobs/main.hbs`, @@ -28,14 +27,6 @@ export class MobSheet extends GenericActorSheet { let d = new DiceList(this.actor); d.render(true); }); - html.find(`[data-text-editor]`) - .on(`click`, () => { - let editor = new PopoutTextEditor( - this.actor, - `system.description` - ); - editor.render(true); - }); }; async getData() { diff --git a/module/sheets/SyncVariations/AbstractSyncSheet.mjs b/module/sheets/SyncVariations/AbstractSyncSheet.mjs index 53bfdf1..80e372b 100644 --- a/module/sheets/SyncVariations/AbstractSyncSheet.mjs +++ b/module/sheets/SyncVariations/AbstractSyncSheet.mjs @@ -2,7 +2,7 @@ import { GenericActorSheet } from "../GenericActorSheet.mjs"; export class AbstractSyncSheet extends GenericActorSheet { static get defaultOptions() { - let opts = mergeObject( + let opts = foundry.utils.mergeObject( super.defaultOptions, { width: 200, diff --git a/module/utils/DialogManager.mjs b/module/utils/DialogManager.mjs new file mode 100644 index 0000000..7c40407 --- /dev/null +++ b/module/utils/DialogManager.mjs @@ -0,0 +1,83 @@ +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, } + ); + }; + + static get size() { + return DialogManager.#dialogs.size; + } +}; diff --git a/module/utils/GenericContextMenu.mjs b/module/utils/GenericContextMenu.mjs new file mode 100644 index 0000000..9749b4f --- /dev/null +++ b/module/utils/GenericContextMenu.mjs @@ -0,0 +1,6 @@ +export class GenericContextMenu extends ContextMenu { + constructor(element, selector, menuItems, opts = {}) { + super(element, selector, menuItems, opts); + this.menuItems.forEach(i => i.icon ??= ``); + }; +}; diff --git a/module/utils/localizer.mjs b/module/utils/localizer.mjs index 550c1cb..7cfebb0 100644 --- a/module/utils/localizer.mjs +++ b/module/utils/localizer.mjs @@ -18,12 +18,20 @@ export function localizer(key, args = {}, depth = 0) { return localized; }; + /* + Helps prevent recursion 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; - localized = - localized.slice(0, match.index) - + localizer(subkey, args, depth + 1) - + localized.slice(match.index + subkey.length + 1) + if (localizedSubkeys.has(subkey)) continue; + localizedSubkeys.set(subkey, localizer(subkey, args, depth + 1)); }; - return localized; + + return localized.replace( + localizerConfig.subKeyPattern, + (_fullMatch, subkey) => { + return localizedSubkeys.get(subkey); + } + ); }; diff --git a/styles/dialog/DiceList.scss b/styles/dialog/DiceList.scss index 469539e..c7833bc 100644 --- a/styles/dialog/DiceList.scss +++ b/styles/dialog/DiceList.scss @@ -1,9 +1,9 @@ -.dotdungeon:has(.dialog--dice-list) { +.dotdungeon:not(.style-v3):has(.dialog--dice-list) { max-width: 275px; min-width: 275px; } -.dotdungeon .dialog--dice-list { +.dotdungeon:not(.style-v3) .dialog--dice-list { padding: 8px; .dice { diff --git a/styles/dialog/text-editor.scss b/styles/dialog/text-editor.scss deleted file mode 100644 index 1c581c6..0000000 --- a/styles/dialog/text-editor.scss +++ /dev/null @@ -1,5 +0,0 @@ -.dialog--text-editor { - [role="application"] { - height: 100%; - } -} \ No newline at end of file diff --git a/styles/generic.scss b/styles/generic.scss index 00a474a..92cfb3e 100644 --- a/styles/generic.scss +++ b/styles/generic.scss @@ -8,7 +8,7 @@ Enabling scrollbar customization on a per-sheet basis, with a relatively low specificity to allow easier overriding without artificially increasing it. */ -.dotdungeon { +.dotdungeon:not(.style-v3) { --scrollbar-width: 5px; --scrollbar-handle-color: #782e22; --scrollbar-handle-border-color: var(--color-border-highlight); @@ -24,7 +24,7 @@ specificity to allow easier overriding without artificially increasing it. } // Reset the parts of Foundry's styling which gets in the way of what I want -.dotdungeon > .window-content { +.dotdungeon:not(.style-v3) > .window-content { h2, h3, h4, h5, h6 { @include fvtt_reset; @@ -74,7 +74,7 @@ specificity to allow easier overriding without artificially increasing it. } // Styling that doesn't belong to any particular part of my sheet -.dotdungeon.dotdungeon.dotdungeon.dotdungeon { +.dotdungeon.dotdungeon.dotdungeon.dotdungeon:not(.style-v3) { container-type: size; > .window-content { diff --git a/styles/global/buttons.scss b/styles/global/buttons.scss index 0159a03..77531b3 100644 --- a/styles/global/buttons.scss +++ b/styles/global/buttons.scss @@ -2,7 +2,7 @@ @use "sass:color" as color; -.dotdungeon.dotdungeon > .window-content { +.dotdungeon.dotdungeon:not(.style-v3) > .window-content { button { border-radius: 4px; box-sizing: border-box; diff --git a/styles/global/design-v2.scss b/styles/global/design-v2.scss index e9985a1..5c22fe1 100644 --- a/styles/global/design-v2.scss +++ b/styles/global/design-v2.scss @@ -1,5 +1,5 @@ -.item--custom, -.actor--mob { +.item--custom:not(.style-v3), +.actor--mob:not(.style-v3) { input { border: 2px solid black; background: none; diff --git a/styles/global/icons.scss b/styles/global/icons.scss index 500ebca..3d62ada 100644 --- a/styles/global/icons.scss +++ b/styles/global/icons.scss @@ -1,6 +1,6 @@ $iconSizes: 12, 14, 16, 18, 20, 22, 24; -.dotdungeon.dotdungeon.dotdungeon.dotdungeon { +.dotdungeon.dotdungeon.dotdungeon.dotdungeon:not(.style-v3) { .icon { display: inline-flex; justify-content: center; @@ -14,4 +14,4 @@ $iconSizes: 12, 14, 16, 18, 20, 22, 24; }; } } -} \ No newline at end of file +} diff --git a/styles/mixins/_partials.scss b/styles/mixins/_partials.scss index 8932b3e..e006802 100644 --- a/styles/mixins/_partials.scss +++ b/styles/mixins/_partials.scss @@ -18,9 +18,9 @@ @mixin sub-nav($parent, $group, $display) { nav.#{$group} { @include material.elevate(1); - display: none; + display: none !important; } &:has(nav.#{$parent} .active[data-tab="#{$group}"]) nav.#{$group} { - display: #{$display}; + display: #{$display} !important; } } diff --git a/styles/root.scss b/styles/root.scss index 65c8cd5..8ff6cdf 100644 --- a/styles/root.scss +++ b/styles/root.scss @@ -9,7 +9,6 @@ @use "./global/design-v2.scss"; @use "./dialog/DiceList.scss"; -@use "./dialog/text-editor.scss"; @use "./sheets/partials/stat.scss"; @use "./sheets/partials/skill.scss"; @@ -22,4 +21,8 @@ @use "./sheets/items/custom.scss"; @use "./sheets/items/aspect.scss"; @use "./sheets/items/spell.scss"; -@use "./sheets/items/pet.scss"; \ No newline at end of file +@use "./sheets/items/pet.scss"; + +/* NEW BETTER SCOPED CSS ONLY BELOW HERE */ + +@use "./v3/index.scss"; \ No newline at end of file diff --git a/styles/sheets/actor/char-sheet/v2/pages/inventory.scss b/styles/sheets/actor/char-sheet/v2/pages/inventory.scss index 8581328..57626fd 100644 --- a/styles/sheets/actor/char-sheet/v2/pages/inventory.scss +++ b/styles/sheets/actor/char-sheet/v2/pages/inventory.scss @@ -49,11 +49,11 @@ &__list { &--material { display: grid; - grid-template-columns: 1fr 1fr; + grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; } - &--untyped, &--aspect { + &--untyped, &--aspect, &--foil, &--weapon, &--pet { display: flex; gap: 8px; flex-direction: column; @@ -68,7 +68,7 @@ .bytes-panel { display: grid; - grid-template-columns: 1fr min-content 50px min-content; + grid-template-columns: 1fr auto; gap: 8px; align-items: center; @@ -164,8 +164,8 @@ @include material.elevate(1); padding: 8px; gap: 8px; - display: grid; - grid-template-columns: 1fr min-content 50px min-content; + display: flex; + justify-content: space-between; align-items: center; border-radius: 4px; @@ -218,7 +218,7 @@ } } - .untyped, .aspect, .weapon { + .untyped, .aspect, .weapon, .pet { @include material.elevate(1); padding: 8px; border-radius: 4px; diff --git a/styles/sheets/actor/mob/mob.scss b/styles/sheets/actor/mob/mob.scss index fd3e063..11634b2 100644 --- a/styles/sheets/actor/mob/mob.scss +++ b/styles/sheets/actor/mob/mob.scss @@ -1,7 +1,7 @@ @use "../../../vars" as *; @use "avatar" as *; -.dotdungeon .actor--mob { +.dotdungeon:not(.style-v3) .actor--mob { --gap: 8px; --avatar-size: 100px; --row-height: calc((var(--avatar-size) - var(--gap)) / 2); diff --git a/styles/sheets/actor/mob/v2.scss b/styles/sheets/actor/mob/v2.scss new file mode 100644 index 0000000..d7f0a5c --- /dev/null +++ b/styles/sheets/actor/mob/v2.scss @@ -0,0 +1,123 @@ +@use "../../../vars" as *; +@use "avatar" as *; + +.dotdungeon:not(.style-v3) .actor--mob2 { + --gap: 8px; + --avatar-size: 100px; + --row-height: calc((var(--avatar-size) - var(--gap)) / 2); + padding: var(--gap); + display: grid; + grid-template-columns: var(--avatar-size) repeat(2, minmax(0, 1fr)); + grid-template-rows: repeat(5, var(--row-height)) minmax(var(--row-height), 1fr); + gap: var(--gap); + grid-template-areas: + "avatar . ." + "avatar . ." + "dice tabs tabs" + "dice tabs tabs" + "dice tabs tabs" + "dice tabs tabs"; + + label, .dice { + border: 2px solid black; + border-radius: 4px; + gap: 4px; + padding: 4px; + } + + input.masked { + border: 2px solid black; + background: none; + box-shadow: none; + &:focus { + transform: scale(102%); + } + } + + label.mask-input { + display: flex; + flex-direction: row; + align-items: center; + transition: all ease-in-out 50ms; + + &:focus-within { + transform: scale(102%); + } + + input { + text-align: right; + border: none; + background: none; + border-radius: 0; + border-bottom: 1px solid black; + &:focus, &:focus-visible { + box-shadow: none; + } + } + + input.left { + text-align: left; + } + } + + .wide { + grid-column: span 2; + } + + .mask-textarea { + display: flex; + flex-direction: column; + transition: all ease-in-out 50ms; + + &:focus-within { + transform: scale(102%); + } + + textarea { + flex-grow: 1; + resize: none; + border: none; + box-shadow: none; + background: none; + } + } + .name { + height: 100%; + font-size: 1.5rem; + input { + height: 100%; + } + } + + .dice { + grid-area: dice; + display: flex; + flex-direction: column; + + .die { + display: flex; + flex-direction: row; + width: 100%; + border: 2px solid black; + border-radius: 4px; + + .formula { + flex-grow: 1; + align-self: center; + } + } + } + + .tabs { grid-area: tabs; } + .stunts { + grid-area: stunts; + } + .description { + grid-area: description; + } + + .avatar { + @include avatar; + border-radius: 4px; + } +} \ No newline at end of file diff --git a/styles/sheets/actor/mvp.scss b/styles/sheets/actor/mvp.scss index 087e744..4193a24 100644 --- a/styles/sheets/actor/mvp.scss +++ b/styles/sheets/actor/mvp.scss @@ -1,7 +1,7 @@ @use "../../vars.scss" as *; @use "../../mixins/breakpoints" as *; -.dotdungeon .actor--pc-mvp { +.dotdungeon:not(.style-v3) .actor--pc-mvp { display: grid; grid-template-areas: "profile stats stats" @@ -270,7 +270,7 @@ @include bp-m { - .dotdungeon { + .dotdungeon:not(.style-v3) { .actor--pc-mvp { grid-template-columns: repeat(2, minmax(0, 1fr)); grid-template-rows: repeat(15, min-content); @@ -310,7 +310,7 @@ } @include bp-s { - .dotdungeon { + .dotdungeon:not(.style-v3) { .actor--pc-mvp { grid-template-columns: 1fr; grid-template-rows: repeat(12, min-content); diff --git a/styles/sheets/actor/sync/basic.scss b/styles/sheets/actor/sync/basic.scss index 6f412be..904d093 100644 --- a/styles/sheets/actor/sync/basic.scss +++ b/styles/sheets/actor/sync/basic.scss @@ -1,6 +1,6 @@ @use "../../../mixins/breakpoints" as *; -.dotdungeon { +:not(.style-v3).dotdungeon { .actor--basic-sync { display: flex; justify-content: center; @@ -52,7 +52,7 @@ } @include bp-xs { - .dotdungeon { + :not(.style-v3).dotdungeon { &--sync-sheet { header { .configure-sheet { diff --git a/styles/sheets/items/aspect.scss b/styles/sheets/items/aspect.scss index 2461b9b..8f355fe 100644 --- a/styles/sheets/items/aspect.scss +++ b/styles/sheets/items/aspect.scss @@ -1,6 +1,6 @@ @use "../../vars" as *; -.dotdungeon .item--aspect { +.dotdungeon:not(.style-v3) .item--aspect { padding: 4px; input[type=text] { diff --git a/styles/sheets/items/custom.scss b/styles/sheets/items/custom.scss index 0899a91..9c5bc96 100644 --- a/styles/sheets/items/custom.scss +++ b/styles/sheets/items/custom.scss @@ -1,4 +1,4 @@ -.item--custom { +.item--custom:not(.style-v3) { padding: 8px; display: grid; grid-template-columns: 1fr 1fr; diff --git a/styles/sheets/items/pet.scss b/styles/sheets/items/pet.scss index 8cfc8e5..10e4d51 100644 --- a/styles/sheets/items/pet.scss +++ b/styles/sheets/items/pet.scss @@ -1,6 +1,6 @@ @use "../../vars" as *; -.dotdungeon .item--pet { +.dotdungeon:not(.style-v3) .item--pet { padding: 4px; input[type=text] { diff --git a/styles/sheets/items/spell.scss b/styles/sheets/items/spell.scss index 21ba81b..8d4405f 100644 --- a/styles/sheets/items/spell.scss +++ b/styles/sheets/items/spell.scss @@ -1,6 +1,6 @@ @use "../../vars" as *; -.dotdungeon .item--spell { +.dotdungeon:not(.style-v3) .item--spell { padding: 4px; input[type=text] { diff --git a/styles/sheets/partials/panel.scss b/styles/sheets/partials/panel.scss index eb77497..2ebf74c 100644 --- a/styles/sheets/partials/panel.scss +++ b/styles/sheets/partials/panel.scss @@ -2,7 +2,7 @@ @use "../../mixins/foundry" as *; @use "../../vars" as *; -.dotdungeon .actor--pc-mvp .panel { +.dotdungeon:not(.style-v3) .actor--pc-mvp .panel { display: grid; grid-template-rows: min-content 1fr; @@ -39,7 +39,7 @@ } @include bp-s { - .dotdungeon .panel__header .icon { + .dotdungeon:not(.style-v3) .panel__header .icon { display: none; visibility: hidden; } diff --git a/styles/sheets/partials/skill.scss b/styles/sheets/partials/skill.scss index 40b43de..6034e86 100644 --- a/styles/sheets/partials/skill.scss +++ b/styles/sheets/partials/skill.scss @@ -1,4 +1,4 @@ -.dotdungeon .skill { +.dotdungeon:not(.style-v3) .skill { display: flex; flex-direction: row; justify-content: center; diff --git a/styles/sheets/partials/stat.scss b/styles/sheets/partials/stat.scss index 1937854..0cfbe76 100644 --- a/styles/sheets/partials/stat.scss +++ b/styles/sheets/partials/stat.scss @@ -1,4 +1,4 @@ -.dotdungeon .actor--pc-mvp .stat { +.dotdungeon:not(.style-v3) .actor--pc-mvp .stat { display: flex; flex-direction: column; align-items: center; diff --git a/styles/v3/components/common.scss b/styles/v3/components/common.scss new file mode 100644 index 0000000..59f812d --- /dev/null +++ b/styles/v3/components/common.scss @@ -0,0 +1,7 @@ +// 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/v3/components/icon.scss b/styles/v3/components/icon.scss new file mode 100644 index 0000000..012593f --- /dev/null +++ b/styles/v3/components/icon.scss @@ -0,0 +1,23 @@ +/* +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); +} diff --git a/styles/v3/components/incrementer.scss b/styles/v3/components/incrementer.scss new file mode 100644 index 0000000..abe9478 --- /dev/null +++ b/styles/v3/components/incrementer.scss @@ -0,0 +1,63 @@ +/* +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 "../mixins/material"; +@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); + @include material.elevate(2); + + &:hover { + @include material.elevate(4); + } + + &:focus-within { + @include material.elevate(6); + } +} + +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/v3/elements/button.scss b/styles/v3/elements/button.scss new file mode 100644 index 0000000..33ad097 --- /dev/null +++ b/styles/v3/elements/button.scss @@ -0,0 +1,46 @@ +@use "../mixins/material" as material; + +.dotdungeon.style-v3 > .window-content { + button, a.button { + @include material.elevate(2); + align-items: center; + border-radius: 4px; + border: none; + box-sizing: border-box; + color: inherit; + cursor: pointer; + display: inline-flex; + font-family: sans-serif; + gap: 4px; + justify-content: center; + margin: 0; + outline: none; + padding: 4px 8px; + transition: all 200ms ease-in-out; + width: initial; + + &:hover, &:focus-visible { + @include material.elevate(4); + text-shadow: none; + } + &:active { + @include material.elevate(6); + } + + &:disabled { + opacity: 50%; + cursor: default; + } + + /* Icon buttons don't use Material styling */ + &.icon { + @include material.undo; + padding: 4px; + + &:focus-visible { + @include material.undo; + // TODO : Accessible focus state + } + } + } +} diff --git a/styles/v3/elements/checkbox.scss b/styles/v3/elements/checkbox.scss new file mode 100644 index 0000000..d3bae5f --- /dev/null +++ b/styles/v3/elements/checkbox.scss @@ -0,0 +1,24 @@ +.dotdungeon.style-v3 { + input[type="checkbox"] { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + border-radius: 2px; + margin: 0; + cursor: pointer; + + background: var(--elevation-8dp-bg); + position: relative; + &:checked::before { + content: ""; + background: var(--checkbox-checked); + border-radius: 3px; + $margin: 4px; + top: $margin; + bottom: $margin; + left: $margin; + right: $margin; + position: absolute; + } + } +} diff --git a/styles/v3/elements/headers.scss b/styles/v3/elements/headers.scss new file mode 100644 index 0000000..0174901 --- /dev/null +++ b/styles/v3/elements/headers.scss @@ -0,0 +1,10 @@ +.dotdungeon.style-v3 > .window-content { + h1, h2, h3, h4, h5, h6 { + border: none; + font-size: 1rem; + margin: 0; + } + h1, .h1 { + font-size: 1.5rem; + } +} diff --git a/styles/v3/elements/hr.scss b/styles/v3/elements/hr.scss new file mode 100644 index 0000000..3f23a68 --- /dev/null +++ b/styles/v3/elements/hr.scss @@ -0,0 +1,6 @@ +.dotdungeon.style-v3 > .window-content hr { + border-color: black; + opacity: 25%; + width: 100%; + grid-column: 1 / -1; +} diff --git a/styles/v3/elements/icons.scss b/styles/v3/elements/icons.scss new file mode 100644 index 0000000..0a44997 --- /dev/null +++ b/styles/v3/elements/icons.scss @@ -0,0 +1,17 @@ +$iconSizes: 12, 14, 16, 18, 20, 22, 24; + +.dotdungeon.style-v3 > .window-content { + .icon { + display: inline-flex; + justify-content: center; + align-items: center; + + // The various icon sizes + @each $size in $iconSizes { + &--#{$size} { + height: #{$size}px; + width: #{$size}px; + }; + } + } +} diff --git a/styles/v3/elements/label.scss b/styles/v3/elements/label.scss new file mode 100644 index 0000000..12e3afb --- /dev/null +++ b/styles/v3/elements/label.scss @@ -0,0 +1,9 @@ +@use "../mixins/material"; + +.dotdungeon.style-v3 > .window-content label { + cursor: pointer; + + &.justify-start { + justify-self: start; + } +} diff --git a/styles/v3/elements/nav.scss b/styles/v3/elements/nav.scss new file mode 100644 index 0000000..6046e41 --- /dev/null +++ b/styles/v3/elements/nav.scss @@ -0,0 +1,11 @@ +@use "../mixins/material" as material; + +.dotdungeon.style-v3 > .window-content { + nav:not(#context-menu) { + display: flex; + flex-direction: row; + overflow-x: auto; + gap: 8px; + padding: 8px; + } +} diff --git a/styles/v3/elements/number-input.scss b/styles/v3/elements/number-input.scss new file mode 100644 index 0000000..ad9ce74 --- /dev/null +++ b/styles/v3/elements/number-input.scss @@ -0,0 +1,20 @@ +@use "../mixins/material"; + +.dotdungeon.style-v3 > .window-content input[type=number] { + outline: none; + border: none; + @include material.elevate(3); + padding: 4px 8px; + color: white; + transition: all 200ms ease-in-out; + text-align: center; + line-height: 1rem; + + &:hover { + @include material.elevate(4); + } + + &:focus-visible { + @include material.elevate(6); + } +} diff --git a/styles/v3/elements/panel.scss b/styles/v3/elements/panel.scss new file mode 100644 index 0000000..de7cf9f --- /dev/null +++ b/styles/v3/elements/panel.scss @@ -0,0 +1,21 @@ +@use "../mixins/material" as material; + +.dotdungeon.style-v3 > .window-content { + .panel { + @include material.elevate(2); + border-radius: 4px; + padding: var(--gap); + + &--row { + @extend .panel; + display: flex; + flex-direction: row; + gap: var(--gap); + align-items: center; + + &.space-between { + justify-content: space-between; + } + } + } +} diff --git a/styles/v3/elements/select.scss b/styles/v3/elements/select.scss new file mode 100644 index 0000000..55125ac --- /dev/null +++ b/styles/v3/elements/select.scss @@ -0,0 +1,26 @@ +@use "../mixins/material"; + +.dotdungeon.style-v3 > .window-content { + select { + outline: none; + border: none; + @include material.elevate(3); + padding: 4px 8px; + color: white; + transition: all 200ms ease-in-out; + width: 6.25rem; + + &:hover { + @include material.elevate(4); + } + + &:focus-visible { + @include material.elevate(6); + } + } + + option { + background: var(--surface); + color: var(--on-surface); + } +} diff --git a/styles/v3/elements/text-input.scss b/styles/v3/elements/text-input.scss new file mode 100644 index 0000000..4c066ed --- /dev/null +++ b/styles/v3/elements/text-input.scss @@ -0,0 +1,20 @@ +@use "../mixins/material"; + +.dotdungeon.style-v3 > .window-content input[type=text] { + outline: none; + border: none; + @include material.elevate(3); + padding: 4px 8px; + color: white; + transition: all 200ms ease-in-out; + width: auto; + height: auto; + + &:hover { + @include material.elevate(4); + } + + &:focus-visible { + @include material.elevate(6); + } +} diff --git a/styles/v3/elements/textarea.scss b/styles/v3/elements/textarea.scss new file mode 100644 index 0000000..bc8adcd --- /dev/null +++ b/styles/v3/elements/textarea.scss @@ -0,0 +1,24 @@ +@use "../mixins/material" as material; + +.dotdungeon.style-v3 > .window-content textarea { + outline: none; + border: none; + @include material.elevate(3); + color: white; + transition: all 200ms ease-in-out; + font-family: inherit; + font-size: 0.9rem; + + &:hover { + @include material.elevate(4); + } + + &:focus-visible { + @include material.elevate(6); + } + + &.no-resize { + @extend textarea; + resize: none; + } +} diff --git a/styles/v3/elements/utilities.scss b/styles/v3/elements/utilities.scss new file mode 100644 index 0000000..32cc792 --- /dev/null +++ b/styles/v3/elements/utilities.scss @@ -0,0 +1,12 @@ +.dotdungeon.style-v3 > .window-content { + .grow { + flex-grow: 1; + } + + .text-center { + text-align: center; + } + .text-right { + text-align: right; + } +} diff --git a/styles/v3/index.scss b/styles/v3/index.scss new file mode 100644 index 0000000..85ec4bf --- /dev/null +++ b/styles/v3/index.scss @@ -0,0 +1,52 @@ +/* Themes */ +@use "./themes/material.css"; +@use "./themes/dark.css"; + +/* Element-Styling */ +@use "./elements/utilities.scss"; +@use "./elements/button.scss"; +@use "./elements/checkbox.scss"; +@use "./elements/select.scss"; +@use "./elements/text-input.scss"; +@use "./elements/number-input.scss"; +@use "./elements/textarea.scss"; +@use "./elements/headers.scss"; +@use "./elements/hr.scss"; +@use "./elements/icons.scss"; +@use "./elements/nav.scss"; +@use "./elements/panel.scss"; +@use "./elements/label.scss"; + +/* Sheet Layouts */ +@use "./layouts/datasheet.scss"; +@use "./layouts/items/untyped/v2.scss"; + +/* Sheet Options */ +.dotdungeon.style-v3 { + --scrollbar-width: 5px; + --scrollbar-handle-color: #782e22; + --scrollbar-handle-border-color: var(--color-border-highlight); + --color-checkbox-checked: inherit; + ::-webkit-scrollbar { + width: var(--scrollbar-width); + } + ::-webkit-scrollbar-thumb { + background: var(--scrollbar-handle-color); + border-color: var(--scrollbar-handle-border-color); + border-radius: 5px; + } + + container-type: size; + + > .window-content { + padding: 0; + background: var(--sheet); + color: var(--on-sheet); + + .debug-data { + opacity: 60%; + font-family: sans-serif; + word-break: break-all; + } + } +} diff --git a/styles/v3/layouts/datasheet.scss b/styles/v3/layouts/datasheet.scss new file mode 100644 index 0000000..aa91e4d --- /dev/null +++ b/styles/v3/layouts/datasheet.scss @@ -0,0 +1,6 @@ +.dotdungeon.style-v3 .datasheet { + display: flex; + flex-direction: column; + gap: 8px; + margin: 8px; +} diff --git a/styles/v3/layouts/items/untyped/v2.scss b/styles/v3/layouts/items/untyped/v2.scss new file mode 100644 index 0000000..d94bf49 --- /dev/null +++ b/styles/v3/layouts/items/untyped/v2.scss @@ -0,0 +1,81 @@ +@use "../../../mixins/material"; +@use "../../../mixins/utils"; + +.dotdungeon.style-v3 .item--untyped { + --gap: 8px; + + .nav-bar { + @include material.elevate(8); + position: absolute; + bottom: 0; + left: 0; + right: 6px; + + nav { + display: flex; + flex-direction: row; + gap: var(--gap); + padding: var(--gap); + } + } + + .page-content { + padding: calc(var(--gap) * 1); + padding-bottom: 60px; + height: 100%; + + > .tab { + height: 100%; + gap: var(--gap); + } + } + + @include utils.tab("general") { + display: grid; + --height: 50px; + grid-template-columns: var(--height) 1fr; + grid-template-rows: var(--height) 1fr; + + .description { + grid-column: 1 / -1; + } + } + + %flex-col { + display: flex; + flex-direction: column; + gap: 8px; + } + + @include utils.tab("details") { + @extend %flex-col; + + .number { + display: grid; + grid-template-columns: 1fr 55px; + align-items: center; + } + } + + @include utils.tab("effects") { + @extend %flex-col; + } + + @include utils.tab("settings") { + @extend %flex-col; + + .capacity-usage, .quantity-capacity, .combat-relevant { + display: flex; + align-items: center; + } + + .capacity { + &--calculated { + display: flex; + flex-direction: row; + align-items: center; + gap: 8px; + } + } + } +} diff --git a/styles/v3/mixins/_material.scss b/styles/v3/mixins/_material.scss new file mode 100644 index 0000000..a869640 --- /dev/null +++ b/styles/v3/mixins/_material.scss @@ -0,0 +1,13 @@ +@mixin elevate($height) { + background-color: var(--elevation-#{$height}dp-bg); + -webkit-box-shadow: 0px 0px #{$height * 1.75}px 0px rgba(0,0,0,0.75); + -moz-box-shadow: 0px 0px #{$height * 1.75}px 0px rgba(0,0,0,0.75); + box-shadow: 0px 0px #{$height * 1.75}px 0px rgba(0,0,0,0.75); +} + +@mixin undo { + background-color: transparent; + -webkit-box-shadow: none; + -moz-box-shadow: none; + box-shadow: none; +} diff --git a/styles/v3/mixins/_utils.scss b/styles/v3/mixins/_utils.scss new file mode 100644 index 0000000..02818c0 --- /dev/null +++ b/styles/v3/mixins/_utils.scss @@ -0,0 +1,5 @@ +@mixin tab($name) { + .tab.active[data-tab="#{$name}"] { + @content; + } +} diff --git a/styles/v3/themes/dark.css b/styles/v3/themes/dark.css new file mode 100644 index 0000000..374056f --- /dev/null +++ b/styles/v3/themes/dark.css @@ -0,0 +1,9 @@ +.dotdungeon.style-v3 { + --sheet: #0a0a0a; + --on-sheet: white; + + --surface: #121212; + --on-surface: white; + + --checkbox-checked: #00d300; +} diff --git a/styles/v3/themes/material.css b/styles/v3/themes/material.css new file mode 100644 index 0000000..7c934dc --- /dev/null +++ b/styles/v3/themes/material.css @@ -0,0 +1,27 @@ +/* +These CSS variables are all used in order to try following Material design as +best as I can. +*/ + +.dotdungeon.style-v3 { + --elevation-0dp-bg: var(--surface); + --elevation-0dp-text: var(--on-surface); + --elevation-1dp-bg: color-mix(in lab, transparent, white 5%); + --elevation-1dp-text: white; + --elevation-2dp-bg: color-mix(in lab, transparent, white 7%); + --elevation-2dp-text: white; + --elevation-3dp-bg: color-mix(in lab, transparent, white 8%); + --elevation-3dp-text: white; + --elevation-4dp-bg: color-mix(in lab, transparent, white 9%); + --elevation-4dp-text: white; + --elevation-6dp-bg: color-mix(in lab, transparent, white 11%); + --elevation-6dp-text: white; + --elevation-8dp-bg: color-mix(in lab, transparent, white 12%); + --elevation-8dp-text: white; + --elevation-12dp-bg: color-mix(in lab, transparent, white 14%); + --elevation-12dp-text: white; + --elevation-16dp-bg: color-mix(in lab, transparent, white 15%); + --elevation-16dp-text: white; + --elevation-24dp-bg: color-mix(in lab, transparent, white 16%); + --elevation-24dp-text: white; +} \ No newline at end of file diff --git a/system.json b/system.json index 3c80e4b..5d3387c 100644 --- a/system.json +++ b/system.json @@ -2,7 +2,7 @@ "id": "dotdungeon", "title": ".dungeon", "description": "", - "version": "0.0.7", + "version": "0.0.9", "download": "https://github.com/Oliver-Akins/foundry.dungeon/releases/latest/download/dotdungeon.zip", "manifest": "https://github.com/Oliver-Akins/foundry.dungeon/releases/latest/download/system.json", "url": "https://github.com/Oliver-Akins/foundry.dungeon", diff --git a/templates/actors/char-sheet/v2/partials/inventory/inventory.v2.pc.hbs b/templates/actors/char-sheet/v2/partials/inventory/inventory.v2.pc.hbs index 0468990..f4e2bd5 100644 --- a/templates/actors/char-sheet/v2/partials/inventory/inventory.v2.pc.hbs +++ b/templates/actors/char-sheet/v2/partials/inventory/inventory.v2.pc.hbs @@ -1,4 +1,4 @@
{{> dotdungeon.pc.v2.storage }} {{> dotdungeon.pc.v2.player }} -
\ No newline at end of file + diff --git a/templates/actors/char-sheet/v2/partials/inventory/item-list.v2.pc.hbs b/templates/actors/char-sheet/v2/partials/inventory/item-list.v2.pc.hbs index b3905eb..73926f9 100644 --- a/templates/actors/char-sheet/v2/partials/inventory/item-list.v2.pc.hbs +++ b/templates/actors/char-sheet/v2/partials/inventory/item-list.v2.pc.hbs @@ -11,16 +11,13 @@

{{filter.label}}


@@ -34,4 +31,4 @@ {{/if}} {{/each}} - \ No newline at end of file + diff --git a/templates/actors/char-sheet/v2/partials/inventory/items/aspect.v2.pc.hbs b/templates/actors/char-sheet/v2/partials/inventory/items/aspect.v2.pc.hbs index 9381ec4..ff106e6 100644 --- a/templates/actors/char-sheet/v2/partials/inventory/items/aspect.v2.pc.hbs +++ b/templates/actors/char-sheet/v2/partials/inventory/items/aspect.v2.pc.hbs @@ -8,14 +8,12 @@ >

{{item.name}} @@ -32,7 +30,6 @@ type="checkbox" {{checked item.system.used}} id="{{meta.idp}}-{{item.uuid}}-used" - value="{{item.system.used}}" data-embedded-id="{{item.uuid}}" data-embedded-update="system.used" data-embedded-update-on="change" @@ -86,8 +83,7 @@ data-tooltip="{{dd-i18n 'dotdungeon.common.delete'}}" data-tooltip-direction="RIGHT" class="aspect__button--delete" - data-embedded-delete - data-embedded-id="{{item.uuid}}" + data-embedded-delete="{{item.uuid}}" > \ No newline at end of file + diff --git a/templates/actors/char-sheet/v2/sheet.hbs b/templates/actors/char-sheet/v2/sheet.hbs index ac8702a..b32fac7 100644 --- a/templates/actors/char-sheet/v2/sheet.hbs +++ b/templates/actors/char-sheet/v2/sheet.hbs @@ -1,6 +1,6 @@
{{!-- All panels here --}} -
+
Avatar
{{> dotdungeon.pc.v2.stats }}
Combat
@@ -8,7 +8,7 @@
Spells
Settings
{{> dotdungeon.pc.v2.effects }} -
+ {{!-- Tab list here --}} diff --git a/templates/actors/mobs/main.v2.hbs b/templates/actors/mobs/main.v2.hbs new file mode 100644 index 0000000..3e701f2 --- /dev/null +++ b/templates/actors/mobs/main.v2.hbs @@ -0,0 +1,32 @@ + +
+ +
+ Dice: + + + +
+ +
+
+
+
Combat
+
About
+
+
+ Combat Tab +
+
+
\ No newline at end of file diff --git a/templates/actors/sync/basic.hbs b/templates/actors/sync/basic.hbs index 93a0e84..209e064 100644 --- a/templates/actors/sync/basic.hbs +++ b/templates/actors/sync/basic.hbs @@ -1,6 +1,6 @@