mirror of
https://github.com/Kornstalx/5etools-mirror-2.github.io.git
synced 2025-10-28 20:45:35 -05:00
379 lines
12 KiB
JavaScript
379 lines
12 KiB
JavaScript
"use strict";
|
|
|
|
class RenderDecks {
|
|
static SETTINGS = {
|
|
cardLayout: new SettingsUtil.EnumSetting({
|
|
type: "enum",
|
|
name: "Card Layout",
|
|
help: `The layout to use when displaying cards in a deck.`,
|
|
defaultVal: "list",
|
|
enumVals: [
|
|
{
|
|
value: "list",
|
|
name: "List",
|
|
},
|
|
{
|
|
value: "grid",
|
|
name: "Grid",
|
|
},
|
|
],
|
|
}),
|
|
};
|
|
|
|
static getCardTextHtml ({card, deck = null}) {
|
|
const ptText = Renderer.get()
|
|
.setFirstSection(true)
|
|
.setPartPageExpandCollapseDisabled(true)
|
|
.render({name: card.name, entries: Renderer.card.getFullEntries(card, {backCredit: deck?.back?.credit})}, 1);
|
|
Renderer.get().setPartPageExpandCollapseDisabled(false);
|
|
return ptText;
|
|
}
|
|
|
|
static getRenderedDeckMeta (
|
|
ent,
|
|
{
|
|
settingsManager,
|
|
|
|
cardStateManager,
|
|
} = {},
|
|
) {
|
|
const fnsCleanup = [];
|
|
|
|
const hashDeck = UrlUtil.autoEncodeHash(ent);
|
|
|
|
const $rowsCards = ent.cards
|
|
.map((card, ixCard) => {
|
|
const ptText = this.getCardTextHtml({card});
|
|
|
|
const $btnMarkDrawn = $(`<button class="btn btn-default btn-xs" title="Mark Card as Drawn"><i class="fas fa-fw fa-cards"></i></button>`)
|
|
.click(async evt => {
|
|
evt.stopPropagation();
|
|
await cardStateManager.pDrawCard(ent, card);
|
|
});
|
|
|
|
const $btnReplace = $(`<button class="btn btn-default btn-xs" title="Return Card to Deck"><i class="fas fa-arrow-rotate-left"></i></button>`)
|
|
.click(async evt => {
|
|
evt.stopPropagation();
|
|
await cardStateManager.pReplaceCard(ent, card);
|
|
});
|
|
|
|
const $btnViewer = $(`<button class="btn btn-default btn-xs" title="Open Card Viewer"><span class="glyphicon glyphicon-eye-open"></span></button>`)
|
|
.click(async evt => {
|
|
evt.stopPropagation();
|
|
try {
|
|
$btnViewer.prop("disabled", true);
|
|
await RenderDecks.pRenderStgCard({deck: ent, card});
|
|
} finally {
|
|
$btnViewer.prop("disabled", false);
|
|
}
|
|
});
|
|
|
|
const $wrpFace = $$`<div class="no-shrink px-1 decks__wrp-card-face relative">
|
|
<div class="absolute pt-2 pr-2 decks__wrp-btn-show-card">
|
|
<div class="btn-group ve-flex-v-center">
|
|
${$btnMarkDrawn}
|
|
${$btnReplace}
|
|
${$btnViewer}
|
|
</div>
|
|
</div>
|
|
${Renderer.get().setFirstSection(true).render({...card.face, title: card.name, altText: card.name})}
|
|
</div>`;
|
|
|
|
const $imgFace = $wrpFace.find("img");
|
|
const title = $imgFace.closest(`[title]`).title();
|
|
const propCardDrawn = cardStateManager.getPropCardDrawn({hashDeck, ixCard});
|
|
const hkCardDrawn = cardStateManager.addHookBase(propCardDrawn, () => {
|
|
const isDrawn = !!cardStateManager.get(propCardDrawn);
|
|
$btnMarkDrawn.prop("disabled", isDrawn);
|
|
$btnReplace.prop("disabled", !isDrawn);
|
|
$wrpFace.toggleClass("decks__wrp-card-face--drawn", isDrawn);
|
|
$imgFace.title(isDrawn ? `${title} (Drawn)` : title);
|
|
});
|
|
fnsCleanup.push(() => cardStateManager.removeHookBase(propCardDrawn, hkCardDrawn));
|
|
hkCardDrawn();
|
|
|
|
return $$`<div class="ve-flex-v-center decks__wrp-row">
|
|
${$wrpFace}
|
|
<div class="ml-2 decks__wrp-card-text w-100">${ptText}</div>
|
|
</div>`;
|
|
});
|
|
|
|
const $wrpCardRows = $$`<div class="decks__wrp-card-rows">
|
|
${$rowsCards}
|
|
</div>`;
|
|
|
|
const $ptCards = $$`<div class="ve-flex-col">
|
|
<h3 class="dnd-font my-0 mb-1 decks__h-cards">Cards</h3>
|
|
${$wrpCardRows}
|
|
</div>`;
|
|
|
|
const hkCardLayout = settingsManager.addHookBase("cardLayout", () => {
|
|
const mode = settingsManager.get("cardLayout");
|
|
$wrpCardRows.toggleClass(`decks__wrp-card-rows--list`, false);
|
|
$wrpCardRows.toggleClass(`decks__wrp-card-rows--grid`, false);
|
|
$wrpCardRows.toggleClass(`decks__wrp-card-rows--${mode}`);
|
|
});
|
|
fnsCleanup.push(() => settingsManager.removeHookBase("cardLayout", hkCardLayout));
|
|
hkCardLayout();
|
|
|
|
const $ele = $$`
|
|
${Renderer.utils.getBorderTr()}
|
|
${Renderer.utils.getExcludedTr({entity: ent, dataProp: "deck"})}
|
|
${Renderer.utils.getNameTr(ent, {page: UrlUtil.PG_DECKS})}
|
|
|
|
<tr class="text"><td colspan="6">
|
|
${Renderer.get().setFirstSection(true).render({entries: ent.entries}, 1)}
|
|
<hr class="hr-3">
|
|
${$ptCards}
|
|
</td></tr>
|
|
|
|
${Renderer.utils.getPageTr(ent)}
|
|
${Renderer.utils.getBorderTr()}`;
|
|
|
|
return {$ele, fnsCleanup};
|
|
}
|
|
|
|
/* -------------------------------------------- */
|
|
|
|
static async pRenderStgCard ({deck, card}) {
|
|
const imgUrlBack = (card.back || deck.back) ? Renderer.utils.getEntryMediaUrl(card.back || deck.back, "href", "img") : null;
|
|
const imgUrlCard = Renderer.utils.getEntryMediaUrl(card.face, "href", "img");
|
|
|
|
const imgBack = imgUrlBack ? await AnimationUtil.pLoadImage(imgUrlBack) : null;
|
|
if (imgBack) {
|
|
e_({
|
|
ele: imgBack,
|
|
clazz: "decks-draw__img-card-back absolute",
|
|
});
|
|
}
|
|
|
|
const imgCard = await AnimationUtil.pLoadImage(imgUrlCard);
|
|
e_({
|
|
ele: imgCard,
|
|
clazz: "decks-draw__img-card",
|
|
});
|
|
|
|
const dispGlint = e_({
|
|
tag: "div",
|
|
clazz: "decks-draw__disp-glint no-events no-select absolute",
|
|
});
|
|
|
|
const wrpCard = e_({
|
|
tag: "div",
|
|
clazz: "decks-draw__wrp-card relative",
|
|
children: [
|
|
imgBack,
|
|
imgCard,
|
|
dispGlint,
|
|
],
|
|
});
|
|
|
|
const wrpCardFlip = e_({
|
|
tag: "div",
|
|
clazz: "decks-draw__wrp-card-flip",
|
|
children: [
|
|
wrpCard,
|
|
],
|
|
});
|
|
|
|
const $wrpCardSway = $$`<div class="decks-draw__wrp-card-sway ve-flex-col no-select relative">${wrpCardFlip}</div>`
|
|
.click(evt => evt.stopPropagation());
|
|
|
|
const metasSparkles = await [...new Array(8)]
|
|
.pSerialAwaitMap(async (_, i) => {
|
|
const imgSparkle = i % 2
|
|
? await AnimationUtil.pLoadImage(Renderer.get().getMediaUrl("img", "decks/page/medium-2.webp"))
|
|
: await AnimationUtil.pLoadImage(Renderer.get().getMediaUrl("img", "decks/page/medium-1.webp"));
|
|
|
|
e_({
|
|
ele: imgSparkle,
|
|
clazz: "decks-draw__img-sparkle relative",
|
|
});
|
|
|
|
imgSparkle.style.animationDuration = `${4_500 + (Math.random() * 3_000)}ms, ${60_000 + (Math.random() * 60_000)}ms`;
|
|
imgSparkle.style.animationDelay = `-${i + 1}00ms, -${i + 1}00ms`;
|
|
|
|
const wrpSparkleSway = e_({
|
|
tag: "div",
|
|
clazz: "decks-draw__wrp-sparkle-sway ve-flex-col absolute",
|
|
children: [
|
|
imgSparkle,
|
|
],
|
|
});
|
|
|
|
wrpSparkleSway.style.top = `${-10 + (120 * Math.random())}%`;
|
|
wrpSparkleSway.style.left = `${-10 + (120 * Math.random())}%`;
|
|
|
|
wrpSparkleSway.style.width = `min(67%, ${imgSparkle.width}px)`;
|
|
wrpSparkleSway.style.height = `min(67%, ${imgSparkle.height}px)`;
|
|
|
|
wrpSparkleSway.style.animationDuration = `${10_000 + (Math.random() * 4_000)}ms`;
|
|
wrpSparkleSway.style.animationDelay = `-${3_000 + (Math.random() * 1_500)}ms`;
|
|
|
|
return {wrpSparkleSway, imgSparkle};
|
|
});
|
|
|
|
const $wrpCardOuter = $$`<div class="ve-flex-col no-select relative">
|
|
${metasSparkles.map(it => it.wrpSparkleSway)}
|
|
${$wrpCardSway}
|
|
</div>`;
|
|
|
|
const ptText = RenderDecks.getCardTextHtml({card, deck});
|
|
|
|
const $wrpInfo = $$`<div class="stats stats--book decks-draw__wrp-desc mobile__hidden px-2 ve-text-center mb-4">${ptText}</div>`
|
|
.click(evt => evt.stopPropagation());
|
|
|
|
Renderer.dice.bindOnclickListener($wrpInfo[0]);
|
|
|
|
const $btnFlip = imgBack
|
|
? $(`<button class="btn btn-default btn-xs px-3" title="Flip Card"><i class="fas fa-rotate"></i> Flip</button>`)
|
|
.click(evt => {
|
|
evt.stopPropagation();
|
|
wrpCardFlip.classList.toggle("decks-draw__wrp-card-flip--flipped");
|
|
})
|
|
: null;
|
|
|
|
const $wrpRhs = $$`<div class="decks-draw__wrp-rhs ve-flex-col mobile__ml-0">
|
|
${$wrpInfo}
|
|
<div class="ve-flex-vh-center mobile__mt-5">${$btnFlip}</div>
|
|
</div>`
|
|
.click(evt => evt.stopPropagation());
|
|
|
|
const onDeviceorientation = evt => {
|
|
// region Emulate mouse position by projecting orientation angle from a point `depth` behind the screen
|
|
const bcr = wrpCard.getBoundingClientRect();
|
|
|
|
const cCenterX = bcr.left + bcr.width / 2;
|
|
const cCenterY = bcr.top + bcr.height / 2;
|
|
|
|
const depth = (window.innerWidth + window.innerHeight) / 2;
|
|
|
|
const gammaRot = Math.max(Math.min(Math.abs(evt.gamma - 90), 135), 45);
|
|
const betaRot = Math.max(Math.min(Math.abs(evt.beta - 90), 135), 45);
|
|
|
|
const fromCenterX = depth / Math.tan(-gammaRot * Math.PI / 180);
|
|
const fromCenterY = depth / Math.tan(-betaRot * Math.PI / 180);
|
|
|
|
const mouseX = cCenterX + fromCenterX;
|
|
const mouseY = cCenterY + fromCenterY;
|
|
// endregion
|
|
|
|
this._pRenderStgCard_onMouseMove_mutElements({mouseX, mouseY, wrpCard, dispGlint});
|
|
};
|
|
|
|
const $wrpDrawn = $$`<div class="decks-draw__stg ve-flex-vh-center">
|
|
<div class="ve-flex-v-center mobile__ve-flex-col">
|
|
${$wrpCardOuter}
|
|
${$wrpRhs}
|
|
</div>
|
|
</div>`
|
|
.click(evt => {
|
|
evt.stopPropagation();
|
|
$wrpDrawn.remove();
|
|
window.removeEventListener("deviceorientation", onDeviceorientation);
|
|
})
|
|
.mousemove(evt => {
|
|
const mouseX = EventUtil.getClientX(evt);
|
|
const mouseY = EventUtil.getClientY(evt);
|
|
|
|
requestAnimationFrame(() => {
|
|
this._pRenderStgCard_onMouseMove_mutElements({mouseX, mouseY, wrpCard, dispGlint});
|
|
});
|
|
});
|
|
|
|
window.addEventListener("deviceorientation", onDeviceorientation);
|
|
|
|
const {x: mouseX, y: mouseY} = EventUtil.getMousePos();
|
|
this._pRenderStgCard_onMouseMove_mutElements({mouseX, mouseY, wrpCard, dispGlint});
|
|
|
|
$wrpDrawn.appendTo(document.body);
|
|
|
|
await AnimationUtil.pRecomputeStyles();
|
|
|
|
$wrpDrawn.addClass("decks-draw__stg--visible");
|
|
wrpCard.classList.add("decks-draw__wrp-card--visible");
|
|
$wrpRhs.addClass("decks-draw__wrp-rhs--visible");
|
|
metasSparkles.forEach(it => it.imgSparkle.classList.add("decks-draw__img-sparkle--visible"));
|
|
}
|
|
|
|
static _pRenderStgCard_onMouseMove_mutElements ({mouseX, mouseY, wrpCard, dispGlint}) {
|
|
const perStyles = this._pRenderStgCard_getPerspectiveStyles({mouseX, mouseY, ele: wrpCard});
|
|
wrpCard.style.transform = perStyles.cardTransform;
|
|
dispGlint.style.background = perStyles.glintBackground;
|
|
}
|
|
|
|
static _pRenderStgCard_getPerspectiveStyles ({mouseX, mouseY, ele}) {
|
|
const bcr = ele.getBoundingClientRect();
|
|
const hView = window.innerHeight;
|
|
|
|
const cCenterX = bcr.left + bcr.width / 2;
|
|
const cCenterY = bcr.top + bcr.height / 2;
|
|
|
|
const cMouseX = mouseX - cCenterX;
|
|
const cMouseY = (hView - mouseY) - (hView - cCenterY);
|
|
|
|
const scaleFactor = hView * 2;
|
|
|
|
const rotX = cMouseY / scaleFactor;
|
|
const rotY = cMouseX / scaleFactor;
|
|
|
|
return {
|
|
...this._pRenderStgCard_getPerspectiveStyles_card({mouseX, mouseY, bcr, hView, rotX, rotY}),
|
|
...this._pRenderStgCard_getPerspectiveStyles_glint({mouseX, mouseY, bcr, hView, rotX, rotY}),
|
|
};
|
|
}
|
|
|
|
static _pRenderStgCard_getPerspectiveStyles_card ({rotX, rotY}) {
|
|
return {
|
|
cardTransform: `perspective(100vh) rotateX(${rotX}rad) rotateY(${rotY}rad)`,
|
|
};
|
|
}
|
|
|
|
static _pRenderStgCard_getPerspectiveStyles_glint ({mouseX, mouseY, bcr, hView, rotX, rotY}) {
|
|
const cCenterX = bcr.left + bcr.width / 2;
|
|
const cCenterY = bcr.top + bcr.height / 2;
|
|
|
|
const cMouseX = mouseX - cCenterX;
|
|
const cMouseY = (hView - mouseY) - (hView - cCenterY);
|
|
|
|
const glintDist = Math.sqrt(Math.pow(cMouseX, 2) + Math.pow(cMouseY, 2));
|
|
const glintDistRatio = glintDist / hView;
|
|
|
|
const pctLeft = ((mouseX - bcr.left) / bcr.width) * 100;
|
|
const pctTop = ((mouseY - bcr.top) / bcr.height) * 100;
|
|
|
|
const pctLeftClamped = Math.max(0, Math.min(100, pctLeft));
|
|
const pctTopClamped = Math.max(0, Math.min(100, pctTop));
|
|
|
|
const glintOpacityFalloff = glintDistRatio * 0.33;
|
|
|
|
const gradSpot = `radial-gradient(
|
|
circle at left ${pctLeftClamped}% top ${pctTopClamped}%,
|
|
rgba(255, 255, 255, 0.73) 0%,
|
|
rgba(255, 255, 255, ${1.0 - glintOpacityFalloff}) 1%,
|
|
rgba(255, 255, 255, ${1.0 - glintOpacityFalloff}) ${1 + (glintDistRatio * 2)}%,
|
|
rgba(255, 255, 255, 0.53) ${2 + (glintDistRatio * 2)}%,
|
|
transparent ${5 + (glintDistRatio * 13)}%
|
|
)`;
|
|
|
|
const gradSpotInv = `radial-gradient(
|
|
circle at left ${100 - pctLeftClamped}% top ${100 - pctTopClamped}%,
|
|
#fff2 0%,
|
|
#fff2 ${10 + (glintDistRatio * 2)}%,
|
|
transparent ${20 + (glintDistRatio * 5)}%
|
|
)`;
|
|
|
|
const gradEdge = `linear-gradient(
|
|
${-rotX + rotY}rad,
|
|
var(--rgb-card-glint--edge) 0%,
|
|
transparent 4%,
|
|
transparent 96%,
|
|
var(--rgb-card-glint--edge) 100%
|
|
)`;
|
|
|
|
return {
|
|
glintBackground: `${gradSpot}, ${gradSpotInv}, ${gradEdge}`,
|
|
};
|
|
}
|
|
}
|