mirror of
https://gitgud.io/BondageProjects/Bondage-College.git
synced 2025-04-24 01:09:19 +00:00
Merge branch 'craft-focus' into 'master'
ENH: Add a focus grid for filtering crafting items by group See merge request BondageProjects/Bondage-College!5493
This commit is contained in:
commit
8625a09b8e
5 changed files with 250 additions and 42 deletions
BondageClub
|
@ -132,6 +132,11 @@
|
||||||
height: calc(2.5 * var(--button-size));
|
height: calc(2.5 * var(--button-size));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#crafting-asset-grid button[data-unload]:not([aria-checked="true"]),
|
||||||
|
#crafting-asset-grid button[data-unload-group]:not([aria-checked="true"]) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
#crafting-property-grid button {
|
#crafting-property-grid button {
|
||||||
width: calc(5.7 * var(--button-size));
|
width: calc(5.7 * var(--button-size));
|
||||||
height: calc(1.5 * var(--button-size));
|
height: calc(1.5 * var(--button-size));
|
||||||
|
|
|
@ -320,6 +320,31 @@
|
||||||
padding-inline: 0.15em 0.15em;
|
padding-inline: 0.15em 0.15em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dialog-focus-grid [role="radiogroup"] {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border: min(0.3vh, 0.15vw) solid #80808040;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-focus-grid [role="radio"] {
|
||||||
|
position: absolute;
|
||||||
|
border: min(0.3vh, 0.15vw) solid #80808040;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-focus-grid [role="radio"][data-blocked] {
|
||||||
|
border-color: #88000580;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-focus-grid [role="radio"][data-equipped] {
|
||||||
|
border-color: #D5A30080;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-focus-grid [role="radio"][aria-checked="true"] {
|
||||||
|
border-width: min(0.5vh, 0.25vw);
|
||||||
|
border-color: cyan;
|
||||||
|
}
|
||||||
|
|
||||||
@supports(height: 100dvh) {
|
@supports(height: 100dvh) {
|
||||||
.dialog-root {
|
.dialog-root {
|
||||||
--menu-button-size: min(9dvh, 4.5dvw);
|
--menu-button-size: min(9dvh, 4.5dvw);
|
||||||
|
@ -334,6 +359,18 @@
|
||||||
.dialog-grid-button > .button-tooltip {
|
.dialog-grid-button > .button-tooltip {
|
||||||
width: calc(400% + 4 * min(0.2dvh, 0.1dvw) + 3 * var(--gap));
|
width: calc(400% + 4 * min(0.2dvh, 0.1dvw) + 3 * var(--gap));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dialog-focus-grid [role="radiogroup"] {
|
||||||
|
border-width: min(0.3dvh, 0.15dvw);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-focus-grid [role="radio"] {
|
||||||
|
border-width: min(0.3dvh, 0.15dvw);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-focus-grid [role="radio"][aria-checked="true"] {
|
||||||
|
border-width: min(0.5dvh, 0.25dvw);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@supports (background-color: color-mix(in srgb, black 50%, transparent)) {
|
@supports (background-color: color-mix(in srgb, black 50%, transparent)) {
|
||||||
|
|
|
@ -748,10 +748,6 @@ var CraftingEventListeners = {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trigger a search query in order to filter the results by whatever input the user has specified
|
|
||||||
const searchInput = sidePannel.querySelector("input[type='search']");
|
|
||||||
searchInput?.dispatchEvent(new Event("input"));
|
|
||||||
|
|
||||||
if (this.getAttribute("aria-checked") === "true") {
|
if (this.getAttribute("aria-checked") === "true") {
|
||||||
controlButton.innerHTML = this.innerHTML;
|
controlButton.innerHTML = this.innerHTML;
|
||||||
return;
|
return;
|
||||||
|
@ -780,8 +776,8 @@ var CraftingEventListeners = {
|
||||||
searchResultCandidates?.querySelectorAll("button.button").forEach(button => {
|
searchResultCandidates?.querySelectorAll("button.button").forEach(button => {
|
||||||
const label = button.querySelector(".button-label");
|
const label = button.querySelector(".button-label");
|
||||||
if (label) {
|
if (label) {
|
||||||
const displayStyle = (button.getAttribute("aria-checked") === "true" || label.textContent.toUpperCase().includes(query)) ? "" : "none";
|
const displayStyle = (button.getAttribute("aria-checked") === "true" || label.textContent.toUpperCase().includes(query)) ? false : true;
|
||||||
/** @type {HTMLButtonElement} */(button).style.display = displayStyle;
|
button.toggleAttribute("data-unload", displayStyle);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -808,21 +804,92 @@ var CraftingEventListeners = {
|
||||||
}
|
}
|
||||||
descriptionInput.dispatchEvent(new InputEvent("input"));
|
descriptionInput.dispatchEvent(new InputEvent("input"));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @type {(this: HTMLButtonElement, ev: MouseEvent) => void}
|
||||||
|
*/
|
||||||
|
_ClickGroup: function _ClickGroup(ev) {
|
||||||
|
const groupName = /** @type {AssetGroupItemName} */(this.name);
|
||||||
|
const assetList = document.getElementById(CraftingID.assetGrid);
|
||||||
|
if (!assetList) {
|
||||||
|
ev.stopImmediatePropagation();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.getAttribute("aria-checked") === "true") {
|
||||||
|
// Apply the filtering
|
||||||
|
for (const button of assetList.children) {
|
||||||
|
button.toggleAttribute("data-unload-group", button.getAttribute("data-group") !== groupName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the label of the asset panel
|
||||||
|
document.querySelector(`#${CraftingID.assetHeader} > span`)?.replaceChildren(
|
||||||
|
`${TextGet("SelectItem")} ${TextGet("SelectItemSuffix")} ${this.getAttribute("aria-label").toLocaleLowerCase()}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Make sure that the asset panel is open and scroll to the top
|
||||||
|
document.querySelector(`#${CraftingID.assetButton}[aria-checked="false"]`)?.dispatchEvent(new MouseEvent("click"));
|
||||||
|
assetList.scrollTo({ top: 0 });
|
||||||
|
} else {
|
||||||
|
document.querySelector(`#${CraftingID.assetHeader} > span`)?.replaceChildren(TextGet("SelectItem"));
|
||||||
|
for (const button of assetList.children) {
|
||||||
|
button.toggleAttribute("data-unload-group", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
const checked = assetList.querySelector("[aria-checked='true']");
|
||||||
|
if (checked) {
|
||||||
|
checked.scrollIntoView({ behavior: "instant" });
|
||||||
|
} else {
|
||||||
|
assetList.scrollTo({ top: 0 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @type {(this: HTMLInputElement, ev: FocusEvent) => Promise<void>}
|
||||||
|
*/
|
||||||
|
_FocusSearchAsset: async function _FocusSearchAsset(ev) {
|
||||||
|
const focusGrid = document.getElementById(CraftingID.centerPanel);
|
||||||
|
if (!focusGrid) {
|
||||||
|
ev.stopImmediatePropagation();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const group = /** @type {"ALL" | AssetGroupItemName} */(focusGrid.querySelector("[role='radio'][aria-checked='true']")?.getAttribute("name") ?? "ALL");
|
||||||
|
const cachedGroup = this.getAttribute("data-group");
|
||||||
|
if (cachedGroup === group) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let options = CraftingElements._SearchCache.get(group);
|
||||||
|
if (!options) {
|
||||||
|
const searchResults = document.getElementById(this.getAttribute("aria-controls"));
|
||||||
|
const query = group === "ALL" ? ".button-label" : `[data-group='${group}'] .button-label`;
|
||||||
|
options = Array.from(searchResults?.querySelectorAll(query) ?? []).map(e => ElementCreate({ tag: "option", attributes: { value: e.textContent }}));
|
||||||
|
CraftingElements._SearchCache.set(group, options);
|
||||||
|
}
|
||||||
|
this.list?.replaceChildren(...options);
|
||||||
|
this.setAttribute("data-group", group);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @type {(this: HTMLInputElement, ev: FocusEvent) => Promise<void>}
|
||||||
|
*/
|
||||||
|
_FocusSearch: async function _FocusSearch(ev) {
|
||||||
|
if (this.list?.options.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const searchResults = document.getElementById(this.getAttribute("aria-controls"));
|
||||||
|
const options = Array.from(searchResults?.querySelectorAll(".button-label") ?? []).map(e => ElementCreate({ tag: "option", attributes: { value: e.textContent }}));
|
||||||
|
this.list?.replaceChildren(...options);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
var CraftingElements = {
|
var CraftingElements = {
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
* @param {string} controls
|
|
||||||
* @returns {() => string[]}
|
|
||||||
*/
|
|
||||||
_SearchInputGetDataList: function _SearchInputGetDataList(controls) {
|
|
||||||
return () => {
|
|
||||||
const searchResults = document.getElementById(controls);
|
|
||||||
return Array.from(searchResults?.querySelectorAll("button > label") ?? []).map(e => e.textContent);
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
* @param {string} id
|
* @param {string} id
|
||||||
|
@ -830,15 +897,35 @@ var CraftingElements = {
|
||||||
* @param {string} placeholder
|
* @param {string} placeholder
|
||||||
* @returns {HTMLInputElement}
|
* @returns {HTMLInputElement}
|
||||||
*/
|
*/
|
||||||
_SearchInput: function _SearchInput(id, controls, placeholder) {
|
_SearchInput: function _SearchInput(id, controls, placeholder, assetSearch=false) {
|
||||||
const ret = ElementCreateSearchInput(id, CraftingElements._SearchInputGetDataList(controls));
|
return ElementCreate({
|
||||||
ret.setAttribute("aria-controls", controls);
|
tag: "input",
|
||||||
ret.setAttribute("size", 0);
|
attributes: {
|
||||||
ret.addEventListener("input", CraftingEventListeners._InputSearch);
|
type: "search",
|
||||||
ret.placeholder = placeholder;
|
id,
|
||||||
return ret;
|
placeholder,
|
||||||
|
list: `${id}-datalist`,
|
||||||
|
size: 0,
|
||||||
|
"aria-controls": controls,
|
||||||
|
},
|
||||||
|
dataAttributes: {
|
||||||
|
group: undefined, // Initialized and managed by the `focus` event listener for asset searches
|
||||||
|
},
|
||||||
|
eventListeners: {
|
||||||
|
input: CraftingEventListeners._InputSearch,
|
||||||
|
focus: assetSearch ? CraftingEventListeners._FocusSearchAsset : CraftingEventListeners._FocusSearch,
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{ tag: "datalist", attributes: { id: `${id}-datalist` } },
|
||||||
|
],
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {Map<"ALL" | AssetGroupItemName, readonly HTMLOptionElement[]>}
|
||||||
|
*/
|
||||||
|
_SearchCache: new Map(),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
* @param {string} id
|
* @param {string} id
|
||||||
|
@ -941,6 +1028,11 @@ function CraftingLoad() {
|
||||||
),
|
),
|
||||||
ElementButton.Create(CraftingID.uploadButton, CraftingEventListeners._ClickUpload, { tooltip: TextGet("Upload") }),
|
ElementButton.Create(CraftingID.uploadButton, CraftingEventListeners._ClickUpload, { tooltip: TextGet("Upload") }),
|
||||||
ElementButton.Create(CraftingID.downloadButton, CraftingEventListeners._ClickDownload, { tooltip: TextGet("Download") }),
|
ElementButton.Create(CraftingID.downloadButton, CraftingEventListeners._ClickDownload, { tooltip: TextGet("Download") }),
|
||||||
|
ElementButton.Create(
|
||||||
|
CraftingID.undressButton, CraftingEventListeners._ClickUndress,
|
||||||
|
{ tooltip: TextGet("Undress"), role: "menuitemcheckbox" },
|
||||||
|
{ button: { attributes: { "aria-checked": CraftingNakedPreview ? "true" : "false" } } },
|
||||||
|
),
|
||||||
],
|
],
|
||||||
{ direction: "rtl" },
|
{ direction: "rtl" },
|
||||||
),
|
),
|
||||||
|
@ -1019,7 +1111,7 @@ function CraftingLoad() {
|
||||||
attributes: { id: CraftingID.assetHeader },
|
attributes: { id: CraftingID.assetHeader },
|
||||||
children: [
|
children: [
|
||||||
{ tag: "span", children: [TextGet("SelectItem")] },
|
{ tag: "span", children: [TextGet("SelectItem")] },
|
||||||
CraftingElements._SearchInput(CraftingID.assetSearch, CraftingID.assetGrid, TextGet("FilterAsset")),
|
CraftingElements._SearchInput(CraftingID.assetSearch, CraftingID.assetGrid, TextGet("FilterAsset"), true),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -1076,14 +1168,7 @@ function CraftingLoad() {
|
||||||
{ menu: { attributes: { "aria-orientation": "vertical" }, parent } },
|
{ menu: { attributes: { "aria-orientation": "vertical" }, parent } },
|
||||||
),
|
),
|
||||||
|
|
||||||
ElementCreate({
|
parent.append(DialogFocusGroup.Create(CraftingID.centerPanel, CraftingEventListeners._ClickGroup, { useDynamicGroupName: true }));
|
||||||
tag: "div",
|
|
||||||
attributes: { id: CraftingID.centerPanel },
|
|
||||||
parent,
|
|
||||||
children: [
|
|
||||||
ElementButton.Create(CraftingID.undressButton, CraftingEventListeners._ClickUndress),
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
ElementCreate({
|
ElementCreate({
|
||||||
tag: "div",
|
tag: "div",
|
||||||
|
@ -1920,12 +2005,13 @@ function CraftingExitResetElements() {
|
||||||
|
|
||||||
// Clear all search inputs and undo their filtering
|
// Clear all search inputs and undo their filtering
|
||||||
const searchInputs = /** @type {NodeListOf<HTMLInputElement>} */(document.querySelectorAll(`#${CraftingID.leftPanel} input[type='search']`));
|
const searchInputs = /** @type {NodeListOf<HTMLInputElement>} */(document.querySelectorAll(`#${CraftingID.leftPanel} input[type='search']`));
|
||||||
searchInputs.forEach((searchInp) => {
|
searchInputs.forEach((searchInp) => searchInp.value ||= "");
|
||||||
if (searchInp.value) {
|
|
||||||
searchInp.value = "";
|
const focusGroup = document.querySelector(`#${CraftingID.centerPanel} [role='radio'][aria-checked='true']`);
|
||||||
searchInp.dispatchEvent(new Event("input"));
|
if (focusGroup) {
|
||||||
}
|
focusGroup.dispatchEvent(new MouseEvent("click"));
|
||||||
});
|
document.querySelectorAll(`#${CraftingID.assetGrid} [data-unload-group]`).forEach(e => e.toggleAttribute("data-unload-group", false));
|
||||||
|
}
|
||||||
|
|
||||||
// Close the side pannel
|
// Close the side pannel
|
||||||
document.querySelector(`#${CraftingID.leftPanel} > [aria-checked='true']`)?.dispatchEvent(new Event("click"));
|
document.querySelector(`#${CraftingID.leftPanel} > [aria-checked='true']`)?.dispatchEvent(new Event("click"));
|
||||||
|
@ -1951,8 +2037,10 @@ function CraftingExit(allowPanelClose=true) {
|
||||||
return;
|
return;
|
||||||
case "Name": {
|
case "Name": {
|
||||||
const activePanel = document.querySelector(`#${CraftingID.leftPanel} > [aria-checked='true']`);
|
const activePanel = document.querySelector(`#${CraftingID.leftPanel} > [aria-checked='true']`);
|
||||||
if (activePanel && allowPanelClose) {
|
const activeGroup = document.querySelector(`#${CraftingID.centerPanel} [aria-checked='true']`);
|
||||||
activePanel.dispatchEvent(new Event("click"));
|
if ((activePanel || activeGroup) && allowPanelClose) {
|
||||||
|
activePanel?.dispatchEvent(new MouseEvent("click"));
|
||||||
|
activeGroup?.dispatchEvent(new MouseEvent("click"));
|
||||||
} else {
|
} else {
|
||||||
CraftingExitResetElements();
|
CraftingExitResetElements();
|
||||||
CraftingUnload();
|
CraftingUnload();
|
||||||
|
@ -1964,6 +2052,7 @@ function CraftingExit(allowPanelClose=true) {
|
||||||
case "Slot": {
|
case "Slot": {
|
||||||
ElementRemove(CraftingID.root);
|
ElementRemove(CraftingID.root);
|
||||||
CharacterDelete(CraftingPreview);
|
CharacterDelete(CraftingPreview);
|
||||||
|
CraftingElements._SearchCache.clear();
|
||||||
CraftingPreview = null;
|
CraftingPreview = null;
|
||||||
CraftingOffset = 0;
|
CraftingOffset = 0;
|
||||||
CraftingDestroy = false;
|
CraftingDestroy = false;
|
||||||
|
|
|
@ -13,6 +13,7 @@ NoLock,No lock
|
||||||
SelectSlot,"Select an empty slot to craft a new item, or click on the item to edit. Page"
|
SelectSlot,"Select an empty slot to craft a new item, or click on the item to edit. Page"
|
||||||
SelectDestroy,Select a crafted item slot to destroy. Page
|
SelectDestroy,Select a crafted item slot to destroy. Page
|
||||||
SelectItem,Select an item
|
SelectItem,Select an item
|
||||||
|
SelectItemSuffix,for the
|
||||||
SelectProperty,Select an item property
|
SelectProperty,Select an item property
|
||||||
SelectLock,Select a lock
|
SelectLock,Select a lock
|
||||||
SelectName,Configure the crafted item
|
SelectName,Configure the crafted item
|
||||||
|
@ -83,3 +84,4 @@ Upload,Import crafting code
|
||||||
UploadPrompt,Please paste the crafting code. Importing crafting codes will overwrite existing settings. Are you sure?
|
UploadPrompt,Please paste the crafting code. Importing crafting codes will overwrite existing settings. Are you sure?
|
||||||
UploadSucces,Crafting code successfully parsed
|
UploadSucces,Crafting code successfully parsed
|
||||||
UploadFailure,Failed to parse the passed crafting code
|
UploadFailure,Failed to parse the passed crafting code
|
||||||
|
Undress,Undress character preview
|
||||||
|
|
|
|
@ -4481,6 +4481,81 @@ function DialogDrawTopMenu(C) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var DialogFocusGroup = {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} id - The ID for the to-be created focus group grid
|
||||||
|
* @param {(this: HTMLButtonElement, ev: MouseEvent) => any} listener - The listener to-be executed upon selecting a group; the group name can be retrieved from `this.name`
|
||||||
|
* @param {null | { required?: boolean, useDynamicGroupName?: boolean }} options - Further options for the to-be created focus group grid
|
||||||
|
* @returns {HTMLElement} - The created element
|
||||||
|
*/
|
||||||
|
Create(id, listener, options=null) {
|
||||||
|
options ??= {};
|
||||||
|
const root = document.getElementById(id);
|
||||||
|
if (root) {
|
||||||
|
console.error(`Element "${id}" already exists`);
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
let top = Infinity;
|
||||||
|
let bottom = 0;
|
||||||
|
let left = Infinity;
|
||||||
|
let right = 0;
|
||||||
|
|
||||||
|
/** @type {{ group: AssetGroup, index: number, zone: RectTuple }[]} */
|
||||||
|
const grid = [];
|
||||||
|
for (const group of AssetGroup) {
|
||||||
|
for (const [index, zone] of (group.Zone ?? []).entries()) {
|
||||||
|
grid.push({ group, index, zone});
|
||||||
|
left = Math.min(left, zone[0]);
|
||||||
|
right = Math.max(right, zone[0] + zone[2]);
|
||||||
|
top = Math.min(top, zone[1]);
|
||||||
|
bottom = Math.max(bottom, zone[1] + zone[3]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
grid.sort((a, b) => (a.zone[1] - b.zone[1]) || (a.zone[0] - b.zone[0]));
|
||||||
|
|
||||||
|
const width = right - left;
|
||||||
|
const height = bottom - top;
|
||||||
|
|
||||||
|
const children = grid.map(({ group, index, zone }, i) => ElementButton.Create(
|
||||||
|
`${id}-${group.Name}-${index}`,
|
||||||
|
listener,
|
||||||
|
{ noStyling: true, role: "radio" },
|
||||||
|
{ button: {
|
||||||
|
attributes: {
|
||||||
|
name: options.useDynamicGroupName ? group.DynamicGroupName : group.Name,
|
||||||
|
tabindex: i === 0 ? 0 : -1,
|
||||||
|
"aria-hidden": index !== 0 ? "true" : undefined,
|
||||||
|
"aria-label": group.Description,
|
||||||
|
},
|
||||||
|
style: {
|
||||||
|
left: `${100 * (zone[0] - left) / width}%`,
|
||||||
|
top: `${100 * (zone[1] - top) / height}%`,
|
||||||
|
width: `${100 * (zone[2] / width)}%`,
|
||||||
|
height: `${100 * (zone[3] / height)}%`,
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
));
|
||||||
|
|
||||||
|
return ElementCreate({
|
||||||
|
tag: "div",
|
||||||
|
attributes: { id },
|
||||||
|
classList: ["dialog-focus-grid"],
|
||||||
|
children: [{
|
||||||
|
tag: "div",
|
||||||
|
children,
|
||||||
|
attributes: {
|
||||||
|
id: `${id}-radiogroup`,
|
||||||
|
role: "radiogroup",
|
||||||
|
"aria-required": options.required ? "true" : "false",
|
||||||
|
"aria-label": "Select focus group",
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Draws the left menu for the character
|
* Draws the left menu for the character
|
||||||
* @param {Character} C - The currently focused character
|
* @param {Character} C - The currently focused character
|
||||||
|
|
Loading…
Add table
Reference in a new issue