add scaling option for fear tracker
This commit is contained in:
parent
318498489b
commit
737690f42a
3 changed files with 262 additions and 16 deletions
11
lang/en.json
11
lang/en.json
|
|
@ -17,5 +17,14 @@
|
||||||
"COSMOS_DH_TWEAKS.Settings.ResetFearPosition.ButtonText": "Reset Position",
|
"COSMOS_DH_TWEAKS.Settings.ResetFearPosition.ButtonText": "Reset Position",
|
||||||
"COSMOS_DH_TWEAKS.Settings.ResetFearPosition.Success": "Fear tracker position has been reset.",
|
"COSMOS_DH_TWEAKS.Settings.ResetFearPosition.Success": "Fear tracker position has been reset.",
|
||||||
"COSMOS_DH_TWEAKS.ContextMenu.LockPosition": "Lock Position",
|
"COSMOS_DH_TWEAKS.ContextMenu.LockPosition": "Lock Position",
|
||||||
"COSMOS_DH_TWEAKS.ContextMenu.UnlockPosition": "Unlock Position"
|
"COSMOS_DH_TWEAKS.ContextMenu.UnlockPosition": "Unlock Position",
|
||||||
|
"COSMOS_DH_TWEAKS.Settings.FearTrackerScale.Name": "Fear Tracker Scale",
|
||||||
|
"COSMOS_DH_TWEAKS.Settings.FearTrackerScale.Hint": "Scale the size of the Fear Tracker window and its icons.",
|
||||||
|
"COSMOS_DH_TWEAKS.Settings.ResetFearScale.Name": "Reset Fear Tracker Scale",
|
||||||
|
"COSMOS_DH_TWEAKS.Settings.ResetFearScale.Hint": "Resets the Fear Tracker scale back to 100%.",
|
||||||
|
"COSMOS_DH_TWEAKS.Settings.ResetFearScale.ButtonText": "Reset Scale",
|
||||||
|
"COSMOS_DH_TWEAKS.Settings.ResetFearScale.Success": "Fear tracker scale has been reset.",
|
||||||
|
"COSMOS_DH_TWEAKS.ContextMenu.ResetPosition": "Reset Position",
|
||||||
|
"COSMOS_DH_TWEAKS.ContextMenu.ResetScale": "Reset Scale",
|
||||||
|
"COSMOS_DH_TWEAKS.ContextMenu.ScaleHeader": "Scale"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,35 @@ Hooks.once("init", () => {
|
||||||
default: false
|
default: false
|
||||||
});
|
});
|
||||||
|
|
||||||
|
game.settings.register("cosmos-dh-tweaks", "fearTrackerScale", {
|
||||||
|
name: "COSMOS_DH_TWEAKS.Settings.FearTrackerScale.Name",
|
||||||
|
hint: "COSMOS_DH_TWEAKS.Settings.FearTrackerScale.Hint",
|
||||||
|
scope: "client",
|
||||||
|
config: true,
|
||||||
|
type: Number,
|
||||||
|
range: {
|
||||||
|
min: 0.5,
|
||||||
|
max: 1.5,
|
||||||
|
step: 0.05
|
||||||
|
},
|
||||||
|
default: 1.0,
|
||||||
|
onChange: () => {
|
||||||
|
if (ui.resources && ui.resources.rendered) {
|
||||||
|
ui.resources.updateScale();
|
||||||
|
ui.resources.adjustSizeToMaxFear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
game.settings.register("cosmos-dh-tweaks", "resetFearScale", {
|
||||||
|
name: "COSMOS_DH_TWEAKS.Settings.ResetFearScale.Name",
|
||||||
|
hint: "COSMOS_DH_TWEAKS.Settings.ResetFearScale.Hint",
|
||||||
|
scope: "client",
|
||||||
|
config: true,
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
});
|
||||||
|
|
||||||
// Override the FearTracker class in CONFIG
|
// Override the FearTracker class in CONFIG
|
||||||
const OriginalFearTracker = CONFIG.ui.resources;
|
const OriginalFearTracker = CONFIG.ui.resources;
|
||||||
if (OriginalFearTracker) {
|
if (OriginalFearTracker) {
|
||||||
|
|
@ -106,9 +135,16 @@ Hooks.once("init", () => {
|
||||||
|
|
||||||
this.updateTitleBarStatus();
|
this.updateTitleBarStatus();
|
||||||
this.updateLockStatus();
|
this.updateLockStatus();
|
||||||
|
this.updateScale();
|
||||||
this.adjustSizeToMaxFear();
|
this.adjustSizeToMaxFear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateScale() {
|
||||||
|
if (!this.element) return;
|
||||||
|
const scale = game.settings.get("cosmos-dh-tweaks", "fearTrackerScale") ?? 1.0;
|
||||||
|
this.element.style.setProperty('--cosmo-fear-scale', scale);
|
||||||
|
}
|
||||||
|
|
||||||
updateTitleBarStatus() {
|
updateTitleBarStatus() {
|
||||||
if (!this.element) return;
|
if (!this.element) return;
|
||||||
const removeTitleBar = game.settings.get("cosmos-dh-tweaks", "removeFearTitleBar");
|
const removeTitleBar = game.settings.get("cosmos-dh-tweaks", "removeFearTitleBar");
|
||||||
|
|
@ -192,24 +228,35 @@ Hooks.once("init", () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
calculatePerfectSize(targetWidth, measurements) {
|
calculatePerfectSize(targetWidth, measurements) {
|
||||||
const { skullW, skullH, gapX, gapY, padX, padY, headerH } = measurements;
|
const scale = game.settings.get("cosmos-dh-tweaks", "fearTrackerScale") ?? 1.0;
|
||||||
|
|
||||||
|
// Divide measurements by scale to work with unscaled base measurements
|
||||||
|
const skullW = measurements.skullW / scale;
|
||||||
|
const skullH = measurements.skullH / scale;
|
||||||
|
const gapX = measurements.gapX / scale;
|
||||||
|
const gapY = measurements.gapY / scale;
|
||||||
|
const padX = measurements.padX / scale;
|
||||||
|
const padY = measurements.padY / scale;
|
||||||
|
const headerH = measurements.headerH / scale;
|
||||||
|
|
||||||
const display = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance).displayFear;
|
const display = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.appearance).displayFear;
|
||||||
const maxFear = this.maxFear;
|
const maxFear = this.maxFear;
|
||||||
|
|
||||||
if (display === 'bar') {
|
if (display === 'bar') {
|
||||||
const barElement = this.element.querySelector('#resource-fear');
|
const barElement = this.element.querySelector('#resource-fear');
|
||||||
const barH = barElement ? barElement.getBoundingClientRect().height : 30;
|
const barH = barElement ? (barElement.getBoundingClientRect().height / scale) : 30;
|
||||||
const perfectH = barH + padY + headerH;
|
const perfectH = barH + padY + headerH;
|
||||||
const minW = 200;
|
const minW = 200;
|
||||||
const perfectW = Math.max(minW, targetWidth || 222);
|
const targetWUnscaled = targetWidth ? (targetWidth / scale) : 222;
|
||||||
|
const perfectW = Math.max(minW, targetWUnscaled);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
width: perfectW,
|
width: perfectW * scale,
|
||||||
height: perfectH
|
height: perfectH * scale
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
const w = targetWidth || 222;
|
const targetWUnscaled = targetWidth ? (targetWidth / scale) : 222;
|
||||||
let cols = Math.round((w - padX + gapX) / (skullW + gapX));
|
let cols = Math.round((targetWUnscaled - padX + gapX) / (skullW + gapX));
|
||||||
cols = Math.max(1, Math.min(maxFear, cols));
|
cols = Math.max(1, Math.min(maxFear, cols));
|
||||||
|
|
||||||
const perfectW = cols * skullW + (cols - 1) * gapX + padX + 2;
|
const perfectW = cols * skullW + (cols - 1) * gapX + padX + 2;
|
||||||
|
|
@ -217,8 +264,8 @@ Hooks.once("init", () => {
|
||||||
const perfectH = rows * skullH + (rows - 1) * gapY + padY + headerH + 2;
|
const perfectH = rows * skullH + (rows - 1) * gapY + padY + headerH + 2;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
width: perfectW,
|
width: perfectW * scale,
|
||||||
height: perfectH
|
height: perfectH * scale
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -289,6 +336,7 @@ Hooks.once("init", () => {
|
||||||
if (existing) existing.remove();
|
if (existing) existing.remove();
|
||||||
|
|
||||||
const isLocked = game.settings.get("cosmos-dh-tweaks", "lockFearTrackerPosition");
|
const isLocked = game.settings.get("cosmos-dh-tweaks", "lockFearTrackerPosition");
|
||||||
|
const currentScale = game.settings.get("cosmos-dh-tweaks", "fearTrackerScale") ?? 1.0;
|
||||||
const menu = document.createElement('div');
|
const menu = document.createElement('div');
|
||||||
menu.className = 'cosmo-fear-context-menu';
|
menu.className = 'cosmo-fear-context-menu';
|
||||||
|
|
||||||
|
|
@ -301,6 +349,20 @@ Hooks.once("init", () => {
|
||||||
<li class="cosmo-context-menu-item lock-toggle" data-action="toggle-lock">
|
<li class="cosmo-context-menu-item lock-toggle" data-action="toggle-lock">
|
||||||
${lockText}
|
${lockText}
|
||||||
</li>
|
</li>
|
||||||
|
<li class="cosmo-context-menu-item reset-position" data-action="reset-position">
|
||||||
|
<i class="fa-solid fa-arrows-rotate"></i> ${game.i18n.localize("COSMOS_DH_TWEAKS.ContextMenu.ResetPosition")}
|
||||||
|
</li>
|
||||||
|
<li class="cosmo-context-menu-item reset-scale" data-action="reset-scale">
|
||||||
|
<i class="fa-solid fa-expand"></i> ${game.i18n.localize("COSMOS_DH_TWEAKS.ContextMenu.ResetScale")}
|
||||||
|
</li>
|
||||||
|
<li class="cosmo-context-menu-header">${game.i18n.localize("COSMOS_DH_TWEAKS.ContextMenu.ScaleHeader")}</li>
|
||||||
|
<li class="cosmo-context-menu-scale-controls">
|
||||||
|
<button class="scale-btn ${Math.abs(currentScale - 0.7) < 0.01 ? 'active' : ''}" data-scale="0.7">70%</button>
|
||||||
|
<button class="scale-btn ${Math.abs(currentScale - 0.85) < 0.01 ? 'active' : ''}" data-scale="0.85">85%</button>
|
||||||
|
<button class="scale-btn ${Math.abs(currentScale - 1.0) < 0.01 ? 'active' : ''}" data-scale="1.0">100%</button>
|
||||||
|
<button class="scale-btn ${Math.abs(currentScale - 1.15) < 0.01 ? 'active' : ''}" data-scale="1.15">115%</button>
|
||||||
|
<button class="scale-btn ${Math.abs(currentScale - 1.3) < 0.01 ? 'active' : ''}" data-scale="1.3">130%</button>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|
@ -319,6 +381,37 @@ Hooks.once("init", () => {
|
||||||
menu.remove();
|
menu.remove();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
menu.querySelector('[data-action="reset-position"]').addEventListener('click', async (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
await game.user.unsetFlag(CONFIG.DH.id, 'app.resources.position');
|
||||||
|
if (this.rendered) {
|
||||||
|
const defaultPos = CONFIG.ui.resources.DEFAULT_OPTIONS.position;
|
||||||
|
await this.setPosition(defaultPos);
|
||||||
|
}
|
||||||
|
ui.notifications.info(game.i18n.localize("COSMOS_DH_TWEAKS.Settings.ResetFearPosition.Success"));
|
||||||
|
menu.remove();
|
||||||
|
});
|
||||||
|
|
||||||
|
menu.querySelector('[data-action="reset-scale"]').addEventListener('click', async (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
await game.settings.set("cosmos-dh-tweaks", "fearTrackerScale", 1.0);
|
||||||
|
this.updateScale();
|
||||||
|
this.adjustSizeToMaxFear();
|
||||||
|
ui.notifications.info(game.i18n.localize("COSMOS_DH_TWEAKS.Settings.ResetFearScale.Success"));
|
||||||
|
menu.remove();
|
||||||
|
});
|
||||||
|
|
||||||
|
menu.querySelectorAll('.scale-btn').forEach(btn => {
|
||||||
|
btn.addEventListener('click', async (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
const newScale = parseFloat(btn.dataset.scale);
|
||||||
|
await game.settings.set("cosmos-dh-tweaks", "fearTrackerScale", newScale);
|
||||||
|
this.updateScale();
|
||||||
|
this.adjustSizeToMaxFear();
|
||||||
|
menu.remove();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
const closeMenu = (event) => {
|
const closeMenu = (event) => {
|
||||||
if (!menu.contains(event.target)) {
|
if (!menu.contains(event.target)) {
|
||||||
menu.remove();
|
menu.remove();
|
||||||
|
|
@ -628,12 +721,12 @@ Hooks.on("destroyToken", (token) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
Hooks.on("renderSettingsConfig", (app, html, data) => {
|
Hooks.on("renderSettingsConfig", (app, html, data) => {
|
||||||
const input = html.querySelector
|
const posInput = html.querySelector
|
||||||
? html.querySelector('[name="cosmos-dh-tweaks.resetFearPosition"]')
|
? html.querySelector('[name="cosmos-dh-tweaks.resetFearPosition"]')
|
||||||
: html.find('[name="cosmos-dh-tweaks.resetFearPosition"]')[0];
|
: html.find('[name="cosmos-dh-tweaks.resetFearPosition"]')[0];
|
||||||
|
|
||||||
if (input) {
|
if (posInput) {
|
||||||
const formGroup = input.closest('.form-group');
|
const formGroup = posInput.closest('.form-group');
|
||||||
if (formGroup) {
|
if (formGroup) {
|
||||||
const formFields = formGroup.querySelector('.form-fields');
|
const formFields = formGroup.querySelector('.form-fields');
|
||||||
if (formFields) {
|
if (formFields) {
|
||||||
|
|
@ -656,4 +749,33 @@ Hooks.on("renderSettingsConfig", (app, html, data) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const scaleInput = html.querySelector
|
||||||
|
? html.querySelector('[name="cosmos-dh-tweaks.resetFearScale"]')
|
||||||
|
: html.find('[name="cosmos-dh-tweaks.resetFearScale"]')[0];
|
||||||
|
|
||||||
|
if (scaleInput) {
|
||||||
|
const formGroup = scaleInput.closest('.form-group');
|
||||||
|
if (formGroup) {
|
||||||
|
const formFields = formGroup.querySelector('.form-fields');
|
||||||
|
if (formFields) {
|
||||||
|
const button = document.createElement('button');
|
||||||
|
button.type = 'button';
|
||||||
|
button.className = 'cosmo-reset-fear-scale-btn';
|
||||||
|
button.innerHTML = `<i class="fa-solid fa-arrows-rotate"></i> ${game.i18n.localize("COSMOS_DH_TWEAKS.Settings.ResetFearScale.ButtonText")}`;
|
||||||
|
button.addEventListener('click', async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
await game.settings.set("cosmos-dh-tweaks", "fearTrackerScale", 1.0);
|
||||||
|
if (ui.resources && ui.resources.rendered) {
|
||||||
|
ui.resources.updateScale();
|
||||||
|
ui.resources.adjustSizeToMaxFear();
|
||||||
|
}
|
||||||
|
ui.notifications.info(game.i18n.localize("COSMOS_DH_TWEAKS.Settings.ResetFearScale.Success"));
|
||||||
|
});
|
||||||
|
|
||||||
|
formFields.innerHTML = '';
|
||||||
|
formFields.appendChild(button);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -156,8 +156,8 @@
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Reset button styling */
|
button.cosmo-reset-fear-pos-btn,
|
||||||
button.cosmo-reset-fear-pos-btn {
|
button.cosmo-reset-fear-scale-btn {
|
||||||
background: rgba(197, 164, 94, 0.1);
|
background: rgba(197, 164, 94, 0.1);
|
||||||
border: 1px solid rgb(153, 122, 79);
|
border: 1px solid rgb(153, 122, 79);
|
||||||
color: #d3d3d3;
|
color: #d3d3d3;
|
||||||
|
|
@ -172,8 +172,123 @@ button.cosmo-reset-fear-pos-btn {
|
||||||
transition: background-color 0.2s, color 0.2s, border-color 0.2s;
|
transition: background-color 0.2s, color 0.2s, border-color 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
button.cosmo-reset-fear-pos-btn:hover {
|
button.cosmo-reset-fear-pos-btn:hover,
|
||||||
|
button.cosmo-reset-fear-scale-btn:hover {
|
||||||
background: rgba(197, 164, 94, 0.25);
|
background: rgba(197, 164, 94, 0.25);
|
||||||
border-color: #ffffff;
|
border-color: #ffffff;
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Fear Tracker Scaling */
|
||||||
|
#resources {
|
||||||
|
--cosmo-fear-scale: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Scale the skull icons */
|
||||||
|
#resources #resource-fear i {
|
||||||
|
width: calc(3rem * var(--cosmo-fear-scale, 1)) !important;
|
||||||
|
height: calc(3rem * var(--cosmo-fear-scale, 1)) !important;
|
||||||
|
font-size: calc(1.125rem * var(--cosmo-fear-scale, 1)) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Scale hover state font-size for GMs */
|
||||||
|
#resources.isGM #resource-fear i:hover {
|
||||||
|
font-size: calc(1.25rem * var(--cosmo-fear-scale, 1)) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Scale the control buttons (+ and -) */
|
||||||
|
#resources #resource-fear .controls {
|
||||||
|
width: calc(30px * var(--cosmo-fear-scale, 1)) !important;
|
||||||
|
height: calc(30px * var(--cosmo-fear-scale, 1)) !important;
|
||||||
|
font-size: calc(1.25rem * var(--cosmo-fear-scale, 1)) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#resources #resource-fear .controls:hover {
|
||||||
|
font-size: calc(1.5rem * var(--cosmo-fear-scale, 1)) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Scale the resource bar */
|
||||||
|
#resources #resource-fear .resource-bar {
|
||||||
|
font-size: calc(1.25rem * var(--cosmo-fear-scale, 1)) !important;
|
||||||
|
padding: calc(0.25rem * var(--cosmo-fear-scale, 1)) calc(0.5rem * var(--cosmo-fear-scale, 1)) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Scale the window padding and flex gap */
|
||||||
|
#resources .window-content {
|
||||||
|
padding: calc(0.5rem * var(--cosmo-fear-scale, 1)) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#resources #resource-fear {
|
||||||
|
gap: calc(0.5rem * var(--cosmo-fear-scale, 1)) calc(0.25rem * var(--cosmo-fear-scale, 1)) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Scale the window header */
|
||||||
|
#resources header.window-header {
|
||||||
|
font-size: calc(14px * var(--cosmo-fear-scale, 1)) !important;
|
||||||
|
min-height: calc(30px * var(--cosmo-fear-scale, 1)) !important;
|
||||||
|
height: calc(30px * var(--cosmo-fear-scale, 1)) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#resources header.window-header h1 {
|
||||||
|
font-size: calc(14px * var(--cosmo-fear-scale, 1)) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#resources header.window-header button,
|
||||||
|
#resources header.window-header a,
|
||||||
|
#resources header.window-header i {
|
||||||
|
font-size: calc(14px * var(--cosmo-fear-scale, 1)) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#resources header.window-header button,
|
||||||
|
#resources header.window-header a {
|
||||||
|
width: calc(30px * var(--cosmo-fear-scale, 1)) !important;
|
||||||
|
height: calc(30px * var(--cosmo-fear-scale, 1)) !important;
|
||||||
|
line-height: calc(30px * var(--cosmo-fear-scale, 1)) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Context menu scale controls styling */
|
||||||
|
.cosmo-context-menu-header {
|
||||||
|
padding: 6px 12px 2px 12px;
|
||||||
|
font-size: 11px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: #a49a85;
|
||||||
|
font-weight: bold;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
border-top: 1px solid rgba(153, 122, 79, 0.3);
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cosmo-context-menu-scale-controls {
|
||||||
|
display: flex;
|
||||||
|
padding: 4px 12px 8px 12px;
|
||||||
|
gap: 4px;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cosmo-context-menu-scale-controls button.scale-btn {
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
border: 1px solid rgba(153, 122, 79, 0.4);
|
||||||
|
color: #d3d3d3;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 3px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 11px;
|
||||||
|
font-family: inherit;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cosmo-context-menu-scale-controls button.scale-btn:hover {
|
||||||
|
background: rgba(197, 164, 94, 0.25);
|
||||||
|
border-color: #ffffff;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cosmo-context-menu-scale-controls button.scale-btn.active {
|
||||||
|
background: rgba(197, 164, 94, 0.4);
|
||||||
|
border-color: rgb(197, 164, 94);
|
||||||
|
color: #ffffff;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue