diff --git a/src/components/widgets/ubg-card.js b/src/components/widgets/ubg-card.js index 615888430..89390b2ed 100644 --- a/src/components/widgets/ubg-card.js +++ b/src/components/widgets/ubg-card.js @@ -2,11 +2,6 @@ import Morph from 'src/components/widgets/lively-morph.js'; -import { Point } from 'src/client/graphics.js' - -import paper from 'src/client/paperjs-wrapper.js' -// import 'https://lively-kernel.org/lively4/ubg-assets/load-assets.js'; - import preloaWebComponents from 'src/client/preload-components.js' await preloaWebComponents(['ubg-rules-text']); @@ -49,477 +44,7 @@ function mmToPoint() { return this * 2.835; } -const fire = ; -const water = ; -const earth = ; -const wind = ; -const gray = ; -const question = ; - -class PathDataScaleCache { - static getPathData(element, size = lively.pt(10, 10)) { - if (!this.cache) { - this.cache = {} - } - - const key = `${element}-${size.x}-${size.y}`; - if (!this.cache[key]) { - // lively.notify(`${element}-${size.x}-${size.y}`, 'cache miss') - this.cache[key] = this._scalePathData(element, size) - } - - return this.cache[key] - } - - static _scalePathData(element, size) { - const { glyph } = forElement(element); - const path = new paper.Path(glyph.getAttribute('d')); - - path.scale(1, -1); - - const margin = size.scaleBy(0.1); - const boundingRect = new paper.Path.Rectangle({ - point: margin.toPair(), - size: size.subPt(margin.scaleBy(2)).toPair() - }); - path.fitBounds(boundingRect.bounds); - - return path.pathData; - } -} - -function tenTenPathData(element) { - return PathDataScaleCache.getPathData(element, lively.pt(10, 10)); -} - -const elementInfo = { - fire: { - name: 'fire', - faIcon: 'book', - glyph: fire, - get pathData() { return tenTenPathData('fire') }, - pathWidth: parseInt(fire.getAttribute('horiz-adv-x')), - pathHeight: parseInt(fire.getAttribute('vert-adv-y')), - fill: '#ffbbbb', - stroke: '#ff0000', - others: ['water', 'earth', 'wind'] - }, - water: { - name: 'water', - faIcon: 'droplet', - glyph: water, - get pathData() { return tenTenPathData('water') }, - pathWidth: parseInt(water.getAttribute('horiz-adv-x')), - pathHeight: parseInt(water.getAttribute('vert-adv-y')), - fill: '#8888ff', - stroke: '#0000ff', - others: ['fire', 'earth', 'wind'] - }, - earth: { - name: 'earth', - faIcon: 'mountain', - glyph: earth, - get pathData() { return tenTenPathData('earth') }, - pathWidth: parseInt(earth.getAttribute('horiz-adv-x')), - pathHeight: parseInt(earth.getAttribute('vert-adv-y')), - fill: 'rgb(255, 255, 183)', - stroke: '#ffd400', - others: ['fire', 'water', 'wind'] - }, - wind: { - name: 'wind', - faIcon: 'cloud', - glyph: wind, - get pathData() { return tenTenPathData('wind') }, - pathWidth: parseInt(wind.getAttribute('horiz-adv-x')), - pathHeight: parseInt(wind.getAttribute('vert-adv-y')), - fill: '#bbffbb', - stroke: '#00ff00', - others: ['fire', 'water', 'earth'] - }, - gray: { - name: 'gray', - faIcon: 'circle', - glyph: gray, - get pathData() { return tenTenPathData('gray') }, - pathWidth: parseInt(gray.getAttribute('horiz-adv-x')), - pathHeight: parseInt(gray.getAttribute('vert-adv-y')), - fill: '#dddddd', - stroke: '#5A5A5A', - others: ['gray', 'gray', 'gray'] - }, - unknown: { - name: 'unknown', - faIcon: 'question', - glyph: question, - get pathData() { return tenTenPathData('question') }, - pathWidth: parseInt(question.getAttribute('horiz-adv-x')), - pathHeight: parseInt(question.getAttribute('vert-adv-y')), - fill: 'pink', - stroke: 'violet', - others: ['question', 'question', 'question'] - } -}; - -function forElement(element) { - const cleanElement = (element || '').toLowerCase(); - return elementInfo[cleanElement] || elementInfo.unknown; -} - -class SVG { - - static outerSVG(children, innerBounds, outerBounds, attrs = '', style = '') { - return `${children}`; - } - - static inlineSVG(children, bounds = lively.rect(0, 0, 10, 10), attrs = '', style = '') { - return `${children}`; - } - - /*MD ## Basic Shapes MD*/ - static circleRing(center, innerRadius, outerRadius, attrs) { - return `` - } - - static circle(center, radius, attrs) { - return `` - } - - /*MD ## Icons MD*/ - static elementGlyph(element, center, radius, attrs) { - const pathData = PathDataScaleCache.getPathData(element, lively.pt(2 * radius, 2 * radius)); - return `` - } - - static elementSymbol(element, center, radius) { - const { name: elementName, fill, stroke } = forElement(element); - const innerRadius = .9 * radius; - return `${SVG.circle(center, innerRadius, `fill="${fill}"`)} -${SVG.elementGlyph(elementName, center, innerRadius, `fill="${stroke}"`)} - ${SVG.circleRing(center, innerRadius, radius, `fill="${stroke}"`)}` - } -} - -const castIcon = do { - const size = 100; - const bounds = lively.rect(0, 0, size, size) - const innerBounds = bounds.insetBy(5); - - const innerRadius = innerBounds.width / 2; - const outerCircle = SVG.circleRing(bounds.center(), innerRadius, bounds.width / 2, `fill="#7A7A7A"`); - - const sqrt2 = 2**.5 - const radius = innerRadius * 1 / (sqrt2 + 1); - const distToMiddle = innerRadius * sqrt2 / (sqrt2 + 1); - const elements = ['water', 'earth', 'fire', 'wind']; - const mainElements = elements.map((element, i) => { - const center = bounds.center().addPt(Point.polar(distToMiddle, Math.PI / 2 * i)); - return SVG.elementSymbol(element, center, radius) - }).join('\n'); - - SVG.inlineSVG(`${outerCircle} -${mainElements}`, bounds); -} - - -function previewSVG(svg) { - const hedronTemp = document.getElementById(svg.id) - if (hedronTemp) { - hedronTemp.remove() - } - document.body.insertAdjacentHTML("afterbegin", svg.outerHTML) -} - -const hedronSVG = do { - function point(pt) { - return `${pt.x} ${pt.y}`; - } - - const topB = lively.pt(11.5, 14.401); - const topL = topB.addXY(-11.5, -4.758); - const topT = topL.addXY(11.5, -9.66); - const topR = topT.addXY(11.5, 9.66); - const topB2 = topR.addXY(-11.5, 4.758); - const topLeftData = `M${point(topB)} L ${point(topL)} ${point(topT)} z`; - const topRightData = `M${point(topB)} L ${point(topT)} ${point(topR)} z`; - - const bottomB = lively.pt(11.5, 16.036); - const bottomL = bottomB.addXY(-11.5, -5.050); - const bottomT = bottomL.addXY(11.5, 12.030); - const bottomR = bottomT.addXY(11.5, -12.030); - const bottomB2 = bottomR.addXY(-11.5, 5.050); - const bottomLeftData = `M${point(bottomB)} L ${point(bottomL)} ${point(bottomT)} z`; - const bottomRightData = `M${point(bottomB)} L ${point(bottomT)} ${point(bottomR)} ${point(bottomB2)} z`; - - const greenHedron = true; - - - - - - ; -}; -// previewSVG(hedronSVG) - -const upgradeSVG = do { - const svg = ( - - - - - - - - -); -svg -}; -// previewSVG(upgradeSVG) - - -function rectToViewBox(rect) { - return `${rect.x} ${rect.y} ${rect.width} ${rect.height}` -} - -const TAP_VIEWBOX = lively.rect(2 ,20 ,103 ,103); -const tapSVG = do { - function toPair(pt) { - return `${pt.x} ${pt.y}` - } - - const size = 18 - const tip = lively.pt(83.5, 65) - const anchor = tip.subY(size); - const tail = anchor.subXY(20, 25) - const tipLeft = tip.subXY(size, size) - const tipRight = tip.addXY(size, -size) - const anchorLeft = tipLeft.addX(12.5) - const anchorRight = tipRight.subX(12.5) - const controlLeft = anchorLeft.subY(18) - const controlRight = anchorRight.subY(18) - const controlTail = tail.addX(5) - const path = -; - const C_BACKCARD_FILL = "transparent"; - const C_BACKCARD_STROKE = "black"; - const C_FRONTCARD_FILL = "black"; - const C_FRONTCARD_STROKE = "black"; - - const svg = ( - - - {path} - ); -svg -}; -// previewSVG(tapSVG) - - - - - -const tradeSVG = do { - const path1 = "M19.335 11.943c1.463 0.801 2.775 2.074 4.369 4.148 0.005-0.056 0.010-0.113 0.016-0.171 0.309-3.338 0.912-9.84-9.249-13.17 0.113 0.146 0.508 0.575 0.958 1.064 0.75 0.815 1.651 1.795 1.651 1.901-0.903-0.529-5.419-1.906-9.333 0.847s-5.189 6.67-4.616 11.329c0.455 3.7 3.289 6.799 6.95 8.289-2.584-1.464-4.341-4.342-4.341-7.654 0-4.795 3.684-8.682 8.229-8.682 2.050 0 3.925 0.791 5.366 2.099z"; - const path2 = "M12.665 20.057c-1.463-0.801-2.775-2.074-4.369-4.148-0.005 0.056-0.010 0.113-0.016 0.171-0.309 3.338-0.912 9.839 9.249 13.17-0.113-0.145-0.508-0.575-0.958-1.064-0.75-0.815-1.651-1.795-1.651-1.901 0.903 0.529 5.419 1.906 9.333-0.847s5.189-6.67 4.616-11.329c-0.454-3.7-3.289-6.799-6.95-8.289 2.584 1.464 4.341 4.342 4.341 7.654 0 4.795-3.684 8.682-8.229 8.682-2.050 0-3.925-0.791-5.366-2.099z"; - const svg = ( - - - - - - - - - - -); -svg -}; -// previewSVG(tradeSVG) - - -const CARD_COST_ONE_VIEWBOX = lively.rect(0, 0, 270, 270); -const cardCostOneSVG = do { - '#d3d3d3' - const C_OUTER = '#252525' - const C_INNER = '#d1d1d1' - const C_TOP = '#e1e5e4' - const C_IMAGE = '#f4f4f4' - - const outer = lively.rect(0, 0, 190, 270) - const inner = outer.insetBy(15) - const top = inner.insetBy(10) - const image = top.insetByRect(lively.rect(0, 30, 0, 45)) - const svg = ( - - - - - - - - -); -svg -}; -// previewSVG(cardCostOneSVG) - - -const CARD_COST_VIEWBOX = lively.rect(0, 0, 376, 326); -const cardCostTwoSVG = do { - const C_OUTER = 'rgb(243, 243, 243)' - const C_INNER = 'rgb(129, 129, 129)' - const C_TOP = 'rgb(162, 165, 168)' - const C_IMAGE = 'rgb(148, 147, 152)' - const C_BOTTOM = C_TOP; - - const svg = ( - - - - - - - -); -svg -}; - +import { fire, water, earth, wind, gray, question, PathDataScaleCache, tenTenPathData, elementInfo, forElement, SVG } from './ubg-utils.js'; class FileCache { diff --git a/src/components/widgets/ubg-card.js.l4a b/src/components/widgets/ubg-card.js.l4a deleted file mode 100644 index bbba889f4..000000000 --- a/src/components/widgets/ubg-card.js.l4a +++ /dev/null @@ -1,2 +0,0 @@ -{"type":"Reference","version":"f8c2c62649498ac6ba1526d53619de4419e3d73c","content":"/* global globalThis */\n\nimport Morph from 'src/components/widgets/lively-morph.js';\n\nimport { Point } from 'src/client/graphics.js'\n\nimport paper from 'src/client/paperjs-wrapper.js'\n// import 'https://lively-kernel.org/lively4/ubg-assets/load-assets.js';\n\nimport preloaWebComponents from 'src/client/preload-components.js'\nawait preloaWebComponents(['ubg-rules-text']);\n\nimport qrcodegen from 'https://lively-kernel.org/lively4/aexpr/src/external/qrcodegen.js'\n\nconst POKER_CARD_SIZE_INCHES = lively.pt(2.5, 3.5);\nconst POKER_CARD_SIZE_MM = POKER_CARD_SIZE_INCHES.scaleBy(25.4);\n\nconst CSS_FONT_FAMILY_BEAUFORT_FOR_LOL_BOLD = \"Beaufort for LOL Bold\"\nconst CSS_FONT_FAMILY_BEAUFORT_FOR_LOL_REGULAR = \"Beaufort for LOL Regular\"\nconst CSS_FONT_FAMILY_UNIVERS_55 = \"Univers 55\"\nconst CSS_FONT_FAMILY_UNIVERS_45_LIGHT_ITALIC = \"Univers 45 Light Italic\"\n\n// Card name, card cost, card stats -- Beaufort for LOL Bold\nconst CSS_FONT_FAMILY_CARD_NAME = CSS_FONT_FAMILY_BEAUFORT_FOR_LOL_BOLD\nconst CSS_FONT_FAMILY_CARD_COST = CSS_FONT_FAMILY_BEAUFORT_FOR_LOL_BOLD\nconst CSS_FONT_FAMILY_CARD_VP = CSS_FONT_FAMILY_BEAUFORT_FOR_LOL_BOLD\nconst CSS_FONT_FAMILY_CARD_TYPE = CSS_FONT_FAMILY_BEAUFORT_FOR_LOL_REGULAR\n// #TODO: Card group name (ELITE, SPIDER, YETI, etc.) -- Univers 59 // #BROKEN?? #TODO\n\n// Card description -- Univers 55\nconst CSS_FONT_FAMILY_CARD_TEXT = CSS_FONT_FAMILY_UNIVERS_55\n\nfunction identity(value) {\n return value;\n}\n\n/* `this` is a lively.rect */\nfunction xYWidthHeight() {\n return [this.x, this.y, this.width, this.height];\n}\n\n/* `this` is a Number */\nfunction pointToMM() {\n return this / 2.835;\n}\n\n/* `this` is a Number */\nfunction mmToPoint() {\n return this * 2.835;\n}\n\nconst fire = ;\nconst water = ;\nconst earth = ;\nconst wind = ;\nconst gray = ;\nconst question = ;\n\nclass PathDataScaleCache {\n static getPathData(element, size = lively.pt(10, 10)) {\n if (!this.cache) {\n this.cache = {}\n }\n \n const key = `${element}-${size.x}-${size.y}`;\n if (!this.cache[key]) {\n // lively.notify(`${element}-${size.x}-${size.y}`, 'cache miss')\n this.cache[key] = this._scalePathData(element, size)\n }\n \n return this.cache[key]\n }\n \n static _scalePathData(element, size) {\n const { glyph } = forElement(element);\n const path = new paper.Path(glyph.getAttribute('d'));\n\n path.scale(1, -1);\n\n const margin = size.scaleBy(0.1);\n const boundingRect = new paper.Path.Rectangle({\n point: margin.toPair(),\n size: size.subPt(margin.scaleBy(2)).toPair()\n });\n path.fitBounds(boundingRect.bounds);\n\n return path.pathData;\n }\n}\n\nfunction tenTenPathData(element) {\n return PathDataScaleCache.getPathData(element, lively.pt(10, 10));\n}\n\nconst elementInfo = {\n fire: {\n name: 'fire',\n faIcon: 'book',\n glyph: fire,\n get pathData() { return tenTenPathData('fire') },\n pathWidth: parseInt(fire.getAttribute('horiz-adv-x')),\n pathHeight: parseInt(fire.getAttribute('vert-adv-y')),\n fill: '#ffbbbb',\n stroke: '#ff0000',\n others: ['water', 'earth', 'wind']\n },\n water: {\n name: 'water',\n faIcon: 'droplet',\n glyph: water,\n get pathData() { return tenTenPathData('water') },\n pathWidth: parseInt(water.getAttribute('horiz-adv-x')),\n pathHeight: parseInt(water.getAttribute('vert-adv-y')),\n fill: '#8888ff',\n stroke: '#0000ff',\n others: ['fire', 'earth', 'wind']\n },\n earth: {\n name: 'earth',\n faIcon: 'mountain',\n glyph: earth,\n get pathData() { return tenTenPathData('earth') },\n pathWidth: parseInt(earth.getAttribute('horiz-adv-x')),\n pathHeight: parseInt(earth.getAttribute('vert-adv-y')),\n fill: 'rgb(255, 255, 183)',\n stroke: '#ffd400',\n others: ['fire', 'water', 'wind']\n },\n wind: {\n name: 'wind',\n faIcon: 'cloud',\n glyph: wind,\n get pathData() { return tenTenPathData('wind') },\n pathWidth: parseInt(wind.getAttribute('horiz-adv-x')),\n pathHeight: parseInt(wind.getAttribute('vert-adv-y')),\n fill: '#bbffbb',\n stroke: '#00ff00',\n others: ['fire', 'water', 'earth']\n },\n gray: {\n name: 'gray',\n faIcon: 'circle',\n glyph: gray,\n get pathData() { return tenTenPathData('gray') },\n pathWidth: parseInt(gray.getAttribute('horiz-adv-x')),\n pathHeight: parseInt(gray.getAttribute('vert-adv-y')),\n fill: '#dddddd',\n stroke: '#5A5A5A',\n others: ['gray', 'gray', 'gray']\n },\n unknown: {\n name: 'unknown',\n faIcon: 'question',\n glyph: question,\n get pathData() { return tenTenPathData('question') },\n pathWidth: parseInt(question.getAttribute('horiz-adv-x')),\n pathHeight: parseInt(question.getAttribute('vert-adv-y')),\n fill: 'pink',\n stroke: 'violet',\n others: ['question', 'question', 'question']\n }\n};\n\nfunction forElement(element) {\n const cleanElement = (element || '').toLowerCase();\n return elementInfo[cleanElement] || elementInfo.unknown;\n}\n\nclass SVG {\n\n static outerSVG(children, innerBounds, outerBounds, attrs = '', style = '') {\n return `${children}`;\n }\n\n static inlineSVG(children, bounds = lively.rect(0, 0, 10, 10), attrs = '', style = '') {\n return `${children}`;\n }\n\n /*MD ## Basic Shapes MD*/\n static circleRing(center, innerRadius, outerRadius, attrs) {\n return ``\n }\n\n static circle(center, radius, attrs) {\n return ``\n }\n\n /*MD ## Icons MD*/\n static elementGlyph(element, center, radius, attrs) {\n const pathData = PathDataScaleCache.getPathData(element, lively.pt(2 * radius, 2 * radius));\n return ``\n }\n \n static elementSymbol(element, center, radius) {\n const { name: elementName, fill, stroke } = forElement(element);\n const innerRadius = .9 * radius;\n return `${SVG.circle(center, innerRadius, `fill=\"${fill}\"`)}\n${SVG.elementGlyph(elementName, center, innerRadius, `fill=\"${stroke}\"`)}\n ${SVG.circleRing(center, innerRadius, radius, `fill=\"${stroke}\"`)}`\n }\n}\n\nconst castIcon = do {\n const size = 100;\n const bounds = lively.rect(0, 0, size, size)\n const innerBounds = bounds.insetBy(5);\n \n const innerRadius = innerBounds.width / 2;\n const outerCircle = SVG.circleRing(bounds.center(), innerRadius, bounds.width / 2, `fill=\"#7A7A7A\"`);\n \n const sqrt2 = 2**.5\n const radius = innerRadius * 1 / (sqrt2 + 1);\n const distToMiddle = innerRadius * sqrt2 / (sqrt2 + 1);\n const elements = ['water', 'earth', 'fire', 'wind'];\n const mainElements = elements.map((element, i) => {\n const center = bounds.center().addPt(Point.polar(distToMiddle, Math.PI / 2 * i));\n return SVG.elementSymbol(element, center, radius)\n }).join('\\n');\n\n SVG.inlineSVG(`${outerCircle}\n${mainElements}`, bounds);\n}\n\n\nfunction previewSVG(svg) {\n const hedronTemp = document.getElementById(svg.id)\n if (hedronTemp) {\n hedronTemp.remove()\n }\n document.body.insertAdjacentHTML(\"afterbegin\", svg.outerHTML)\n}\n\nconst hedronSVG = do {\n function point(pt) {\n return `${pt.x} ${pt.y}`;\n }\n\n const topB = lively.pt(11.5, 14.401);\n const topL = topB.addXY(-11.5, -4.758);\n const topT = topL.addXY(11.5, -9.66);\n const topR = topT.addXY(11.5, 9.66);\n const topB2 = topR.addXY(-11.5, 4.758);\n const topLeftData = `M${point(topB)} L ${point(topL)} ${point(topT)} z`;\n const topRightData = `M${point(topB)} L ${point(topT)} ${point(topR)} z`;\n\n const bottomB = lively.pt(11.5, 16.036);\n const bottomL = bottomB.addXY(-11.5, -5.050);\n const bottomT = bottomL.addXY(11.5, 12.030);\n const bottomR = bottomT.addXY(11.5, -12.030);\n const bottomB2 = bottomR.addXY(-11.5, 5.050);\n const bottomLeftData = `M${point(bottomB)} L ${point(bottomL)} ${point(bottomT)} z`;\n const bottomRightData = `M${point(bottomB)} L ${point(bottomT)} ${point(bottomR)} ${point(bottomB2)} z`;\n \n const greenHedron = true;\n \n \n \n \n \n ;\n};\n// previewSVG(hedronSVG)\n\nconst upgradeSVG = do {\n const svg = (\n\n\n\n\n\n\n\n\n);\nsvg\n};\n// previewSVG(upgradeSVG)\n\n\nfunction rectToViewBox(rect) {\n return `${rect.x} ${rect.y} ${rect.width} ${rect.height}`\n}\n\nconst TAP_VIEWBOX = lively.rect(2 ,20 ,103 ,103);\nconst tapSVG = do {\n function toPair(pt) {\n return `${pt.x} ${pt.y}`\n }\n\n const size = 18\n const tip = lively.pt(83.5, 65)\n const anchor = tip.subY(size);\n const tail = anchor.subXY(20, 25)\n const tipLeft = tip.subXY(size, size)\n const tipRight = tip.addXY(size, -size)\n const anchorLeft = tipLeft.addX(12.5)\n const anchorRight = tipRight.subX(12.5)\n const controlLeft = anchorLeft.subY(18)\n const controlRight = anchorRight.subY(18)\n const controlTail = tail.addX(5)\n const path = \n;\n const C_BACKCARD_FILL = \"transparent\";\n const C_BACKCARD_STROKE = \"black\";\n const C_FRONTCARD_FILL = \"black\";\n const C_FRONTCARD_STROKE = \"black\";\n\n const svg = (\n \n \n {path}\n );\nsvg\n}; \n// previewSVG(tapSVG)\n\n\n\n\n\nconst tradeSVG = do {\n const path1 = \"M19.335 11.943c1.463 0.801 2.775 2.074 4.369 4.148 0.005-0.056 0.010-0.113 0.016-0.171 0.309-3.338 0.912-9.84-9.249-13.17 0.113 0.146 0.508 0.575 0.958 1.064 0.75 0.815 1.651 1.795 1.651 1.901-0.903-0.529-5.419-1.906-9.333 0.847s-5.189 6.67-4.616 11.329c0.455 3.7 3.289 6.799 6.95 8.289-2.584-1.464-4.341-4.342-4.341-7.654 0-4.795 3.684-8.682 8.229-8.682 2.050 0 3.925 0.791 5.366 2.099z\";\n const path2 = \"M12.665 20.057c-1.463-0.801-2.775-2.074-4.369-4.148-0.005 0.056-0.010 0.113-0.016 0.171-0.309 3.338-0.912 9.839 9.249 13.17-0.113-0.145-0.508-0.575-0.958-1.064-0.75-0.815-1.651-1.795-1.651-1.901 0.903 0.529 5.419 1.906 9.333-0.847s5.189-6.67 4.616-11.329c-0.454-3.7-3.289-6.799-6.95-8.289 2.584 1.464 4.341 4.342 4.341 7.654 0 4.795-3.684 8.682-8.229 8.682-2.050 0-3.925-0.791-5.366-2.099z\";\n const svg = (\n \n \n \n \n \n \n \n \n \n \n);\nsvg\n};\n// previewSVG(tradeSVG)\n\n\nconst CARD_COST_ONE_VIEWBOX = lively.rect(0, 0, 270, 270);\nconst cardCostOneSVG = do {\n '#d3d3d3'\n const C_OUTER = '#252525'\n const C_INNER = '#d1d1d1'\n const C_TOP = '#e1e5e4'\n const C_IMAGE = '#f4f4f4'\n \n const outer = lively.rect(0, 0, 190, 270)\n const inner = outer.insetBy(15)\n const top = inner.insetBy(10)\n const image = top.insetByRect(lively.rect(0, 30, 0, 45))\n const svg = (\n \n \n \n \n \n \n\n\n);\nsvg\n};\n// previewSVG(cardCostOneSVG)\n\n\nconst CARD_COST_VIEWBOX = lively.rect(0, 0, 376, 326);\nconst cardCostTwoSVG = do {\n const C_OUTER = 'rgb(243, 243, 243)'\n const C_INNER = 'rgb(129, 129, 129)'\n const C_TOP = 'rgb(162, 165, 168)'\n const C_IMAGE = 'rgb(148, 147, 152)'\n const C_BOTTOM = C_TOP;\n \n const svg = (\n \n \n \n \n \n \n\n);\nsvg\n};\n\n\nclass FileCache {\n\n constructor() {\n this.files = {};\n }\n\n dirtyFolder(path) {}\n\n getFile(path, callback) {\n if (this.files[path]) {\n // lively.notify('cache hit')\n } else {\n // lively.notify('cache miss')\n this.files[path] = callback(path);\n }\n\n return this.files[path];\n }\n\n}\n\nif (globalThis.__ubg_file_cache__) {\n globalThis.__ubg_file_cache__.migrateTo(FileCache);\n} else {\n globalThis.__ubg_file_cache__ = new FileCache();\n}\n\nconst VP_FILL = 'violet';\nconst VP_STROKE = '#9400d3'; // darkviolet\nconst VP_FILL_ZERO = '#ddd';\nconst VP_STROKE_ZERO = 'gray';\nconst AFFECT_ALL_COLOR = 'rgba(255, 0, 0, 0.2)';\n\nconst OUTSIDE_BORDER_ROUNDING = lively.pt(3, 3)\n\nexport default class UbgCard extends Morph {\n /*MD ## Filter MD*/\n get assetsFolder() {\n return this.src.replace(/(.*)\\/.*$/i, '$1/assets/');\n }\n\n /*MD ## Build MD*/\n async fetchAssetsInfo() {\n return (await this.assetsFolder.fetchStats()).contents;\n }\n\n /*MD ## Extract Card Info MD*/\n colorsForCard(card) {\n const BOX_FILL_OPACITY = 0.7;\n\n const currentVersion = card.versions.last;\n \n if (card.getType() === 'character') {\n return ['#efc241', '#b8942d', BOX_FILL_OPACITY];\n }\n\n const multiElement = Array.isArray(card.getElement());\n if (multiElement) {\n return ['#ff88ff', '#ff00ff', BOX_FILL_OPACITY];\n }\n\n const singleElementColors = {\n fire: ['#ffaaaa', '#dd0000', BOX_FILL_OPACITY],\n water: ['#aaaaff', '#0000ff', BOX_FILL_OPACITY],\n earth: ['#eeee88', '#cccc00', BOX_FILL_OPACITY],\n wind: ['#88ff88', '#00bb00', BOX_FILL_OPACITY]\n }[currentVersion.element && currentVersion.element.toLowerCase && currentVersion.element.toLowerCase()];\n if (singleElementColors) {\n return singleElementColors;\n }\n\n return ['#ffffff', '#888888', BOX_FILL_OPACITY];\n }\n\n getNameFromCard(cardDesc) {\n const currentVersion = cardDesc.versions.last;\n return currentVersion.name || ''\n }\n \n getElementsFromCard(cardDesc, grayIfEmpty) {\n const element = cardDesc.getElement();\n if (Array.isArray(element)) {\n return element\n } else if (element) {\n return [element]\n } else {\n return grayIfEmpty ? ['gray'] : []\n }\n }\n\n /*MD ## Debugging MD*/\n debugPoint(pt, color = 'red') {\n this.content.append(
);\n }\n \n debugRect(rect, color = 'red') {\n return this.roundedRect(rect, 'transparent', color, 1 / 3.7795275591, 0);\n }\n \n /*MD ## Rendering Helpers MD*/\n line(start, end, color, width) {\n const startX = start.x;\n const startY = start.y;\n const endX = end.x;\n const endY = end.y;\n\n const length = Math.sqrt(Math.pow(endX - startX, 2) + Math.pow(endY - startY, 2));\n const angle = Math.atan2(endY - startY, endX - startX) * (180 / Math.PI);\n\n const line = document.createElement('div');\n line.style.width = length + 'mm';\n line.style.transform = `rotate(${angle}deg) translateY(${-width / 2}mm)`;\n line.style.position = 'absolute';\n line.style.top = startY + 'mm';\n line.style.left = startX + 'mm';\n line.style.height = width + 'mm';\n line.style.backgroundColor = color;\n line.style.transformOrigin = 'top left';\n\n this.content.append(line)\n }\n\n roundedRect(rect, fill, stroke, strokeWidth, borderRadius) {\n const element =
;\n\n this.content.append(element)\n \n return element;\n }\n \n colorWithOpacity(color, opacity) {\n return `color-mix(in srgb, ${color} ${opacity * 100}%, transparent)`\n }\n\n /*MD ## Background Images MD*/\n filePathForBackgroundImage(cardDesc, assetsInfo) {\n const id = cardDesc.id;\n if (id) {\n const possibleFileNames = ['jpg', 'png'].map(ending => `${id}.${ending}`);\n const foundEntry = assetsInfo.find(entry => entry.type === 'file' && possibleFileNames.includes(entry.name));\n if (foundEntry) {\n return this.assetsFolder + foundEntry.name;\n }\n }\n\n const typeString = cardDesc.getType() && cardDesc.getType().toLowerCase && cardDesc.getType().toLowerCase()\n const defaultFiles = {\n gadget: 'default-gadget.jpg',\n character: 'default-character.jpg',\n spell: 'default-spell.jpg'\n };\n return this.assetsFolder + (defaultFiles[typeString] || 'default.jpg');\n }\n \n async setBackgroundImage(cardDesc, assetsInfo) {\n const filePath = this.filePathForBackgroundImage(cardDesc, assetsInfo);\n await this._setBackgroundImage(filePath)\n }\n\n async setBackgroundImageForCardBack() {\n const filePath = this.assetsFolder + 'default-spell.jpg';\n await this._setBackgroundImage(filePath)\n }\n\n // #TODO: wait for image to be loaded\n async _setBackgroundImage(filePath) {\n await this.loadImage(filePath)\n this.get('#bg').style.backgroundImage = `url(${filePath})`\n }\n\n async loadImage(filePath) {\n return new Promise((resolve, reject) => {\n const image = new Image();\n image.addEventListener('load', resolve);\n image.addEventListener('error', reject);\n image.src = filePath;\n });\n }\n\n /*MD ## Rendering MD*/\n async renderMagicStyle(cardDesc, outsideBorder, assetsInfo) {\n const [BOX_FILL_COLOR, BOX_STROKE_COLOR, BOX_FILL_OPACITY] = this.colorsForCard(cardDesc);\n\n // black border\n this.roundedRect(outsideBorder, 'black', 'transparent', '0', '0')\n\n // innerBorder\n const INNER_INSET = 3;\n const innerBorder = outsideBorder.insetBy(INNER_INSET);\n\n // id\n this.renderId(cardDesc)\n this.get('#id-version').style.color = 'white'\n\n // card image\n const filePath = this.filePathForBackgroundImage(cardDesc, assetsInfo);\n const newBG =
;\n this.content.append(newBG)\n\n // title bar\n const TITLE_BAR_HEIGHT = 7;\n const titleBar = innerBorder.insetBy(1);\n titleBar.height = TITLE_BAR_HEIGHT;\n const TITLE_BAR_BORDER_WIDTH = 0.200025;\n this.roundedRect(titleBar, this.colorWithOpacity(BOX_FILL_COLOR, .5), BOX_STROKE_COLOR, TITLE_BAR_BORDER_WIDTH, 1)\n\n // card name\n {\n const pos = titleBar.leftCenter().addX(2);\n const fontSize = .6 * titleBar.height::mmToPoint();\n\n const cardName = this.getNameFromCard(cardDesc);\n this.content.append({cardName})\n }\n\n // cost\n const COIN_RADIUS = 4;\n const coinPos = titleBar.bottomLeft().addY(1).addXY(COIN_RADIUS, COIN_RADIUS);\n this.renderCost(cardDesc, coinPos, COIN_RADIUS)\n\n // type & elements\n const typePos = coinPos.addY(COIN_RADIUS * 1.5)\n this.renderType(cardDesc, typePos, BOX_FILL_COLOR, BOX_FILL_OPACITY)\n\n // rule box\n const ruleBox = outsideBorder.copy()\n const height = outsideBorder.height * .4;\n ruleBox.y = ruleBox.bottom() - height;\n ruleBox.height = height;\n // this.debugRect(ruleBox)\n const ruleBoxInset = 1 + INNER_INSET;\n const ruleTextInset = 2;\n await this.renderRuleText(cardDesc, outsideBorder, ruleBox, {\n insetBoxBy: ruleBoxInset,\n insetTextBy: ruleTextInset,\n innerStrokeColor: BOX_STROKE_COLOR,\n innerFillColor: BOX_FILL_COLOR,\n innerFillOpacity: BOX_FILL_OPACITY,\n outerStrokeColor: 'transparent',\n outerFillColor: 'transparent',\n outerFillOpacity: 0,\n });\n\n // qrcode\n const qrAnchor = titleBar.bottomRight().addY(1);\n this.renderQRCode(cardDesc, qrAnchor, outsideBorder)\n \n // tags\n const tagsAnchor = titleBar.bottomRight().addY(1);\n this.renderTags(cardDesc, tagsAnchor, outsideBorder)\n }\n\n async renderFullBleedStyle(cardDesc, outsideBorder, assetsInfo) {\n const type = cardDesc.getType();\n const typeString = type && type.toLowerCase && type.toLowerCase() || '';\n\n if (typeString === 'spell') {\n await this.renderSpell(cardDesc, outsideBorder, assetsInfo)\n } else if (typeString === 'gadget') {\n await this.renderGadget(cardDesc, outsideBorder, assetsInfo)\n } else if (typeString === 'character') {\n await this.renderCharacter(cardDesc, outsideBorder, assetsInfo)\n } else {\n await this.renderMagicStyle(cardDesc, outsideBorder, assetsInfo)\n }\n \n this.renderIsBad(cardDesc, outsideBorder)\n this.renderVersionIndicator(cardDesc, outsideBorder)\n }\n \n maskedCircle(outsideBorder, center, radius, strokeWidth, fillColor, fillOpacity, strokeColor) {\n strokeWidth *= 2; // half covered by mask\n\n const svg = \n \n \n \n \n \n \n \n \n ;\n\n this.content.insertAdjacentHTML('beforeend', svg.outerHTML)\n \n }\n /*MD ### Rendering Card Types MD*/\n // #important\n async renderSpell(cardDesc, outsideBorder, assetsInfo) {\n const [BOX_FILL_COLOR, BOX_STROKE_COLOR, BOX_FILL_OPACITY] = this.colorsForCard(cardDesc);\n\n // background card image\n await this.setBackgroundImage(cardDesc, assetsInfo)\n\n // spell circle\n {\n const CIRCLE_BORDER = -3;\n const radius = (outsideBorder.width - CIRCLE_BORDER) / 2;\n const center = outsideBorder.center().withY(outsideBorder.top() + CIRCLE_BORDER + radius)\n const strokeWidth = 1;\n this.maskedCircle(outsideBorder, center, radius, strokeWidth, BOX_FILL_COLOR, BOX_FILL_OPACITY, BOX_STROKE_COLOR)\n }\n \n // innerBorder\n const innerBorder = outsideBorder.insetBy(3);\n\n // title\n const TITLE_BAR_HEIGHT = 7;\n const COST_COIN_RADIUS = 4;\n const COST_COIN_MARGIN = 2;\n \n const titleBorder = innerBorder.insetBy(1);\n titleBorder.height = TITLE_BAR_HEIGHT;\n\n this.renderTitleBarAndCost(cardDesc, titleBorder, COST_COIN_RADIUS, COST_COIN_MARGIN)\n\n // rule box\n const ruleBox = outsideBorder.copy()\n const height = outsideBorder.height * .3;\n ruleBox.y = ruleBox.bottom() - height;\n ruleBox.height = height;\n // this.debugRect(ruleBox)\n \n // rule text\n const RULE_BOX_INSET = 1;\n const RULE_TEXT_INSET = 1;\n await this.renderRuleText(cardDesc, outsideBorder, ruleBox, {\n insetBoxBy: RULE_BOX_INSET,\n insetTextBy: RULE_TEXT_INSET,\n outerStrokeColor: 'transparent',\n outerFillColor: 'transparent',\n outerFillOpacity: 0,\n });\n\n // qrcode\n const qrAnchor = lively.pt(titleBorder.right(), titleBorder.bottom()).addXY(-RULE_TEXT_INSET, 1);\n this.renderQRCode(cardDesc, qrAnchor, outsideBorder)\n \n // tags\n const tagsAnchor = lively.pt(titleBorder.right(), titleBorder.bottom()).addXY(-RULE_TEXT_INSET, 1);\n this.renderTags(cardDesc, tagsAnchor, outsideBorder)\n\n // id\n this.renderId(cardDesc)\n }\n\n // #important\n async renderGadget(cardDesc, outsideBorder, assetsInfo) {\n const [BOX_FILL_COLOR, BOX_STROKE_COLOR, BOX_FILL_OPACITY] = this.colorsForCard(cardDesc);\n\n // background card image\n await this.setBackgroundImage(cardDesc, assetsInfo)\n\n // innerBorder\n const innerBorder = outsideBorder.insetBy(3);\n // this.roundedRect(innerBorder, 'steelblue', 'red', 3, 0)\n\n // top box\n const topBox = outsideBorder.copy()\n {\n topBox.height = 13;\n const box = this.roundedRect(topBox, this.colorWithOpacity(BOX_FILL_COLOR, BOX_FILL_OPACITY), 'transparent', 0, 0);\n box.style.backdropFilter = 'blur(4px)';\n \n this.line(topBox.bottomLeft(), topBox.bottomRight(), BOX_STROKE_COLOR, 1)\n }\n \n // title\n {\n const TITLE_BAR_HEIGHT = 7;\n const COST_COIN_RADIUS = 4;\n const COST_COIN_MARGIN = 2;\n \n const titleBorder = innerBorder.insetBy(1);\n titleBorder.height = TITLE_BAR_HEIGHT;\n \n this.renderTitleBarAndCost(cardDesc, titleBorder, COST_COIN_RADIUS, COST_COIN_MARGIN)\n }\n \n // rule box border calc\n const ruleBox = outsideBorder.copy()\n const height = outsideBorder.height * .4;\n ruleBox.y = ruleBox.bottom() - height;\n ruleBox.height = height;\n // this.debugRect(ruleBox)\n \n // rule text\n const RULE_BOX_INSET = 1;\n const RULE_TEXT_INSET = 1;\n await this.renderRuleText(cardDesc, outsideBorder, ruleBox, {\n insetBoxBy: RULE_BOX_INSET,\n insetTextBy: RULE_TEXT_INSET,\n outerStrokeColor: BOX_STROKE_COLOR,\n outerFillColor: BOX_FILL_COLOR,\n outerFillOpacity: BOX_FILL_OPACITY,\n });\n\n // qrcode\n const qrAnchor = lively.pt(topBox.right(), topBox.bottom()).addXY(-RULE_TEXT_INSET, 1);\n this.renderQRCode(cardDesc, qrAnchor, outsideBorder)\n \n // tags\n const tagsAnchor = lively.pt(topBox.right(), topBox.bottom()).addXY(-RULE_TEXT_INSET, 1);\n this.renderTags(cardDesc, tagsAnchor, outsideBorder)\n\n // id\n this.renderId(cardDesc)\n }\n\n // #important\n async renderCharacter(cardDesc, outsideBorder, assetsInfo) {\n const [BOX_FILL_COLOR, BOX_STROKE_COLOR, BOX_FILL_OPACITY] = this.colorsForCard(cardDesc);\n\n // background card image\n await this.setBackgroundImage(cardDesc, assetsInfo)\n\n // Zohar design\n {\n const ZOHAR_DESIGN_BORDER_WIDTH = .5;\n [[outsideBorder.topLeft(), lively.pt(1, 0)], [outsideBorder.topRight(), lively.pt(-1, 0)]].forEach(([startingPt, direction]) => {\n const dirX = direction.x;\n startingPt = startingPt.subY(5)\n const topMost = startingPt.addXY(dirX*8, 0);\n const triangleTop = topMost.addXY(0, 15 + 5);\n const triangleOuter = triangleTop.addXY(-dirX*15, 15);\n const triangleBottom = triangleOuter.addXY(dirX*15, 15);\n const bottom = triangleBottom.addXY(0, 100);\n const bottomOuter = bottom.addXY(-dirX*10, 0);\n const diamondPoints = `${startingPt.x} ${startingPt.y}, ${topMost.x} ${topMost.y}, ${triangleTop.x} ${triangleTop.y}, ${triangleOuter.x} ${triangleOuter.y}, ${triangleBottom.x} ${triangleBottom.y}, ${bottom.x} ${bottom.y}, ${bottomOuter.x} ${bottomOuter.y}`;\n\n const svg = \n \n ;\n\n this.content.insertAdjacentHTML('beforeend', svg.outerHTML)\n })\n }\n \n // innerBorder\n const innerBorder = outsideBorder.insetBy(3);\n // this.roundedRect(innerBorder, 'steelblue', 'red', 3, 0)\n\n // title\n const TITLE_BAR_HEIGHT = 7;\n const COST_COIN_RADIUS = 4;\n const COST_COIN_MARGIN = 2;\n \n const titleBorder = innerBorder.insetBy(1);\n titleBorder.height = TITLE_BAR_HEIGHT;\n \n this.renderTitleBarAndCost(cardDesc, titleBorder, COST_COIN_RADIUS, COST_COIN_MARGIN)\n \n // rule box border calc\n const ruleBox = outsideBorder.copy()\n const height = outsideBorder.height * .4;\n ruleBox.y = ruleBox.bottom() - height;\n ruleBox.height = height;\n \n // rule text\n const RULE_BOX_INSET = 1;\n const RULE_TEXT_INSET = 1;\n await this.renderRuleText(cardDesc, outsideBorder, ruleBox, {\n insetBoxBy: RULE_BOX_INSET,\n insetTextBy: RULE_TEXT_INSET,\n outerStrokeColor: BOX_STROKE_COLOR,\n outerFillColor: BOX_FILL_COLOR,\n outerFillOpacity: BOX_FILL_OPACITY,\n });\n\n // qrcode\n const qrAnchor = lively.pt(titleBorder.right(), titleBorder.bottom()).addXY(-RULE_TEXT_INSET, 1);\n this.renderQRCode(cardDesc, qrAnchor, outsideBorder)\n \n // tags\n const tagsAnchor = lively.pt(titleBorder.right(), titleBorder.bottom()).addXY(-RULE_TEXT_INSET, 1);\n this.renderTags(cardDesc, tagsAnchor, outsideBorder)\n\n // id\n this.renderId(cardDesc)\n }\n \n /*MD ### Rendering Card Components MD*/\n renderTitleBarAndCost(cardDesc, border, costCoinRadius, costCoinMargin) {\n const TITLE_BAR_BORDER_WIDTH = 0.200025;\n\n const titleBar = border.copy()\n const coinLeftCenter = titleBar.leftCenter()\n const spacingForCoin = 2*costCoinRadius + costCoinMargin\n titleBar.x += spacingForCoin\n titleBar.width -= spacingForCoin\n\n const [BOX_FILL_COLOR, BOX_STROKE_COLOR, BOX_FILL_OPACITY] = this.colorsForCard(cardDesc);\n\n // title space\n // this.roundedRect(border, this.colorWithOpacity('#ffffff', .5), 'transparent', 0, 0)\n this.roundedRect(titleBar, this.colorWithOpacity('#ffffff', .5), 'transparent', 0, 1)\n \n // title bar\n this.roundedRect(titleBar, this.colorWithOpacity(BOX_FILL_COLOR, .5), BOX_STROKE_COLOR, TITLE_BAR_BORDER_WIDTH, 1)\n \n // card name\n {\n const pos = titleBar.leftCenter().addX(2);\n const fontSize = .6 * titleBar.height::mmToPoint();\n this.content.append({this.getNameFromCard(cardDesc)})\n }\n\n const coinCenter = coinLeftCenter.addX(costCoinRadius);\n this.renderInHandSymbols(cardDesc, border, costCoinRadius, costCoinMargin, coinCenter)\n }\n \n renderInHandSymbols(cardDesc, border, costCoinRadius, costCoinMargin, coinCenter) {\n let currentCenter = coinCenter;\n\n // cost\n this.renderCost(cardDesc, currentCenter, costCoinRadius)\n\n if ((cardDesc.getType() || '').toLowerCase() !== 'character') {\n // vp\n currentCenter = currentCenter.addY(costCoinRadius * 2.75);\n this.renderBaseVP(cardDesc, currentCenter, costCoinRadius)\n\n // element (list)\n currentCenter = currentCenter.addY(costCoinRadius * 2.75);\n const elementListDirection = 1;\n currentCenter = this.renderElementList(cardDesc, currentCenter, costCoinRadius, elementListDirection)\n } else {\n currentCenter = currentCenter.addY(costCoinRadius * 1);\n }\n\n // type\n currentCenter = currentCenter.addY(costCoinRadius * .75)\n const [BOX_FILL_COLOR, BOX_STROKE_COLOR, BOX_FILL_OPACITY] = this.colorsForCard(cardDesc);\n this.renderType(cardDesc, currentCenter, BOX_FILL_COLOR, BOX_FILL_OPACITY)\n }\n\n renderElementList(cardDesc, pos, radius, direction) {\n const elements = this.getElementsFromCard(cardDesc, true);\n for (let element of elements) {\n this.renderElementSymbol(element, pos, radius)\n pos = pos.addY(direction * radius * .75);\n }\n return pos.addY(direction * radius * .25);\n }\n\n renderCost(cardDesc, pos, coinRadius) {\n const costSize = coinRadius / 3;\n\n const costDesc = cardDesc.getCost();\n const cost = Array.isArray(costDesc) ? costDesc.first : costDesc;\n\n const coinCenter = pos;\n const strokeWidth = .2 * costSize;\n const size = `${2 * coinRadius}mm`;\n const svg = ;\n this.content.insertAdjacentHTML('beforeend', svg.outerHTML)\n\n// {\n// const r = ;\n// const str = {r};\n// this.content.append(str);\n// }\n\n this.renderIconText(coinCenter, costSize, cost, CSS_FONT_FAMILY_CARD_COST)\n }\n\n renderBaseVP(cardDesc, pos, coinRadius) {\n const costSize = coinRadius / 3;\n \n const vp = cardDesc.getBaseVP() || 0;\n const fillColor = vp === 0 ? VP_FILL_ZERO : VP_FILL\n const fillOpacity = .9\n const strokeColor = vp === 0 ? VP_STROKE_ZERO : VP_STROKE\n const strokeWidth = .2 * costSize;\n\n const iconCenter = pos;\n\n // diamond shape\n const diagonal = coinRadius * .9 * Math.sqrt(2)\n const down = pos.addY(diagonal)\n const left = pos.addX(-diagonal)\n const up = pos.addY(-diagonal)\n const right = pos.addX(diagonal)\n const diamondPoints = `${down.x} ${down.y}, ${left.x} ${right.y}, ${up.x} ${up.y}, ${right.x} ${right.y}`;\n\n const svg = \n \n ;\n this.content.insertAdjacentHTML('beforeend', svg.outerHTML)\n this.renderIconText(iconCenter, costSize, vp, CSS_FONT_FAMILY_CARD_VP)\n }\n\n renderIconText(centerPos, size, text, font) {\n if (text === undefined) {\n return\n }\n \n const iconText = {'' + text};\n \n this.content.append(iconText)\n }\n \n // #important\n async renderRuleText(cardDesc, outsideBorder, ruleBox, options) {\n const ruleTextBoxElement = this.setupRuleTextBox(cardDesc, outsideBorder, ruleBox, options)\n this.addTextToRuleBox(cardDesc, ruleTextBoxElement)\n }\n\n setupRuleTextBox(cardDesc, outsideBorder, ruleBox, options) {\n const {\n insetBoxBy = 1,\n insetTextBy = 1,\n innerStrokeWidth = .2,\n innerStrokeColor = 'black',\n innerFillColor = 'white',\n innerFillOpacity = .5,\n outerStrokeColor= 'gray',\n outerFillColor = 'white',\n outerFillOpacity = .5,\n } = options\n \n const outerBox =
;\n\n this.content.append(outerBox)\n\n const ruleTextBox = ruleBox.insetBy(insetTextBy);\n // cardEditor.debugRect(ruleTextBox)\n const marginCalc = `${insetBoxBy}mm - ${innerStrokeWidth}mm / 2`;\n const paddingCalc = `${insetTextBy}mm - ${innerStrokeWidth}mm / 2`;\n const ruleTextBoxElement =
;\n\n outerBox.append(ruleTextBoxElement)\n \n return ruleTextBoxElement;\n }\n \n addTextToRuleBox(cardDesc, ruleTextBoxElement) {\n const rulesText = document.createElement('ubg-rules-text')\n ruleTextBoxElement.append(rulesText)\n rulesText.applyRulesText(cardDesc)\n }\n \n renderType(cardDesc, anchorPt, color, opacity) {\n // function curate() {\n // return this.toLower().upperFirst();\n // }\n // function prepend(other) {\n // return other + ' ' + this;\n // }\n // const element = cardDesc.getElement();\n let fullText = (cardDesc.getType() || '').toLower().upperFirst()\n // if (Array.isArray(element)) {\n // element.forEach(element => {\n // fullText = fullText::prepend(element::curate())\n // })\n // } else if (element) {\n // fullText = fullText::prepend(element::curate())\n // }\n\n this.content.append({fullText});\n }\n \n renderQRCode(cardDesc, qrAnchor, outsideBorder) {\n return;\n const canvas = ;\n const { QrCode } = qrcodegen\n var qr = QrCode.encodeText(\"\" + cardDesc.getId(), QrCode.Ecc.HIGH);\n\n function drawCanvas(qr, scale, border, lightColor, darkColor, canvas) {\n if (scale <= 0 || border < 0)\n throw new RangeError(\"Value out of range\");\n const width = (qr.size + border * 2) * scale;\n canvas.width = width;\n canvas.height = width;\n let ctx = canvas.getContext(\"2d\");\n for (let y = -border; y < qr.size + border; y++) {\n for (let x = -border; x < qr.size + border; x++) {\n ctx.fillStyle = qr.getModule(x, y) ? darkColor : lightColor;\n ctx.fillRect((x + border) * scale, (y + border) * scale, scale, scale);\n }\n }\n }\n\n const [BOX_FILL_COLOR, BOX_STROKE_COLOR, BOX_FILL_OPACITY] = this.colorsForCard(cardDesc);\n\n drawCanvas(qr, 3, 1, \"white\", BOX_STROKE_COLOR, canvas);\n this.content.append(canvas)\n }\n \n renderTags(cardDesc, tagsAnchor, outsideBorder) {\n const tags = cardDesc.getTags().sortBy(i => i, true).map(tag =>
#{tag}
);\n const FONT_SIZE = 7;\n \n this.content.append({...tags})\n }\n\n renderElementSymbol(element, pos, radius) {\n const innerBounds = lively.rect(0, 0, 10, 10)\n const svgInnerPos = innerBounds.center();\n const svgInnerRadius = innerBounds.width / 2;\n const outerBounds = lively.rect(pos.x - radius, pos.y - radius, radius * 2, radius * 2);\n const yourSvgString = SVG.outerSVG(SVG.elementSymbol(element, svgInnerPos, svgInnerRadius), innerBounds, outerBounds);\n\n this.content.insertAdjacentHTML('beforeend', yourSvgString)\n }\n \n renderId(cardDesc) {\n this.get('#card-id').innerHTML = cardDesc.id || '???'\n this.get('#card-version').innerHTML = cardDesc.getHighestVersion()\n }\n\n get content() {\n return this.get('#content');\n }\n \n renderIsBad(cardDesc, outsideBorder) {\n const slash = (color, width=2, offset=lively.pt(0,0)) => {\n const start = outsideBorder.topRight().addPt(offset);\n const end = outsideBorder.bottomLeft().addPt(offset);\n this.line(start, end, color, width)\n }\n \n if (cardDesc.hasTag('duplicate')) {\n slash('#bbbbbb', 2, lively.pt(-3, -3))\n }\n if (cardDesc.hasTag('unfinished')) {\n slash('#888888', 2, lively.pt(-2, -2))\n }\n if (cardDesc.hasTag('bad')) {\n slash('#ff0000', 2)\n }\n if (cardDesc.hasTag('deprecated')) {\n slash('#ff00ff', 2, lively.pt(2, 2))\n }\n if (cardDesc.getRating() === 'remove') {\n slash('#999999', 5, lively.pt(-5, -5))\n }\n }\n \n renderVersionIndicator(cardDesc, outsideBorder) {\n const VERSION_FILL = '#f7d359';\n this.get('#version-indicator').style.setProperty(\"--version-fill\", VERSION_FILL);\n }\n\n async _renderCardBack(cardDesc, outsideBorder, assetsInfo) {\n const [BOX_FILL_COLOR, BOX_STROKE_COLOR, BOX_FILL_OPACITY] = this.colorsForCard(cardDesc);\n\n // background card image\n await this.setBackgroundImageForCardBack()\n \n // inner border\n {\n const radius = outsideBorder.height * .5 * .9;\n const center = outsideBorder.center()\n \n this.maskedCircle(outsideBorder, center, radius, 0, BOX_FILL_COLOR, BOX_FILL_OPACITY, 'transparent')\n }\n\n // element symbols\n const innerBorder = outsideBorder.insetBy(3);\n const RADIUS = 5\n this.renderElementList(cardDesc, innerBorder.topLeft().addXY(RADIUS, RADIUS), RADIUS, 1)\n this.renderElementList(cardDesc, innerBorder.topRight().addXY(-RADIUS, RADIUS), RADIUS, 1)\n this.renderElementList(cardDesc, innerBorder.bottomLeft().addXY(RADIUS, -RADIUS), RADIUS, -1)\n this.renderElementList(cardDesc, innerBorder.bottomRight().addXY(-RADIUS, -RADIUS), RADIUS, -1)\n \n // outerBorder\n this.roundedRect(outsideBorder, 'transparent', BOX_STROKE_COLOR, 2, OUTSIDE_BORDER_ROUNDING.x * 1.5)\n \n // hide version elements\n this.get('#id-version').remove()\n this.get('#version-indicator').remove()\n }\n\n /*MD ## Basic Web Components MD*/\n initialize() {\n if (this.hasAttribute('for-preload')) {\n return;\n }\n\n this.windowTitle = \"UbgCard\";\n }\n \n static get observedAttributes() {\n return [\"card\", \"src\", \"is-cardback\"];\n }\n \n attributeChangedCallback(name, oldValue, newValue) {\n lively.notify(`${oldValue} -> ${newValue}`, name)\n }\n \n /*MD ## External API MD*/\n setSrc(src) {\n return this.src = src;\n }\n\n setCard(card) {\n return this.card = card;\n }\n\n setCards(cards) {\n return this.cards = cards;\n }\n\n async render() {\n this._checkOptionsSet()\n const assetsInfo = await this.fetchAssetsInfo();\n const outsideBorder = lively.pt(0,0).extent(POKER_CARD_SIZE_MM);\n const cardToPrint = this.card;\n await this.renderFullBleedStyle(cardToPrint, outsideBorder, assetsInfo)\n }\n\n async renderCardBack() {\n this._checkOptionsSet()\n const assetsInfo = await this.fetchAssetsInfo();\n const outsideBorder = lively.pt(0,0).extent(POKER_CARD_SIZE_MM);\n const cardToPrint = this.card;\n await this._renderCardBack(cardToPrint, outsideBorder, assetsInfo)\n }\n\n _checkOptionsSet() {\n if (!this.src) {\n lively.warn('cannot render: \"src\" not set')\n }\n if (!this.card) {\n lively.warn('cannot render: \"card\" not set')\n }\n if (!this.cards) {\n lively.warn('cannot render: \"cards\" not set')\n }\n }\n\n /*MD ## Lively-specific API MD*/\n livelyPrepareSave() {\n // this.setAttribute(\"data-mydata\", this.get(\"#textField\").value)\n }\n \n livelyPreMigrate() {\n // is called on the old object before the migration\n }\n \n livelyMigrate(other) {\n const src = other.src;\n if (src) {\n this.setSrc(src);\n }\n \n const cards = other.cards;\n if (cards) {\n this.setCards(cards);\n }\n \n const card = other.card;\n if (card) {\n this.setCard(card);\n this.render();\n }\n }\n \n async livelyExample() {\n }\n \n}\n"} -{"from":22235,"to":22235,"name":"color","color":"#9ecae1"} \ No newline at end of file diff --git a/src/components/widgets/ubg-cards-editor.js.l4a b/src/components/widgets/ubg-cards-editor.js.l4a deleted file mode 100644 index 5dff6bbb1..000000000 --- a/src/components/widgets/ubg-cards-editor.js.l4a +++ /dev/null @@ -1,2 +0,0 @@ -{"type":"Reference","version":"45560bee52223bed5aa637fe9c73e819f5d7437e","content":"import Morph from 'src/components/widgets/lively-morph.js';\n\nimport preloaWebComponents from 'src/client/preload-components.js'\nawait preloaWebComponents(['ubg-card'])\n\nexport default class UBGCardsEditor extends Morph {\n async initialize() {\n\n this.setAttribute(\"tabindex\", 0);\n this.windowTitle = \"UBGCardsEditor\";\n this.registerButtons();\n lively.html.registerKeys(this);\n this.prepareOnChangeCallbacks();\n }\n\n prepareOnChangeCallbacks() {\n // 'focus', 'blur', 'keyup', 'keypress', 'change', \n for (let eventName of ['input']) {\n this.$id.addEventListener(eventName, evt => this.modify$id(evt, eventName), false);\n this.$name.addEventListener(eventName, evt => this.modify$name(evt), false);\n this.$identity.addEventListener(eventName, evt => this.modify$identity(evt), false);\n this.$type.addEventListener(eventName, evt => this.modify$type(evt), false);\n this.$element.addEventListener(eventName, evt => this.modify$element(evt), false);\n this.$cost.addEventListener(eventName, evt => this.modify$cost(evt), false);\n this.$vp.addEventListener(eventName, evt => this.modify$vp(evt), false);\n this.$text.addEventListener(eventName, evt => this.modify$text(evt), false);\n this.$notes.addEventListener(eventName, evt => this.modify$notes(evt), false);\n this.$art.addEventListener(eventName, evt => this.modify$art(evt), false);\n this.$isPrinted.addEventListener(eventName, evt => this.modify$isPrinted(evt), false);\n }\n this.$text.addEventListener('keydown', evt => this.keydown$text(evt), false);\n this.$tagsInput.addEventListener('keydown', evt => this.keydown$tagInput(evt), false);\n const rating = this.get('#rating');\n rating.addEventListener('change', evt => {\n if (evt.target.name === 'rating') {\n this.modify$rating(evt)\n }\n });\n rating.addEventListener('keydown', evt => {\n this.setRatingFromKeyEvent(evt)\n });\n }\n \n setRatingFromKeyEvent(evt) {\n // lively.notify(evt.key)\n const key = evt.key;\n if (key >= '1' && key <= '9') {\n const index = parseInt(key, 10) - 1;\n // lively.notify('key', index)\n const radioButtonName = 'rating';\n const radioButtons = this.getAllSubmorphs(`input[type=\"radio\"][name=\"${radioButtonName}\"]`);\n\n if (index < radioButtons.length) { // Check if the index is within the range of your radio buttons\n const button = radioButtons[index]\n button.checked = true;\n button.focus()\n const changeEvent = new Event('change', {\n 'bubbles': true, // Allows the event to bubble up through the DOM\n 'cancelable': false // Indicates the event cannot be canceled\n });\n radioButtons[index].dispatchEvent(changeEvent);\n }\n }\n }\n \n get ubg() {\n return lively.allParents(this, undefined, true).find(ele => ele.tagName === 'UBG-CARDS' || ele.tagName === 'JSPDF-EXAMPLE');\n }\n\n // onKeyDown(evt) {\n // return;\n // if (evt.ctrlKey && evt.key == \"s\") {\n // evt.stopPropagation();\n // evt.preventDefault();\n // lively.warn(evt.key, 'key from editor')\n // }\n // }\n\n selectedEntries() {\n return Array.from(this.querySelectorAll(\"lively-bibtex-entry.selected\"));\n }\n\n onContextMenu(evt) {\n if (!evt.shiftKey) {\n evt.stopPropagation();\n evt.preventDefault();\n return;\n }\n }\n\n get src() {\n return this.card;\n }\n\n set src(card) {\n this.card = card;\n this.updateView();\n }\n\n get $id() {\n return this.get('#id');\n }\n get $name() {\n return this.get('#name');\n }\n get $identity() {\n return this.get('#identity');\n }\n get $type() {\n return this.get('#type');\n }\n get $element() {\n return this.get('#element');\n }\n get $cost() {\n return this.get('#cost');\n }\n get $vp() {\n return this.get('#vp');\n }\n get $text() {\n return this.get('#text');\n }\n get $tagsInput() {\n return this.get('#tags-input');\n }\n get $tagsList() {\n return this.get('#tags-list');\n }\n get $rating() {\n return this.get('#rating');\n }\n get $notes() {\n return this.get('#notes');\n }\n get $art() {\n return this.get('#art');\n }\n get $isPrinted() {\n return this.get('#isPrinted');\n }\n\n modify$id(evt) {\n const id = this.$id.value;\n const intId = parseInt(id);\n\n if (_.isNaN(intId)) {\n this.card.setId();\n } else {\n this.card.setId(intId);\n }\n\n this.propagateChange()\n }\n display$id() {\n const id = this.card.getId();\n\n if (id === undefined) {\n this.$id.value = '';\n return;\n }\n\n this.$id.value = id;\n }\n\n modify$name(evt) {\n const name = this.$name.value;\n if (name === '') {\n this.card.setName();\n } else {\n this.card.setName(name);\n }\n\n this.propagateChange()\n }\n display$name() {\n const name = this.card.getName();\n this.$name.value = name === undefined ? '' : name;\n }\n\n modify$identity(evt) {\n const identity = this.$identity.value;\n if (identity === '') {\n this.card.setIdentity();\n } else {\n this.card.setIdentity(identity);\n }\n\n this.propagateChange()\n }\n display$identity() {\n const identity = this.card.getIdentity();\n this.$identity.value = identity === undefined ? '' : identity;\n }\n\n modify$type(evt) {\n const type = this.$type.value;\n if (type === '') {\n this.card.setType();\n } else {\n this.card.setType(type);\n }\n\n this.propagateChange()\n }\n display$type() {\n const type = this.card.getType();\n this.$type.value = type === undefined ? '' : type;\n }\n\n modify$element(evt) {\n const element = this.$element.value;\n if (element === '') {\n this.card.setElement();\n } else if (element.includes(',')) {\n this.card.setElement(element.split(','));\n } else {\n this.card.setElement(element);\n }\n\n this.propagateChange()\n }\n display$element() {\n const element = this.card.getElement();\n\n if (element === undefined) {\n this.$element.value = '';\n return;\n }\n\n if (Array.isArray(element)) {\n this.$element.value = element.join(',');\n return;\n }\n\n this.$element.value = element;\n }\n\n modify$cost(evt) {\n const cost = this.$cost.value;\n\n if (cost === '') {\n this.card.setCost();\n } else if (cost.includes(',')) {\n const costs = cost.split(',');\n const parsedCosts = costs.map(c => parseInt(c)).filter(cost => !_.isNaN(cost));\n if (parsedCosts.length >= 1) {\n this.card.setCost(parsedCosts);\n } else {\n this.card.setCost();\n }\n } else {\n const intCost = parseInt(cost);\n if (_.isNaN(intCost)) {\n this.card.setCost();\n } else {\n this.card.setCost(intCost);\n }\n }\n\n this.propagateChange()\n }\n display$cost() {\n const cost = this.card.getCost();\n\n if (cost === undefined) {\n this.$cost.value = '';\n return;\n }\n\n if (Array.isArray(cost)) {\n this.$cost.value = cost.join(',');\n return;\n }\n\n this.$cost.value = cost;\n }\n\n modify$vp(evt) {\n const vp = this.$vp.value;\n \n if (vp === '') {\n this.card.setBaseVP();\n } else if ('*+-'.split('').some(char => vp.endsWith(char))) {\n this.card.setBaseVP(vp);\n } else {\n const intCost = parseInt(vp);\n if (_.isNaN(intCost)) {\n this.card.setBaseVP();\n } else {\n this.card.setBaseVP(intCost);\n }\n }\n\n this.propagateChange()\n }\n display$vp() {\n const vp = this.card.getBaseVP();\n\n if (vp === undefined) {\n this.$vp.value = '';\n return;\n }\n\n this.$vp.value = vp;\n }\n\n\n keydown$text(evt) {\n if (evt.key === 'Delete' && evt.ctrlKey) {\n evt.stopPropagation()\n return\n } \n }\n modify$text(evt) {\n const text = this.$text.value;\n if (text === '') {\n this.card.setText();\n } else {\n this.card.setText(text);\n }\n\n this.propagateChange()\n }\n display$text() {\n const text = this.card.getText();\n this.$text.value = text === undefined ? '' : text;\n }\n \n keydown$tagInput(evt) {\n const input = this.$tagsInput;\n if (evt.key === 'Escape') {\n input.value = ''\n return\n }\n if (evt.key === 'Enter') {\n const value = input.get('input').value;\n if (value) {\n this.card.addTag(value);\n input.value = ''\n this.propagateChange()\n } else {\n lively.warn('no tag to add.')\n }\n return\n }\n }\n display$tags() {\n const tags = _.sortBy(this.card.getTags());\n const editor = this;\n let previouslyFocussed = this.$tagsList.childNodes.find(n => n.matches(':focus'))\n previouslyFocussed = previouslyFocussed && previouslyFocussed.textContent\n \n function getElementIndex(element) {\n // Get all children of the parent element\n var children = Array.from(element.parentNode.children);\n\n // Find the index of 'element' among its siblings\n var index = children.indexOf(element);\n\n return index;\n }\n \n function onkeydown (evt) {\n function getDeepActiveElement() {\n let active = document.activeElement;\n while (active && active.shadowRoot && active.shadowRoot.activeElement) {\n active = active.shadowRoot.activeElement;\n }\n return active;\n }\n \n if (evt.key === 'Delete' || evt.key === 'Backspace') {\n evt.stopPropagation()\n evt.preventDefault()\n if (evt.repeat) {\n return;\n }\n \n if (getDeepActiveElement() === this) {\n lively.notify(123)\n const sibling = this.nextElementSibling || this.previousElementSibling\n if (sibling) {\n sibling.focus()\n } else {\n editor.$tagsInput.focus()\n }\n }\n \n editor.card.removeTag(this.textContent)\n editor.propagateChange()\n return\n }\n }\n\n this.$tagsList.innerHTML = ''\n this.$tagsList.append(...tags.map(tag => {\n return {tag}\n }));\n \n if (previouslyFocussed) {\n const tagElements = this.$tagsList.childNodes;\n if (tagElements.length === 0) {\n this.$tagsInput.focus()\n } else {\n // get best match from childnodes\n for (let tagElement of tagElements) {\n if (tagElement.textContent >= previouslyFocussed) {\n tagElement.focus()\n return\n }\n tagElements[tagElements.length - 1].focus()\n }\n }\n }\n }\n\n modify$rating(evt) {\n const rating = evt.target.value;\n if (rating === '') {\n this.card.setRating();\n } else {\n this.card.setRating(rating);\n }\n\n this.propagateChange()\n }\n display$rating() {\n const rating = this.card.getRating() || 'unset';\n\n const selectedOption = this.$rating.querySelector(`[value='${rating}']`)\n if (selectedOption) {\n selectedOption.checked = true;\n } else {\n lively.warn('Unknown rating ' + rating)\n }\n }\n\n modify$notes(evt) {\n const notes = this.$notes.value;\n if (notes === '') {\n this.card.setNotes();\n } else {\n this.card.setNotes(notes);\n }\n\n this.propagateChange()\n }\n display$notes() {\n const notes = this.card.getNotes();\n this.$notes.value = notes === undefined ? '' : notes;\n }\n \n modify$art(evt) {\n const art = this.$art.value;\n if (art === '') {\n this.card.setArtDirection();\n } else {\n this.card.setArtDirection(art);\n }\n\n this.propagateChange()\n }\n display$art() {\n const art = this.card.getArtDirection();\n this.$art.value = art === undefined ? '' : art;\n }\n\n modify$isPrinted(evt) {\n const isPrinted = this.$isPrinted.checked;\n if (isPrinted) {\n this.card.setIsPrinted(true);\n } else {\n this.card.setIsPrinted();\n }\n\n this.ubgMarkMyCardAsChanged()\n }\n display$isPrinted() {\n // lively.notify('update printed')\n const isPrinted = this.card.getIsPrinted();\n this.$isPrinted.checked = isPrinted === undefined ? false : isPrinted;\n }\n \n propagateChange() {\n this.ubgMarkMyCardAsChanged();\n this.display$isPrinted();\n this.display$tags();\n this.delayedUpdateCardPreview();\n }\n \n ubgMarkMyCardAsChanged() {\n this.ubg.markCardAsChanged(this.card);\n }\n\n updateTagSelection() {\n const tags = this.ubg.getAllTags()\n this.$tagsInput.setOptions(tags)\n }\n \n async updateView() {\n this.updateTagSelection();\n \n this.display$id();\n this.display$name();\n this.display$identity();\n this.display$type();\n this.display$element();\n this.display$cost();\n this.display$vp();\n this.display$text();\n this.display$tags();\n this.display$rating();\n this.display$notes();\n this.display$art();\n this.display$isPrinted();\n\n await this.updateCardPreview();\n }\n\n async delayedUpdateCardPreview() {\n this.setAttribute('preview-queued', true);\n this._delayedUpdateCardPreview = this._delayedUpdateCardPreview || _.debounce(() => this.updateCardPreview(), 500);\n\n this._delayedUpdateCardPreview();\n }\n\n async updateCardPreview() {\n this.removeAttribute('preview-queued');\n delete this._delayedUpdateCardPreview;\n\n this.renderToHTML()\n }\n \n renderToHTML() {\n const card = this.card;\n const ubg = this.ubg;\n \n const cardPreview = document.createElement('ubg-card')\n cardPreview.setAttribute('id', 'preview')\n this.get('#preview').replaceWith(cardPreview)\n \n cardPreview.setCard(card)\n cardPreview.setCards(ubg.cards)\n cardPreview.setSrc(ubg.src)\n cardPreview.render()\n }\n\n selectedEntry() {\n return this.table.asJSO()[this.table.currentRowIndex - 1];\n }\n\n focusOnText() {\n this.$text.focus()\n }\n \n livelyMigrate(other) {\n this.src = other.src;\n }\n}"} -{"from":2780,"to":2780,"name":"color","color":"#9ecae1"} \ No newline at end of file diff --git a/src/components/widgets/ubg-rules-text.js b/src/components/widgets/ubg-rules-text.js index 22e9b7958..13bda9e1a 100644 --- a/src/components/widgets/ubg-rules-text.js +++ b/src/components/widgets/ubg-rules-text.js @@ -4,14 +4,6 @@ import Morph from 'src/components/widgets/lively-morph.js'; import { Point } from 'src/client/graphics.js' -import paper from 'src/client/paperjs-wrapper.js' -// import 'https://lively-kernel.org/lively4/ubg-assets/load-assets.js'; - -import qrcodegen from 'https://lively-kernel.org/lively4/aexpr/src/external/qrcodegen.js' - -const POKER_CARD_SIZE_INCHES = lively.pt(2.5, 3.5); -const POKER_CARD_SIZE_MM = POKER_CARD_SIZE_INCHES.scaleBy(25.4); - const CSS_FONT_FAMILY_BEAUFORT_FOR_LOL_BOLD = "Beaufort for LOL Bold" const CSS_FONT_FAMILY_BEAUFORT_FOR_LOL_REGULAR = "Beaufort for LOL Regular" const CSS_FONT_FAMILY_UNIVERS_55 = "Univers 55" @@ -46,156 +38,7 @@ function mmToPoint() { return this * 2.835; } -const fire = ; -const water = ; -const earth = ; -const wind = ; -const gray = ; -const question = ; - -class PathDataScaleCache { - static getPathData(element, size = lively.pt(10, 10)) { - if (!this.cache) { - this.cache = {} - } - - const key = `${element}-${size.x}-${size.y}`; - if (!this.cache[key]) { - // lively.notify(`${element}-${size.x}-${size.y}`, 'cache miss') - this.cache[key] = this._scalePathData(element, size) - } - - return this.cache[key] - } - - static _scalePathData(element, size) { - const { glyph } = forElement(element); - const path = new paper.Path(glyph.getAttribute('d')); - - path.scale(1, -1); - - const margin = size.scaleBy(0.1); - const boundingRect = new paper.Path.Rectangle({ - point: margin.toPair(), - size: size.subPt(margin.scaleBy(2)).toPair() - }); - path.fitBounds(boundingRect.bounds); - - return path.pathData; - } -} - -function tenTenPathData(element) { - return PathDataScaleCache.getPathData(element, lively.pt(10, 10)); -} - -const elementInfo = { - fire: { - name: 'fire', - faIcon: 'book', - glyph: fire, - get pathData() { return tenTenPathData('fire') }, - pathWidth: parseInt(fire.getAttribute('horiz-adv-x')), - pathHeight: parseInt(fire.getAttribute('vert-adv-y')), - fill: '#ffbbbb', - stroke: '#ff0000', - others: ['water', 'earth', 'wind'] - }, - water: { - name: 'water', - faIcon: 'droplet', - glyph: water, - get pathData() { return tenTenPathData('water') }, - pathWidth: parseInt(water.getAttribute('horiz-adv-x')), - pathHeight: parseInt(water.getAttribute('vert-adv-y')), - fill: '#8888ff', - stroke: '#0000ff', - others: ['fire', 'earth', 'wind'] - }, - earth: { - name: 'earth', - faIcon: 'mountain', - glyph: earth, - get pathData() { return tenTenPathData('earth') }, - pathWidth: parseInt(earth.getAttribute('horiz-adv-x')), - pathHeight: parseInt(earth.getAttribute('vert-adv-y')), - fill: 'rgb(255, 255, 183)', - stroke: '#ffd400', - others: ['fire', 'water', 'wind'] - }, - wind: { - name: 'wind', - faIcon: 'cloud', - glyph: wind, - get pathData() { return tenTenPathData('wind') }, - pathWidth: parseInt(wind.getAttribute('horiz-adv-x')), - pathHeight: parseInt(wind.getAttribute('vert-adv-y')), - fill: '#bbffbb', - stroke: '#00ff00', - others: ['fire', 'water', 'earth'] - }, - gray: { - name: 'gray', - faIcon: 'circle', - glyph: gray, - get pathData() { return tenTenPathData('gray') }, - pathWidth: parseInt(gray.getAttribute('horiz-adv-x')), - pathHeight: parseInt(gray.getAttribute('vert-adv-y')), - fill: '#dddddd', - stroke: '#5A5A5A', - others: ['gray', 'gray', 'gray'] - }, - unknown: { - name: 'unknown', - faIcon: 'question', - glyph: question, - get pathData() { return tenTenPathData('question') }, - pathWidth: parseInt(question.getAttribute('horiz-adv-x')), - pathHeight: parseInt(question.getAttribute('vert-adv-y')), - fill: 'pink', - stroke: 'violet', - others: ['question', 'question', 'question'] - } -}; - -function forElement(element) { - const cleanElement = (element || '').toLowerCase(); - return elementInfo[cleanElement] || elementInfo.unknown; -} - -class SVG { - - static outerSVG(children, innerBounds, outerBounds, attrs = '', style = '') { - return `${children}`; - } - - static inlineSVG(children, bounds = lively.rect(0, 0, 10, 10), attrs = '', style = '') { - return `${children}`; - } - - /*MD ## Basic Shapes MD*/ - static circleRing(center, innerRadius, outerRadius, attrs) { - return `` - } - - static circle(center, radius, attrs) { - return `` - } - - /*MD ## Icons MD*/ - static elementGlyph(element, center, radius, attrs) { - const pathData = PathDataScaleCache.getPathData(element, lively.pt(2 * radius, 2 * radius)); - return `` - } - - static elementSymbol(element, center, radius) { - const { name: elementName, fill, stroke } = forElement(element); - const innerRadius = .9 * radius; - return `${SVG.circle(center, innerRadius, `fill="${fill}"`)} -${SVG.elementGlyph(elementName, center, innerRadius, `fill="${stroke}"`)} - ${SVG.circleRing(center, innerRadius, radius, `fill="${stroke}"`)}` - } -} +import { fire, water, earth, wind, gray, question, PathDataScaleCache, tenTenPathData, elementInfo, forElement, SVG } from './ubg-utils.js'; const castIcon = do { const size = 100; @@ -219,6 +62,7 @@ ${mainElements}`, bounds); } +// #Debug function previewSVG(svg) { const hedronTemp = document.getElementById(svg.id) if (hedronTemp) { @@ -595,7 +439,7 @@ ${SVG.elementSymbol(others[2], lively.pt(12.5, 8.5), 1.5)}`, lively.rect(0, 0, 1 /*MD ## --- MD*/ // #important - static async renderRuleText(cardEditor, cardDesc) { + static async renderRuleText(rulesTextElement, cardDesc) { let printedRules = cardDesc.getText() || ''; // old big cast icon with small tap @@ -617,7 +461,7 @@ ${SVG.elementSymbol(others[2], lively.pt(12.5, 8.5), 1.5)}`, lively.rect(0, 0, 1 printedRules = this.parseEffectsAndLists(printedRules); - printedRules = this.renderReminderText(printedRules, cardEditor, cardDesc) + printedRules = this.renderReminderText(printedRules, rulesTextElement, cardDesc) printedRules = printedRules.replace(/\b(?:\d|-|\+)*x(?:\d|-|\+|vp)*\b/gmi, function replacer(match, innerText, offset, string, groups) { // find the bigger pattern, then just replace all x instead of reconstructing its surrounding characters @@ -641,29 +485,11 @@ ${SVG.elementSymbol(others[2], lively.pt(12.5, 8.5), 1.5)}`, lively.rect(0, 0, 1 printedRules = printedRules.replace(/\bdaybreak\b/gmi, ''); printedRules = printedRules.replace(/\bnightfall\b/gmi, ''); - // - printedRules = printedRules.replace(/\bcardname(?::(\d+))?/gmi, (match, cardId, offset, string, groups) => { - // lor blue card name #519ff1 - // #ffe967 - // #f8d66a - // #de9b75 - function highlightName(name) { - return `${name}` - } - if (!cardId) { - return highlightName(cardEditor.getNameFromCard(cardDesc)) - } - const card = cardEditor.cards.find(card => card.getId() + '' === cardId) - if (card) { - return highlightName(cardEditor.getNameFromCard(card)) - } else { - return `unknown id: ${cardId}` - } - }); - + printedRules = this.renderCardnames(printedRules, rulesTextElement, cardDesc) + printedRules = printedRules.replace(/actionFree/gmi, () => this.chip('free')); printedRules = printedRules.replace(/actionMulti/gmi, () => this.chip('multi')); - printedRules = this.renderXPerTurnOrGame(printedRules, cardEditor, cardDesc); + printedRules = this.renderXPerTurnOrGame(printedRules); printedRules = printedRules.replace(/actionMain:?/gmi, () => { return '' @@ -689,10 +515,63 @@ ${SVG.elementSymbol(others[2], lively.pt(12.5, 8.5), 1.5)}`, lively.rect(0, 0, 1 return ""; }); - this.renderToDoc(cardEditor, printedRules, cardDesc) + this.renderToDoc(rulesTextElement, printedRules, cardDesc) + } + + static renderCardnames(printedRules, rulesTextElement, cardDesc) { + return printedRules.replace(/\bcardname(?::(\d+))?/gmi, (match, cardId, offset, string, groups) => { + // lor blue card name #519ff1 + // #ffe967 + // #f8d66a + // #de9b75 + function getNameFromCard(cardDesc) { + const currentVersion = cardDesc.versions.last; + return currentVersion.name || '' + } + + function highlightName(name) { + return `${name}` + } + + function findParentWith(condition) { + return lively.findParent(rulesTextElement, condition, { deep: true }); + } + + function guessOwnCardname() { + // find a parent with .card + const parentWithCard = findParentWith(element => element.card); + if (!parentWithCard) { + return `no parent for rules with cardname` + } + + return highlightName(getNameFromCard(parentWithCard.card)) + } + + function guessCardnameFromId(cardId) { + // find a parent with .cards + const parentWithCards = findParentWith(element => Array.isArray(element.cards)); + if (!parentWithCards) { + return `no parent for rules with card id: ${cardId}` + } + + // search card with id + const card = parentWithCards.cards.find(card => card.getId() + '' === cardId) + if (!card) { + return `unknown card id: ${cardId} in parent` + } + + return highlightName(getNameFromCard(card)) + } + + if (!cardId) { + return guessOwnCardname() + } else { + return guessCardnameFromId(cardId) + } + }); } - static renderXPerTurnOrGame(printedRules, cardEditor, cardDesc) { + static renderXPerTurnOrGame(printedRules) { return printedRules.replace(/\b((?:\d+)?(?:hedron)?)\/(game|turn)\b/gmi, (match, times, type, string, groups) => { let color = 'black'; if (type === 'turn') { @@ -1254,8 +1133,6 @@ ${textToPrint}`, undefined, undefined, 'transform:scale(1);'); } } -const OUTSIDE_BORDER_ROUNDING = lively.pt(3, 3) - export default class UbgRulesText extends Morph { applyRulesText(cardDesc) { @@ -1301,11 +1178,6 @@ export default class UbgRulesText extends Morph { return ['#ffffff', '#888888', BOX_FILL_OPACITY]; } - getNameFromCard(cardDesc) { - const currentVersion = cardDesc.versions.last; - return currentVersion.name || '' - } - getElementsFromCard(cardDesc, grayIfEmpty) { const element = cardDesc.getElement(); if (Array.isArray(element)) { @@ -1317,24 +1189,6 @@ export default class UbgRulesText extends Morph { } } - /*MD ## Debugging MD*/ - debugPoint(pt, color = 'red') { - this.content.append(
); - } - - debugRect(rect, color = 'red') { - return this.roundedRect(rect, 'transparent', color, 1 / 3.7795275591, 0); - } - /*MD ## Rendering Helpers MD*/ line(start, end, color, width) { const startX = start.x; @@ -1383,600 +1237,17 @@ background: ${color}; return `color-mix(in srgb, ${color} ${opacity * 100}%, transparent)` } - /*MD ## Background Images MD*/ - filePathForBackgroundImage(cardDesc, assetsInfo) { - const id = cardDesc.id; - if (id) { - const possibleFileNames = ['jpg', 'png'].map(ending => `${id}.${ending}`); - const foundEntry = assetsInfo.find(entry => entry.type === 'file' && possibleFileNames.includes(entry.name)); - if (foundEntry) { - return this.assetsFolder + foundEntry.name; - } - } - - const typeString = cardDesc.getType() && cardDesc.getType().toLowerCase && cardDesc.getType().toLowerCase() - const defaultFiles = { - gadget: 'default-gadget.jpg', - character: 'default-character.jpg', - spell: 'default-spell.jpg' - }; - return this.assetsFolder + (defaultFiles[typeString] || 'default.jpg'); - } - - async loadImage(filePath) { - return new Promise((resolve, reject) => { - const image = new Image(); - image.addEventListener('load', resolve); - image.addEventListener('error', reject); - image.src = filePath; - }); - } - - /*MD ## Rendering MD*/ - async renderMagicStyle(cardDesc, outsideBorder, assetsInfo) { - const [BOX_FILL_COLOR, BOX_STROKE_COLOR, BOX_FILL_OPACITY] = this.colorsForCard(cardDesc); - - // black border - this.roundedRect(outsideBorder, 'black', 'transparent', '0', '0') - - // innerBorder - const INNER_INSET = 3; - const innerBorder = outsideBorder.insetBy(INNER_INSET); - - // card image - const filePath = this.filePathForBackgroundImage(cardDesc, assetsInfo); - const newBG =
; - this.content.append(newBG) - - // title bar - const TITLE_BAR_HEIGHT = 7; - const titleBar = innerBorder.insetBy(1); - titleBar.height = TITLE_BAR_HEIGHT; - const TITLE_BAR_BORDER_WIDTH = 0.200025; - this.roundedRect(titleBar, this.colorWithOpacity(BOX_FILL_COLOR, .5), BOX_STROKE_COLOR, TITLE_BAR_BORDER_WIDTH, 1) - - // card name - { - const pos = titleBar.leftCenter().addX(2); - const fontSize = .6 * titleBar.height::mmToPoint(); - - const cardName = this.getNameFromCard(cardDesc); - this.content.append({cardName}) - } - - // cost - const COIN_RADIUS = 4; - const coinPos = titleBar.bottomLeft().addY(1).addXY(COIN_RADIUS, COIN_RADIUS); - this.renderCost(cardDesc, coinPos, COIN_RADIUS) - - // type & elements - const typePos = coinPos.addY(COIN_RADIUS * 1.5) - this.renderType(cardDesc, typePos, BOX_FILL_COLOR, BOX_FILL_OPACITY) - - // rule box - const ruleBox = outsideBorder.copy() - const height = outsideBorder.height * .4; - ruleBox.y = ruleBox.bottom() - height; - ruleBox.height = height; - // this.debugRect(ruleBox) - const ruleBoxInset = 1 + INNER_INSET; - const ruleTextInset = 2; - await this.renderRuleText(cardDesc, outsideBorder, ruleBox, { - insetBoxBy: ruleBoxInset, - insetTextBy: ruleTextInset, - innerStrokeColor: BOX_STROKE_COLOR, - innerFillColor: BOX_FILL_COLOR, - innerFillOpacity: BOX_FILL_OPACITY, - outerStrokeColor: 'transparent', - outerFillColor: 'transparent', - outerFillOpacity: 0, - }); - - // tags - const tagsAnchor = titleBar.bottomRight().addY(1); - this.renderTags(cardDesc, tagsAnchor, outsideBorder) - } - - async renderFullBleedStyle(cardDesc, outsideBorder, assetsInfo) { - const type = cardDesc.getType(); - const typeString = type && type.toLowerCase && type.toLowerCase() || ''; - - if (typeString === 'spell') { - await this.renderSpell(cardDesc, outsideBorder, assetsInfo) - } else if (typeString === 'gadget') { - await this.renderGadget(cardDesc, outsideBorder, assetsInfo) - } else if (typeString === 'character') { - await this.renderCharacter(cardDesc, outsideBorder, assetsInfo) - } else { - await this.renderMagicStyle(cardDesc, outsideBorder, assetsInfo) - } - - this.renderIsBad(cardDesc, outsideBorder) - } - - maskedCircle(outsideBorder, center, radius, strokeWidth, fillColor, fillOpacity, strokeColor) { - strokeWidth *= 2; // half covered by mask - - const svg = - - - - - - - - - ; - - this.content.insertAdjacentHTML('beforeend', svg.outerHTML) - - } - /*MD ### Rendering Card Types MD*/ - // #important - async renderSpell(cardDesc, outsideBorder, assetsInfo) { - const [BOX_FILL_COLOR, BOX_STROKE_COLOR, BOX_FILL_OPACITY] = this.colorsForCard(cardDesc); - - // spell circle - { - const CIRCLE_BORDER = -3; - const radius = (outsideBorder.width - CIRCLE_BORDER) / 2; - const center = outsideBorder.center().withY(outsideBorder.top() + CIRCLE_BORDER + radius) - const strokeWidth = 1; - this.maskedCircle(outsideBorder, center, radius, strokeWidth, BOX_FILL_COLOR, BOX_FILL_OPACITY, BOX_STROKE_COLOR) - } - - // innerBorder - const innerBorder = outsideBorder.insetBy(3); - - // title - const TITLE_BAR_HEIGHT = 7; - const COST_COIN_RADIUS = 4; - const COST_COIN_MARGIN = 2; - - const titleBorder = innerBorder.insetBy(1); - titleBorder.height = TITLE_BAR_HEIGHT; - - this.renderTitleBarAndCost(cardDesc, titleBorder, COST_COIN_RADIUS, COST_COIN_MARGIN) - - // rule box - const ruleBox = outsideBorder.copy() - const height = outsideBorder.height * .3; - ruleBox.y = ruleBox.bottom() - height; - ruleBox.height = height; - // this.debugRect(ruleBox) - - // rule text - const RULE_BOX_INSET = 1; - const RULE_TEXT_INSET = 1; - await this.renderRuleText(cardDesc, outsideBorder, ruleBox, { - insetBoxBy: RULE_BOX_INSET, - insetTextBy: RULE_TEXT_INSET, - outerStrokeColor: 'transparent', - outerFillColor: 'transparent', - outerFillOpacity: 0, - }); - - // tags - const tagsAnchor = lively.pt(titleBorder.right(), titleBorder.bottom()).addXY(-RULE_TEXT_INSET, 1); - this.renderTags(cardDesc, tagsAnchor, outsideBorder) - } - - // #important - async renderGadget(cardDesc, outsideBorder, assetsInfo) { - const [BOX_FILL_COLOR, BOX_STROKE_COLOR, BOX_FILL_OPACITY] = this.colorsForCard(cardDesc); - - // innerBorder - const innerBorder = outsideBorder.insetBy(3); - // this.roundedRect(innerBorder, 'steelblue', 'red', 3, 0) - - // top box - const topBox = outsideBorder.copy() - { - topBox.height = 13; - const box = this.roundedRect(topBox, this.colorWithOpacity(BOX_FILL_COLOR, BOX_FILL_OPACITY), 'transparent', 0, 0); - box.style.backdropFilter = 'blur(4px)'; - - this.line(topBox.bottomLeft(), topBox.bottomRight(), BOX_STROKE_COLOR, 1) - } - - // title - { - const TITLE_BAR_HEIGHT = 7; - const COST_COIN_RADIUS = 4; - const COST_COIN_MARGIN = 2; - - const titleBorder = innerBorder.insetBy(1); - titleBorder.height = TITLE_BAR_HEIGHT; - - this.renderTitleBarAndCost(cardDesc, titleBorder, COST_COIN_RADIUS, COST_COIN_MARGIN) - } - - // rule box border calc - const ruleBox = outsideBorder.copy() - const height = outsideBorder.height * .4; - ruleBox.y = ruleBox.bottom() - height; - ruleBox.height = height; - // this.debugRect(ruleBox) - - // rule text - const RULE_BOX_INSET = 1; - const RULE_TEXT_INSET = 1; - await this.renderRuleText(cardDesc, outsideBorder, ruleBox, { - insetBoxBy: RULE_BOX_INSET, - insetTextBy: RULE_TEXT_INSET, - outerStrokeColor: BOX_STROKE_COLOR, - outerFillColor: BOX_FILL_COLOR, - outerFillOpacity: BOX_FILL_OPACITY, - }); - - // tags - const tagsAnchor = lively.pt(topBox.right(), topBox.bottom()).addXY(-RULE_TEXT_INSET, 1); - this.renderTags(cardDesc, tagsAnchor, outsideBorder) - } - - // #important - async renderCharacter(cardDesc, outsideBorder, assetsInfo) { - const [BOX_FILL_COLOR, BOX_STROKE_COLOR, BOX_FILL_OPACITY] = this.colorsForCard(cardDesc); - - // Zohar design - { - const ZOHAR_DESIGN_BORDER_WIDTH = .5; - [[outsideBorder.topLeft(), lively.pt(1, 0)], [outsideBorder.topRight(), lively.pt(-1, 0)]].forEach(([startingPt, direction]) => { - const dirX = direction.x; - startingPt = startingPt.subY(5) - const topMost = startingPt.addXY(dirX*8, 0); - const triangleTop = topMost.addXY(0, 15 + 5); - const triangleOuter = triangleTop.addXY(-dirX*15, 15); - const triangleBottom = triangleOuter.addXY(dirX*15, 15); - const bottom = triangleBottom.addXY(0, 100); - const bottomOuter = bottom.addXY(-dirX*10, 0); - const diamondPoints = `${startingPt.x} ${startingPt.y}, ${topMost.x} ${topMost.y}, ${triangleTop.x} ${triangleTop.y}, ${triangleOuter.x} ${triangleOuter.y}, ${triangleBottom.x} ${triangleBottom.y}, ${bottom.x} ${bottom.y}, ${bottomOuter.x} ${bottomOuter.y}`; - - const svg = - - ; - - this.content.insertAdjacentHTML('beforeend', svg.outerHTML) - }) - } - - // innerBorder - const innerBorder = outsideBorder.insetBy(3); - // this.roundedRect(innerBorder, 'steelblue', 'red', 3, 0) - - // title - const TITLE_BAR_HEIGHT = 7; - const COST_COIN_RADIUS = 4; - const COST_COIN_MARGIN = 2; - - const titleBorder = innerBorder.insetBy(1); - titleBorder.height = TITLE_BAR_HEIGHT; - - this.renderTitleBarAndCost(cardDesc, titleBorder, COST_COIN_RADIUS, COST_COIN_MARGIN) - - // rule box border calc - const ruleBox = outsideBorder.copy() - const height = outsideBorder.height * .4; - ruleBox.y = ruleBox.bottom() - height; - ruleBox.height = height; - - // rule text - const RULE_BOX_INSET = 1; - const RULE_TEXT_INSET = 1; - await this.renderRuleText(cardDesc, outsideBorder, ruleBox, { - insetBoxBy: RULE_BOX_INSET, - insetTextBy: RULE_TEXT_INSET, - outerStrokeColor: BOX_STROKE_COLOR, - outerFillColor: BOX_FILL_COLOR, - outerFillOpacity: BOX_FILL_OPACITY, - }); - - // tags - const tagsAnchor = lively.pt(titleBorder.right(), titleBorder.bottom()).addXY(-RULE_TEXT_INSET, 1); - this.renderTags(cardDesc, tagsAnchor, outsideBorder) - } - /*MD ### Rendering Card Components MD*/ - renderTitleBarAndCost(cardDesc, border, costCoinRadius, costCoinMargin) { - const TITLE_BAR_BORDER_WIDTH = 0.200025; - - const titleBar = border.copy() - const coinLeftCenter = titleBar.leftCenter() - const spacingForCoin = 2*costCoinRadius + costCoinMargin - titleBar.x += spacingForCoin - titleBar.width -= spacingForCoin - - const [BOX_FILL_COLOR, BOX_STROKE_COLOR, BOX_FILL_OPACITY] = this.colorsForCard(cardDesc); - - // title space - // this.roundedRect(border, this.colorWithOpacity('#ffffff', .5), 'transparent', 0, 0) - this.roundedRect(titleBar, this.colorWithOpacity('#ffffff', .5), 'transparent', 0, 1) - - // title bar - this.roundedRect(titleBar, this.colorWithOpacity(BOX_FILL_COLOR, .5), BOX_STROKE_COLOR, TITLE_BAR_BORDER_WIDTH, 1) - - // card name - { - const pos = titleBar.leftCenter().addX(2); - const fontSize = .6 * titleBar.height::mmToPoint(); - this.content.append({this.getNameFromCard(cardDesc)}) - } - - const coinCenter = coinLeftCenter.addX(costCoinRadius); - this.renderInHandSymbols(cardDesc, border, costCoinRadius, costCoinMargin, coinCenter) - } - - renderInHandSymbols(cardDesc, border, costCoinRadius, costCoinMargin, coinCenter) { - let currentCenter = coinCenter; - - // cost - this.renderCost(cardDesc, currentCenter, costCoinRadius) - - if ((cardDesc.getType() || '').toLowerCase() !== 'character') { - // vp - currentCenter = currentCenter.addY(costCoinRadius * 2.75); - this.renderBaseVP(cardDesc, currentCenter, costCoinRadius) - - // element (list) - currentCenter = currentCenter.addY(costCoinRadius * 2.75); - const elementListDirection = 1; - currentCenter = this.renderElementList(cardDesc, currentCenter, costCoinRadius, elementListDirection) - } else { - currentCenter = currentCenter.addY(costCoinRadius * 1); - } - - // type - currentCenter = currentCenter.addY(costCoinRadius * .75) - const [BOX_FILL_COLOR, BOX_STROKE_COLOR, BOX_FILL_OPACITY] = this.colorsForCard(cardDesc); - this.renderType(cardDesc, currentCenter, BOX_FILL_COLOR, BOX_FILL_OPACITY) - } - - renderElementList(cardDesc, pos, radius, direction) { - const elements = this.getElementsFromCard(cardDesc, true); - for (let element of elements) { - this.renderElementSymbol(element, pos, radius) - pos = pos.addY(direction * radius * .75); - } - return pos.addY(direction * radius * .25); - } - - renderCost(cardDesc, pos, coinRadius) { - const costSize = coinRadius / 3; - - const costDesc = cardDesc.getCost(); - const cost = Array.isArray(costDesc) ? costDesc.first : costDesc; - - const coinCenter = pos; - const strokeWidth = .2 * costSize; - const size = `${2 * coinRadius}mm`; - const svg = ; - this.content.insertAdjacentHTML('beforeend', svg.outerHTML) - -// { -// const r = ; -// const str = {r}; -// this.content.append(str); -// } - - this.renderIconText(coinCenter, costSize, cost, CSS_FONT_FAMILY_CARD_COST) - } - - renderBaseVP(cardDesc, pos, coinRadius) { - const costSize = coinRadius / 3; - - const vp = cardDesc.getBaseVP() || 0; - const fillColor = vp === 0 ? VP_FILL_ZERO : VP_FILL - const fillOpacity = .9 - const strokeColor = vp === 0 ? VP_STROKE_ZERO : VP_STROKE - const strokeWidth = .2 * costSize; - - const iconCenter = pos; - - // diamond shape - const diagonal = coinRadius * .9 * Math.sqrt(2) - const down = pos.addY(diagonal) - const left = pos.addX(-diagonal) - const up = pos.addY(-diagonal) - const right = pos.addX(diagonal) - const diamondPoints = `${down.x} ${down.y}, ${left.x} ${right.y}, ${up.x} ${up.y}, ${right.x} ${right.y}`; - - const svg = - - ; - this.content.insertAdjacentHTML('beforeend', svg.outerHTML) - this.renderIconText(iconCenter, costSize, vp, CSS_FONT_FAMILY_CARD_VP) - } - - renderIconText(centerPos, size, text, font) { - if (text === undefined) { - return - } - - const iconText = {'' + text}; - - this.content.append(iconText) - } - // #important async renderRuleText(cardDesc) { lively.notify('render rules text') return RuleTextRenderer.renderRuleText(this, cardDesc) } - renderType(cardDesc, anchorPt, color, opacity) { - // function curate() { - // return this.toLower().upperFirst(); - // } - // function prepend(other) { - // return other + ' ' + this; - // } - // const element = cardDesc.getElement(); - let fullText = (cardDesc.getType() || '').toLower().upperFirst() - // if (Array.isArray(element)) { - // element.forEach(element => { - // fullText = fullText::prepend(element::curate()) - // }) - // } else if (element) { - // fullText = fullText::prepend(element::curate()) - // } - - this.content.append({fullText}); - } - - renderQRCode(cardDesc, qrAnchor, outsideBorder) { - return; - const canvas = ; - const { QrCode } = qrcodegen - var qr = QrCode.encodeText("" + cardDesc.getId(), QrCode.Ecc.HIGH); - - function drawCanvas(qr, scale, border, lightColor, darkColor, canvas) { - if (scale <= 0 || border < 0) - throw new RangeError("Value out of range"); - const width = (qr.size + border * 2) * scale; - canvas.width = width; - canvas.height = width; - let ctx = canvas.getContext("2d"); - for (let y = -border; y < qr.size + border; y++) { - for (let x = -border; x < qr.size + border; x++) { - ctx.fillStyle = qr.getModule(x, y) ? darkColor : lightColor; - ctx.fillRect((x + border) * scale, (y + border) * scale, scale, scale); - } - } - } - - const [BOX_FILL_COLOR, BOX_STROKE_COLOR, BOX_FILL_OPACITY] = this.colorsForCard(cardDesc); - - drawCanvas(qr, 3, 1, "white", BOX_STROKE_COLOR, canvas); - this.content.append(canvas) - } - - renderTags(cardDesc, tagsAnchor, outsideBorder) { - const tags = cardDesc.getTags().sortBy(i => i, true).map(tag =>
#{tag}
); - const FONT_SIZE = 7; - - this.content.append({...tags}) - } - - renderElementSymbol(element, pos, radius) { - const innerBounds = lively.rect(0, 0, 10, 10) - const svgInnerPos = innerBounds.center(); - const svgInnerRadius = innerBounds.width / 2; - const outerBounds = lively.rect(pos.x - radius, pos.y - radius, radius * 2, radius * 2); - const yourSvgString = SVG.outerSVG(SVG.elementSymbol(element, svgInnerPos, svgInnerRadius), innerBounds, outerBounds); - - this.content.insertAdjacentHTML('beforeend', yourSvgString) - } - + /*MD ## Basic Web Components MD*/ get content() { return this.get('#content'); } - - renderIsBad(cardDesc, outsideBorder) { - const slash = (color, width=2, offset=lively.pt(0,0)) => { - const start = outsideBorder.topRight().addPt(offset); - const end = outsideBorder.bottomLeft().addPt(offset); - this.line(start, end, color, width) - } - - if (cardDesc.hasTag('duplicate')) { - slash('#bbbbbb', 2, lively.pt(-3, -3)) - } - if (cardDesc.hasTag('unfinished')) { - slash('#888888', 2, lively.pt(-2, -2)) - } - if (cardDesc.hasTag('bad')) { - slash('#ff0000', 2) - } - if (cardDesc.hasTag('deprecated')) { - slash('#ff00ff', 2, lively.pt(2, 2)) - } - if (cardDesc.getRating() === 'remove') { - slash('#999999', 5, lively.pt(-5, -5)) - } - } - - /*MD ## Basic Web Components MD*/ initialize() { if (this.hasAttribute('for-preload')) { return; @@ -1984,20 +1255,31 @@ font-family: ${CSS_FONT_FAMILY_UNIVERS_55}; this.windowTitle = "UbgCard"; - this._attrObserver = new MutationObserver(mutations => { + this._mutationObserver = new MutationObserver(mutations => { + let rerenderRequired = false; mutations.forEach(mutation => { - let rerenderRequired = false; - if (mutation.type == "attributes") { - // console.log("observation", mutation.attributeName,mutation.target.getAttribute(mutation.attributeName)); + if (mutation.type === 'characterData') { + lively.notify('Text content changed to:' + mutation.target.data); + rerenderRequired = true; + } else if (mutation.type === 'childList') { + lively.notify('Child nodes changed:' + mutation); + rerenderRequired = true; + } else if (mutation.type == "attributes") { rerenderRequired = true; this.attributeChangedCallback(mutation.attributeName, mutation.oldValue, mutation.target.getAttribute(mutation.attributeName)); } - if (rerenderRequired) { - this.rerender() - } }); + + if (rerenderRequired) { + this.rerender() + } + }); + this._mutationObserver.observe(this, { + attributes: true, + characterData: true, + subtree: true, + childList: true }); - this._attrObserver.observe(this, { attributes: true }); } rerender() { @@ -2009,7 +1291,7 @@ font-family: ${CSS_FONT_FAMILY_UNIVERS_55}; } attributeChangedCallback(name, oldValue, newValue) { - lively.notify(`${oldValue} -> ${newValue}`, name) + lively.notify(`${oldValue} -> ${newValue}`, 'ATTR ' + name) } /*MD ## External API MD*/ @@ -2025,15 +1307,6 @@ font-family: ${CSS_FONT_FAMILY_UNIVERS_55}; return this.cards = cards; } - async render() { - this._checkOptionsSet() - const assetsInfo = await this.fetchAssetsInfo(); - const outsideBorder = lively.pt(0,0).extent(POKER_CARD_SIZE_MM); - const cardToPrint = this.card; - lively.error('FULL RENDER') - await this.renderFullBleedStyle(cardToPrint, outsideBorder, assetsInfo) - } - _checkOptionsSet() { if (!this.src) { lively.warn('cannot render: "src" not set') diff --git a/src/components/widgets/ubg-rules-text.js.l4a b/src/components/widgets/ubg-rules-text.js.l4a deleted file mode 100644 index 98cfef411..000000000 --- a/src/components/widgets/ubg-rules-text.js.l4a +++ /dev/null @@ -1,4 +0,0 @@ -{"type":"Reference","version":"daac81d04ebd730ca309649f1808b53986b50d7b","content":"/* global globalThis */\n\nimport Morph from 'src/components/widgets/lively-morph.js';\n\nimport { Point } from 'src/client/graphics.js'\n\nimport paper from 'src/client/paperjs-wrapper.js'\n// import 'https://lively-kernel.org/lively4/ubg-assets/load-assets.js';\n\nimport qrcodegen from 'https://lively-kernel.org/lively4/aexpr/src/external/qrcodegen.js'\n\nconst POKER_CARD_SIZE_INCHES = lively.pt(2.5, 3.5);\nconst POKER_CARD_SIZE_MM = POKER_CARD_SIZE_INCHES.scaleBy(25.4);\n\nconst CSS_FONT_FAMILY_BEAUFORT_FOR_LOL_BOLD = \"Beaufort for LOL Bold\"\nconst CSS_FONT_FAMILY_BEAUFORT_FOR_LOL_REGULAR = \"Beaufort for LOL Regular\"\nconst CSS_FONT_FAMILY_UNIVERS_55 = \"Univers 55\"\nconst CSS_FONT_FAMILY_UNIVERS_45_LIGHT_ITALIC = \"Univers 45 Light Italic\"\n\n// Card name, card cost, card stats -- Beaufort for LOL Bold\nconst CSS_FONT_FAMILY_CARD_NAME = CSS_FONT_FAMILY_BEAUFORT_FOR_LOL_BOLD\nconst CSS_FONT_FAMILY_CARD_COST = CSS_FONT_FAMILY_BEAUFORT_FOR_LOL_BOLD\nconst CSS_FONT_FAMILY_CARD_VP = CSS_FONT_FAMILY_BEAUFORT_FOR_LOL_BOLD\nconst CSS_FONT_FAMILY_CARD_TYPE = CSS_FONT_FAMILY_BEAUFORT_FOR_LOL_REGULAR\n// #TODO: Card group name (ELITE, SPIDER, YETI, etc.) -- Univers 59 // #BROKEN?? #TODO\n\n// Card description -- Univers 55\nconst CSS_FONT_FAMILY_CARD_TEXT = CSS_FONT_FAMILY_UNIVERS_55\n\nfunction identity(value) {\n return value;\n}\n\n/* `this` is a lively.rect */\nfunction xYWidthHeight() {\n return [this.x, this.y, this.width, this.height];\n}\n\n/* `this` is a Number */\nfunction pointToMM() {\n return this / 2.835;\n}\n\n/* `this` is a Number */\nfunction mmToPoint() {\n return this * 2.835;\n}\n\nconst fire = ;\nconst water = ;\nconst earth = ;\nconst wind = ;\nconst gray = ;\nconst question = ;\n\nclass PathDataScaleCache {\n static getPathData(element, size = lively.pt(10, 10)) {\n if (!this.cache) {\n this.cache = {}\n }\n \n const key = `${element}-${size.x}-${size.y}`;\n if (!this.cache[key]) {\n // lively.notify(`${element}-${size.x}-${size.y}`, 'cache miss')\n this.cache[key] = this._scalePathData(element, size)\n }\n \n return this.cache[key]\n }\n \n static _scalePathData(element, size) {\n const { glyph } = forElement(element);\n const path = new paper.Path(glyph.getAttribute('d'));\n\n path.scale(1, -1);\n\n const margin = size.scaleBy(0.1);\n const boundingRect = new paper.Path.Rectangle({\n point: margin.toPair(),\n size: size.subPt(margin.scaleBy(2)).toPair()\n });\n path.fitBounds(boundingRect.bounds);\n\n return path.pathData;\n }\n}\n\nfunction tenTenPathData(element) {\n return PathDataScaleCache.getPathData(element, lively.pt(10, 10));\n}\n\nconst elementInfo = {\n fire: {\n name: 'fire',\n faIcon: 'book',\n glyph: fire,\n get pathData() { return tenTenPathData('fire') },\n pathWidth: parseInt(fire.getAttribute('horiz-adv-x')),\n pathHeight: parseInt(fire.getAttribute('vert-adv-y')),\n fill: '#ffbbbb',\n stroke: '#ff0000',\n others: ['water', 'earth', 'wind']\n },\n water: {\n name: 'water',\n faIcon: 'droplet',\n glyph: water,\n get pathData() { return tenTenPathData('water') },\n pathWidth: parseInt(water.getAttribute('horiz-adv-x')),\n pathHeight: parseInt(water.getAttribute('vert-adv-y')),\n fill: '#8888ff',\n stroke: '#0000ff',\n others: ['fire', 'earth', 'wind']\n },\n earth: {\n name: 'earth',\n faIcon: 'mountain',\n glyph: earth,\n get pathData() { return tenTenPathData('earth') },\n pathWidth: parseInt(earth.getAttribute('horiz-adv-x')),\n pathHeight: parseInt(earth.getAttribute('vert-adv-y')),\n fill: 'rgb(255, 255, 183)',\n stroke: '#ffd400',\n others: ['fire', 'water', 'wind']\n },\n wind: {\n name: 'wind',\n faIcon: 'cloud',\n glyph: wind,\n get pathData() { return tenTenPathData('wind') },\n pathWidth: parseInt(wind.getAttribute('horiz-adv-x')),\n pathHeight: parseInt(wind.getAttribute('vert-adv-y')),\n fill: '#bbffbb',\n stroke: '#00ff00',\n others: ['fire', 'water', 'earth']\n },\n gray: {\n name: 'gray',\n faIcon: 'circle',\n glyph: gray,\n get pathData() { return tenTenPathData('gray') },\n pathWidth: parseInt(gray.getAttribute('horiz-adv-x')),\n pathHeight: parseInt(gray.getAttribute('vert-adv-y')),\n fill: '#dddddd',\n stroke: '#5A5A5A',\n others: ['gray', 'gray', 'gray']\n },\n unknown: {\n name: 'unknown',\n faIcon: 'question',\n glyph: question,\n get pathData() { return tenTenPathData('question') },\n pathWidth: parseInt(question.getAttribute('horiz-adv-x')),\n pathHeight: parseInt(question.getAttribute('vert-adv-y')),\n fill: 'pink',\n stroke: 'violet',\n others: ['question', 'question', 'question']\n }\n};\n\nfunction forElement(element) {\n const cleanElement = (element || '').toLowerCase();\n return elementInfo[cleanElement] || elementInfo.unknown;\n}\n\nclass SVG {\n\n static outerSVG(children, innerBounds, outerBounds, attrs = '', style = '') {\n return `${children}`;\n }\n\n static inlineSVG(children, bounds = lively.rect(0, 0, 10, 10), attrs = '', style = '') {\n return `${children}`;\n }\n\n /*MD ## Basic Shapes MD*/\n static circleRing(center, innerRadius, outerRadius, attrs) {\n return ``\n }\n\n static circle(center, radius, attrs) {\n return ``\n }\n\n /*MD ## Icons MD*/\n static elementGlyph(element, center, radius, attrs) {\n const pathData = PathDataScaleCache.getPathData(element, lively.pt(2 * radius, 2 * radius));\n return ``\n }\n \n static elementSymbol(element, center, radius) {\n const { name: elementName, fill, stroke } = forElement(element);\n const innerRadius = .9 * radius;\n return `${SVG.circle(center, innerRadius, `fill=\"${fill}\"`)}\n${SVG.elementGlyph(elementName, center, innerRadius, `fill=\"${stroke}\"`)}\n ${SVG.circleRing(center, innerRadius, radius, `fill=\"${stroke}\"`)}`\n }\n}\n\nconst castIcon = do {\n const size = 100;\n const bounds = lively.rect(0, 0, size, size)\n const innerBounds = bounds.insetBy(5);\n \n const innerRadius = innerBounds.width / 2;\n const outerCircle = SVG.circleRing(bounds.center(), innerRadius, bounds.width / 2, `fill=\"#7A7A7A\"`);\n \n const sqrt2 = 2**.5\n const radius = innerRadius * 1 / (sqrt2 + 1);\n const distToMiddle = innerRadius * sqrt2 / (sqrt2 + 1);\n const elements = ['water', 'earth', 'fire', 'wind'];\n const mainElements = elements.map((element, i) => {\n const center = bounds.center().addPt(Point.polar(distToMiddle, Math.PI / 2 * i));\n return SVG.elementSymbol(element, center, radius)\n }).join('\\n');\n\n SVG.inlineSVG(`${outerCircle}\n${mainElements}`, bounds);\n}\n\n\nfunction previewSVG(svg) {\n const hedronTemp = document.getElementById(svg.id)\n if (hedronTemp) {\n hedronTemp.remove()\n }\n document.body.insertAdjacentHTML(\"afterbegin\", svg.outerHTML)\n}\n\nconst hedronSVG = do {\n function point(pt) {\n return `${pt.x} ${pt.y}`;\n }\n\n const topB = lively.pt(11.5, 14.401);\n const topL = topB.addXY(-11.5, -4.758);\n const topT = topL.addXY(11.5, -9.66);\n const topR = topT.addXY(11.5, 9.66);\n const topB2 = topR.addXY(-11.5, 4.758);\n const topLeftData = `M${point(topB)} L ${point(topL)} ${point(topT)} z`;\n const topRightData = `M${point(topB)} L ${point(topT)} ${point(topR)} z`;\n\n const bottomB = lively.pt(11.5, 16.036);\n const bottomL = bottomB.addXY(-11.5, -5.050);\n const bottomT = bottomL.addXY(11.5, 12.030);\n const bottomR = bottomT.addXY(11.5, -12.030);\n const bottomB2 = bottomR.addXY(-11.5, 5.050);\n const bottomLeftData = `M${point(bottomB)} L ${point(bottomL)} ${point(bottomT)} z`;\n const bottomRightData = `M${point(bottomB)} L ${point(bottomT)} ${point(bottomR)} ${point(bottomB2)} z`;\n \n const greenHedron = true;\n \n \n \n \n \n ;\n};\n// previewSVG(hedronSVG)\n\nconst upgradeSVG = do {\n const svg = (\n\n\n\n\n\n\n\n\n);\nsvg\n};\n// previewSVG(upgradeSVG)\n\n\nfunction rectToViewBox(rect) {\n return `${rect.x} ${rect.y} ${rect.width} ${rect.height}`\n}\n\nconst TAP_VIEWBOX = lively.rect(2 ,20 ,103 ,103);\nconst tapSVG = do {\n function toPair(pt) {\n return `${pt.x} ${pt.y}`\n }\n\n const size = 18\n const tip = lively.pt(83.5, 65)\n const anchor = tip.subY(size);\n const tail = anchor.subXY(20, 25)\n const tipLeft = tip.subXY(size, size)\n const tipRight = tip.addXY(size, -size)\n const anchorLeft = tipLeft.addX(12.5)\n const anchorRight = tipRight.subX(12.5)\n const controlLeft = anchorLeft.subY(18)\n const controlRight = anchorRight.subY(18)\n const controlTail = tail.addX(5)\n const path = \n;\n const C_BACKCARD_FILL = \"transparent\";\n const C_BACKCARD_STROKE = \"black\";\n const C_FRONTCARD_FILL = \"black\";\n const C_FRONTCARD_STROKE = \"black\";\n\n const svg = (\n \n \n {path}\n );\nsvg\n}; \n// previewSVG(tapSVG)\n\n\n\n\n\nconst tradeSVG = do {\n const path1 = \"M19.335 11.943c1.463 0.801 2.775 2.074 4.369 4.148 0.005-0.056 0.010-0.113 0.016-0.171 0.309-3.338 0.912-9.84-9.249-13.17 0.113 0.146 0.508 0.575 0.958 1.064 0.75 0.815 1.651 1.795 1.651 1.901-0.903-0.529-5.419-1.906-9.333 0.847s-5.189 6.67-4.616 11.329c0.455 3.7 3.289 6.799 6.95 8.289-2.584-1.464-4.341-4.342-4.341-7.654 0-4.795 3.684-8.682 8.229-8.682 2.050 0 3.925 0.791 5.366 2.099z\";\n const path2 = \"M12.665 20.057c-1.463-0.801-2.775-2.074-4.369-4.148-0.005 0.056-0.010 0.113-0.016 0.171-0.309 3.338-0.912 9.839 9.249 13.17-0.113-0.145-0.508-0.575-0.958-1.064-0.75-0.815-1.651-1.795-1.651-1.901 0.903 0.529 5.419 1.906 9.333-0.847s5.189-6.67 4.616-11.329c-0.454-3.7-3.289-6.799-6.95-8.289 2.584 1.464 4.341 4.342 4.341 7.654 0 4.795-3.684 8.682-8.229 8.682-2.050 0-3.925-0.791-5.366-2.099z\";\n const svg = (\n \n \n \n \n \n \n \n \n \n \n);\nsvg\n};\n// previewSVG(tradeSVG)\n\n\nconst CARD_COST_ONE_VIEWBOX = lively.rect(0, 0, 270, 270);\nconst cardCostOneSVG = do {\n '#d3d3d3'\n const C_OUTER = '#252525'\n const C_INNER = '#d1d1d1'\n const C_TOP = '#e1e5e4'\n const C_IMAGE = '#f4f4f4'\n \n const outer = lively.rect(0, 0, 190, 270)\n const inner = outer.insetBy(15)\n const top = inner.insetBy(10)\n const image = top.insetByRect(lively.rect(0, 30, 0, 45))\n const svg = (\n \n \n \n \n \n \n\n\n);\nsvg\n};\n// previewSVG(cardCostOneSVG)\n\n\nconst CARD_COST_VIEWBOX = lively.rect(0, 0, 376, 326);\nconst cardCostTwoSVG = do {\n const C_OUTER = 'rgb(243, 243, 243)'\n const C_INNER = 'rgb(129, 129, 129)'\n const C_TOP = 'rgb(162, 165, 168)'\n const C_IMAGE = 'rgb(148, 147, 152)'\n const C_BOTTOM = C_TOP;\n \n const svg = (\n \n \n \n \n \n \n\n);\nsvg\n};\n\n\nclass FileCache {\n\n constructor() {\n this.files = {};\n }\n\n dirtyFolder(path) {}\n\n getFile(path, callback) {\n if (this.files[path]) {\n // lively.notify('cache hit')\n } else {\n // lively.notify('cache miss')\n this.files[path] = callback(path);\n }\n\n return this.files[path];\n }\n\n}\n\nif (globalThis.__ubg_file_cache__) {\n globalThis.__ubg_file_cache__.migrateTo(FileCache);\n} else {\n globalThis.__ubg_file_cache__ = new FileCache();\n}\n\nconst VP_FILL = 'violet';\nconst VP_STROKE = '#9400d3'; // darkviolet\nconst VP_FILL_ZERO = '#ddd';\nconst VP_STROKE_ZERO = 'gray';\nconst AFFECT_ALL_COLOR = 'rgba(255, 0, 0, 0.2)';\n\nclass RuleTextRenderer {\n \n static parseEffectsAndLists(printedRules) {\n function prepRule(rule) {\n return rule\n return `${rule}`\n }\n\n const lines = printedRules.split('\\n');\n if (lines.length === 0) {\n return printedRules;\n }\n\n const result = [`
\n${prepRule(lines.shift())}
`];\n \n lines.forEach(line => {\n const bulletMatch = line.match(/^\\s*-\\s*(.+)/);\n if (bulletMatch) {\n const content = bulletMatch[1];\n result.push(`
• ${prepRule(content)}
`);\n } else {\n result.push(`
${prepRule(line)}
`);\n }\n });\n\n return result.join('\\n');\n }\n \n static chip(text) {\n return `${text}`\n }\n \n static manaCost(element) {\n const { others } = forElement(element);\n\n return SVG.inlineSVG(`${SVG.elementSymbol(element, lively.pt(5, 5), 5)}\n${SVG.elementSymbol(others[0], lively.pt(12.5, 1.5), 1.5)}\n${SVG.elementSymbol(others[1], lively.pt(13, 5), 1.5)}\n${SVG.elementSymbol(others[2], lively.pt(12.5, 8.5), 1.5)}`, lively.rect(0, 0, 15, 10));\n }\n \n /*MD ## --- MD*/\n // #important\n static async renderRuleText(cardEditor, cardDesc) {\n let printedRules = cardDesc.getText() || '';\n\n // old big cast icon with small tap\n // printedRules = printedRules.replace(/(^|\\n)t3x(fire|water|earth|wind|gray)([^\\n]*)/gi, function replacer(match, p1, pElement, pText, offset, string, groups) {\n // return `
tap 3x${pElement}${pText}
`;\n // });\n\n // separate rules\n printedRules = printedRules.replace(/affectAll(.*)\\/affectAll/gmi, function replacer(match, innerText, offset, string, groups) {\n return `
${innerText}
`;\n });\n \n printedRules = printedRules.replace(/!!(.*?)!!/gmi, function replacer(match, content) {\n return `${content}`;\n });\n printedRules = printedRules.replace(/\\*(.*?)\\*/gmi, (match, content) => {\n return this.italic(content);\n });\n \n printedRules = this.parseEffectsAndLists(printedRules);\n\n printedRules = this.renderReminderText(printedRules, cardEditor, cardDesc)\n \n printedRules = printedRules.replace(/\\b(?:\\d|-|\\+)*x(?:\\d|-|\\+|vp)*\\b/gmi, function replacer(match, innerText, offset, string, groups) {\n // find the bigger pattern, then just replace all x instead of reconstructing its surrounding characters\n return match.replace('x', 'hedron')\n });\n\n printedRules = printedRules.replace(/blitz/gmi, '');\n printedRules = printedRules.replace(/passive/gmi, ``);\n printedRules = printedRules.replace(/start of turn,?/gmi, '');\n printedRules = printedRules.replace(/ignition/gmi, '');\n printedRules = printedRules.replace(/\\btrain\\b/gmi, '');\n printedRules = printedRules.replace(/\\bdaybreak\\b/gmi, '');\n printedRules = printedRules.replace(/\\bnightfall\\b/gmi, '');\n \n // \n printedRules = printedRules.replace(/\\bcardname(?::(\\d+))?/gmi, (match, cardId, offset, string, groups) => {\n // lor blue card name #519ff1\n // #ffe967\n // #f8d66a\n // #de9b75\n function highlightName(name) {\n return `${name}`\n }\n if (!cardId) {\n return highlightName(cardEditor.getNameFromCard(cardDesc))\n }\n const card = cardEditor.cards.find(card => card.getId() + '' === cardId)\n if (card) {\n return highlightName(cardEditor.getNameFromCard(card))\n } else {\n return `unknown id: ${cardId}`\n }\n });\n\n printedRules = printedRules.replace(/actionFree/gmi, () => this.chip('free'));\n printedRules = printedRules.replace(/actionMulti/gmi, () => this.chip('multi'));\n printedRules = this.renderXPerTurnOrGame(printedRules, cardEditor, cardDesc);\n \n printedRules = printedRules.replace(/actionMain:?/gmi, () => {\n return ''\n });\n \n printedRules = this.renderCastIcon(printedRules)\n\n printedRules = printedRules.replace(/manaCost(fire|water|earth|wind|gray)/gmi, (match, pElement, offset, string, groups) => {\n return this.manaCost(pElement);\n });\n\n printedRules = this.renderElementIcon(printedRules)\n printedRules = this.renderVPIcon(printedRules)\n printedRules = this.renderCardIcon(printedRules)\n printedRules = this.renderCoinIcon(printedRules)\n printedRules = this.renderBracketIcon(printedRules)\n \n printedRules = this.renderKeywords(printedRules)\n printedRules = this.renderHedronIcon(printedRules)\n printedRules = this.renderTapIcon(printedRules)\n printedRules = printedRules.replace(/\\bgear\\b/gmi, '');\n printedRules = printedRules.replace(/combat/gmi, () => {\n return \"\";\n });\n \n this.renderToDoc(cardEditor, printedRules, cardDesc)\n }\n \n static renderXPerTurnOrGame(printedRules, cardEditor, cardDesc) {\n return printedRules.replace(/\\b((?:\\d+)?(?:hedron)?)\\/(game|turn)\\b/gmi, (match, times, type, string, groups) => {\n let color = 'black';\n if (type === 'turn') {\n const perTurnColors = [\n // https://materialui.co/colors\n 'black',\n '#283593',\n '#303F9F',\n '#3949AB',\n '#3F51B5',\n ]\n \n color = perTurnColors[times] || perTurnColors.last\n }\n if (type === 'game') {\n const perGameColors = [\n 'black',\n '#4A148C',\n '#6A1B9A',\n '#7B1FA2',\n '#8E24AA',\n ]\n \n color = perGameColors[1]\n }\n return `${times}/${type}`\n })\n }\n\n static italic(text) {\n return `${text}`\n }\n\n static renderReminderText(printedRules, cardEditor, cardDesc) {\n return printedRules.replace(/\\bremind(?:er)?(\\w+(?:\\-(\\w|\\(|\\))*)*)\\b/gmi, (match, myMatch, offset, string, groups) => {\n const keywords = {\n actionquest: () => {\n return 'You may play this when you perform the action.'\n },\n \n accelerate: (cost) => {\n return `You may play or buy this as a card costing (${cost}). If you do, exec its accelerate effect, !!then trash it!!.)`\n },\n \n affinity: (...args) => {\n let subject = 'This costs'\n if (args.includes('all')) {\n args = args.filter(arg => arg !== 'all')\n // keyword granted\n subject = 'They cost'\n }\n\n if (args.includes('power')) {\n return subject + ' (x) less.'\n }\n\n if (args.includes('vpchips')) {\n return subject + ' (1) less per collected vp.'\n }\n\n if (args.includes('coins')) {\n return subject + ' (1) less per () you have.'\n }\n\n if (args.includes('cards')) {\n return subject + ' (1) less for each of those cards.'\n }\n\n if (args.includes('mana')) {\n const elements = args.filter(arg => arg !== 'mana')\n let elementString\n if (elements.length === 1) {\n elementString = elements.first; \n } else {\n elementString = `${elements.slice(0, -1).join(', ')} or ${elements.last}`; \n }\n return subject + ` (1) less for each mana on ${elementString}.`\n }\n\n throw new Error('unspecified type of Affinity')\n },\n \n blueprint: (cost) => {\n return `Effects below are blocked unless this has stored cards costing (${cost}) or more. As a free action, you may store a card from hand, play or trash.`\n },\n\n bound: (...args) => {\n return 'Only exec bound abilities if the element is called.'\n },\n \n brittle: (...args) => {\n if (args.includes('all')) {\n // keyword granted\n return 'Trash brittle cards after casting them.'\n }\n \n return 'Trash this after casting it.'\n },\n\n clash: (where) => {\n if (!where) {\n throw new Error('no clash area given')\n }\n \n return `To clash, each involved player selects a card from ${where} and draws a card. Compare the total cost of these cards. Higher costs win.`\n },\n\n convokecast: (...args) => {\n if (args.includes('all')) {\n // keyword granted\n return 'Increase their x by 1 for each other card sharing an element with them.'\n }\n \n return 'Increase this card\\'s x by 1 for each other card sharing an element with it.'\n },\n \n countingquest: () => {\n return 'If you fulfill its condition (track with []), as a free action you may trash this to create an Achievement Token.'\n },\n \n cycle: (cost) => {\n if (cost) {\n return `To cycle (${cost}), pay (${cost}) and sacrifice the card to play a card of equal or lower cost.`\n }\n return `To cycle, sacrifice the card to play a card of equal or lower cost.`\n },\n \n cycling: (cost, who) => {\n if (['acard', 'one', 'all'].includes(cost)) {\n who = cost\n cost = undefined\n }\n\n let whoToPrint = 'this'\n if (who === 'acard') {\n whoToPrint = 'a card'\n } else if (who === 'one') {\n whoToPrint = 'the card'\n } else if (who === 'all') {\n whoToPrint = 'a card'\n }\n\n if (cost) {\n return `gear Pay (${cost}) and sacrifice ${whoToPrint} to play a card of equal or lower cost.`\n }\n return `gear Sacrifice ${whoToPrint} to play a card of equal or lower cost.`\n },\n \n dash: (cost, who) => {\n let thatCard = 'this'\n let it = 'this'\n \n if (who === 'one') {\n thatCard = 'that card'\n it = 'it'\n }\n\n if (cost === 'action') {\n return `To dash, play ${thatCard}, but sacrifice ${it} at end of turn.`\n } else {\n return `Pay (${cost}) to play ${thatCard}, but sacrifice ${it} at end of turn.`\n }\n },\n \n \n delirium: () => {\n // alternative: if you have cards of four different elements in trash.\n return `Only activate delirium abilities if you have fire, water, earth and wind cards in trash.`\n },\n \n \n discover: (howMany) => {\n return `To discover ${howMany}, reveal top ${howMany} cards of any piles. Add 1 to your hand, trash the rest.`\n },\n \n emerge: (...args) => {\n if (args.includes('all')) {\n // keyword granted\n return 'When you buy a card: You may sacrifice a card for a discount equal to its cost.'\n }\n \n if (args.includes('one')) {\n // keyword granted\n return 'When you buy the card: You may sacrifice a card for a discount equal to its cost.'\n }\n \n return 'When you buy this: You may sacrifice a card for a discount equal to its cost.'\n },\n\n evoke: (cost, who) => {\n if (who === 'all') {\n return `gear Pay the cost and discard a card to exec its blitz effects.`\n }\n if (who === 'one') {\n return `gear Pay the cost and discard that card to exec its blitz effects.`\n }\n return `gear Pay (${cost}) and discard this to exec its blitz effects.`\n },\n\n flashback: (who) => {\n let subject = 'this';\n if (who === 'all') {\n subject = 'a card';\n }\n if (who === 'one') {\n subject = 'the card';\n }\n return `gear Sacrifice ${subject} to exec its blitz effects.`\n },\n\n impulse: () => {\n return `To impulse a card, set it aside. You may buy it this turn as gear. If you don't: Trash it at end of turn.`\n },\n \n instant: () => {\n return 'gear Buy this.'\n },\n \n invoke: () => {\n return 'gear Discard or sacrifice this to exec the effect.'\n },\n \n manaburst: () => {\n return 'Only activate manaburst abilities if x is 4+.'\n },\n \n magnetic: () => {\n return 'gear Pay this card\\'s cost to meld it from hand to a card on field. The melded card has all abilities and combined stats ((), vp, element, type) of its parts.'\n },\n \n meld: () => {\n return 'The melded card has all abilities and combined stats ((), vp, element, type) of its parts.'\n },\n \n postpone: (cost, delay) => {\n return `You may buy this for ${cost} instead of its normal cost. If you do, put this with [${delay}] in your suspend zone. Start of turn Remove [1] from here. Passive If last [] is removed, play this.`\n },\n \n potion: (...args) => {\n return `Discard or sacrifice this to exec the effect.`\n },\n \n quest: () => {\n return 'As a free action, you may play this if you fulfill its condition.'\n },\n \n quickcast: (...args) => {\n if (args.includes('all')) {\n // keyword granted\n return 'Blitz You may cast it.'\n }\n if (args.includes('one')) {\n // keyword granted\n return 'Blitz You may cast it.'\n }\n \n return 'Blitz You may cast this.'\n },\n\n reap: (...args) => {\n return `To reap a card, gain () equal to its cost OR vp equal to its base vp.`\n },\n \n resonance: (...args) => {\n if (args.includes('all')) {\n // keyword granted\n return 'While a card\\'s element is called, you may cast it along your main spell.'\n }\n if (args.includes('one')) {\n // keyword granted\n return 'While that card\\'s element is called, you may cast it along your main spell.'\n }\n if (args.includes('this')) {\n // variable element known\n return 'While this card\\'s element is called, you may cast this along your main spell.'\n }\n const elements = cardEditor.getElementsFromCard(cardDesc, false)\n let elementString;\n if (elements.length === 0 || (elements.length === 1 && elements.first === 'gray')) {\n elementString = 'this card\\'s element';\n } else if (elements.length === 1) {\n elementString = elements.first;\n } else {\n elementString = `${elements.slice(0, -1).join(', ')} or ${elements.last}`; \n }\n \n // Goal: While wind is called, you may cast this as a free action.\n // While wind is called, you may cast this along another spell.\n return `While ${elementString} is called, you may cast this along your main spell.`\n },\n \n saga: (...args) => {\n return 'Blitz and Start of Turn Put [1] here. Then, exec the corresponding chapter\\'s effect.'\n },\n \n seek: (...args) => {\n return 'Reveal cards from deck until you reveal the appropriate card(s), return the others to the game box.'\n },\n \n stuncounter: (...args) => {\n return 'Casting a card with a stun counter removes the counter instead of the effect.'\n },\n\n synchro: (...args) => {\n return 'Play this as a free action by trashing 2+ cards from field with total cost equal to this card\\'s.'\n },\n\n tiny: () => {\n return 'Tiny cards do not count for triggering the game end.'\n },\n\n trade: (...args) => {\n return 'To trade, discard the card to draw a card.'\n },\n\n trading: (who) => {\n let whoText = 'this'\n if (who === 'one') {\n whoText = 'the card'\n } else if (who === 'all') {\n whoText = 'a card'\n }\n\n return `gear 1/turn Discard ${whoText} to draw a card.`\n },\n\n upgrade: (diff, who) => {\n let whoText = 'this'\n if (who === 'one') {\n whoText = 'the card'\n }\n \n return `To upgrade, trash ${whoText} to play a card costing up to (${diff}) more.`\n },\n };\n \n const modifiers = myMatch.split('-')\n const keyword = modifiers.shift()\n const reminderText = keywords[keyword.toLowerCase()];\n if (!reminderText) {\n lively.error(keyword, 'unknown reminder text')\n return `unknown reminder text '${keyword}''`;\n }\n \n return this.italic(`(${reminderText(...modifiers)})`);\n });\n }\n \n static renderKeywords(printedRules) {\n const C_DARKGRAY = '#555';\n const C_LIGHTGRAY = '#999';\n \n function highlightKeyword(pattern, color=C_DARKGRAY, icon) {\n printedRules = printedRules.replace(pattern, (match, pElement, offset, string, groups) => {\n const text = match;\n return `${icon || ''}${text}`\n });\n }\n \n const C_DARKBEIGE = '#550';\n const C_BROWN = '#a50';\n \n const C_RED_LIGHT = '#d44';\n const C_RED = '#f00';\n const C_DARKRED = '#a11';\n \n const C_ORANGE = '#f50';\n \n const C_GREEN_LIGHT = '#292';\n const C_GREEN = '#090';\n const C_GREEN_DARK = '#170';\n \n const C_TEAL_LIGHT = '#085';\n const C_TEAL_DARK = '#164';\n \n const C_TEAL_BLUE = '#05a';\n \n const C_BLUE = '#00f';\n const C_BLUE_DARK = '#00c';\n \n const C_BLUE_VIOLET = '#219';\n const C_VIOLET_BLUE = '#30a';\n \n const C_VIOLET = '#708';\n\n \n highlightKeyword(/affinity\\b/gmi, C_DARKBEIGE);\n highlightKeyword(/\\bbound\\b(\\sto)?/gmi, C_VIOLET_BLUE);\n highlightKeyword(/brittle\\b/gmi, C_RED);\n highlightKeyword(/cycl(ed?|ing)\\b/gmi, C_DARKGRAY);\n highlightKeyword(/dash(ed|ing)?\\b/gmi, C_BROWN);\n highlightKeyword(/delirium:?\\b/gmi, C_DARKGRAY);\n highlightKeyword(/discover\\b/gmi, C_DARKGRAY, ' ');\n highlightKeyword(/magnetic\\b/gmi, C_RED_LIGHT, ' ');\n highlightKeyword(/manaburst\\b:?/gmi, C_VIOLET, ' ');\n highlightKeyword(/\\b(un)?meld(ed|s)?\\b/gmi, C_BLUE_VIOLET);\n highlightKeyword(/potion\\b/gmi, C_BLUE_VIOLET, ' ');\n highlightKeyword(/quickcast\\b/gmi, C_DARKGRAY);\n highlightKeyword(/resonance\\b/gmi, C_GREEN);\n highlightKeyword(/seek\\b/gmi, C_GREEN, ' ');\n //'#3FDAA5' some turquise\n highlightKeyword(/trad(ed?|ing)\\b/gmi, '#2E9F78', SVG.inlineSVG(tradeSVG.innerHTML, lively.rect(0, 0, 36, 36), 'x=\"10%\" y=\"10%\" width=\"80%\" height=\"80%\"', ''));\n highlightKeyword(/troph(y|ies)(\\spoints?)?\\b/gmi, C_ORANGE, ' ');\n highlightKeyword(/upgraded?\\b/gmi, C_ORANGE, SVG.inlineSVG(upgradeSVG.innerHTML, lively.rect(0, 0, 36, 36), 'x=\"10%\" y=\"10%\" width=\"80%\" height=\"80%\"', ''));\n \n return printedRules\n }\n \n static renderElementIcon(printedRules) {\n function inlineElement(element) {\n return SVG.inlineSVG(SVG.elementSymbol(element, lively.pt(5, 5), 5));\n }\n\n return printedRules.replace(/\\b(fire|water|earth|wind|gray)\\b/gmi, (match, pElement, offset, string, groups) => inlineElement(pElement));\n }\n \n static renderHedronIcon(printedRules) {\n function inlineHedron() {\n return SVG.inlineSVG(hedronSVG.innerHTML, lively.rect(0, 0, 23, 23), 'x=\"10%\" y=\"10%\" width=\"80%\" height=\"80%\"', '')\n }\n\n return printedRules.replace(/hedron/gmi, (match, pElement, offset, string, groups) => inlineHedron());\n }\n \n static renderTapIcon(printedRules) {\n function inlineTapIcon() {\n return SVG.inlineSVG(tapSVG.innerHTML, TAP_VIEWBOX, 'x=\"10%\" y=\"10%\" width=\"80%\" height=\"80%\"', '')\n }\n\n return printedRules.replace(/\\btap\\b/gmi, (match, pElement, offset, string, groups) => inlineTapIcon());\n }\n \n static __textOnIcon__(text, rect, center) {\n let textToPrint\n if (text.includes('hedron') || text.includes('x')) {\n const parts = []\n let isFirst = true;\n for (let part of text.split(/x|hedron/i)) {\n if (isFirst) {\n isFirst = false\n } else {\n parts.push(`hedron`)\n }\n if (part) { // part is not an empty string\n parts.push(part)\n }\n }\n // split available space\n const lengthPerPart = [];\n for (let part of parts) {\n const lengthOfPart = part === 'hedron' ? 1 : part.length\n lengthPerPart.push(lengthOfPart)\n }\n const totalLength = lengthPerPart.sum()\n const percentageSpacePerPart = lengthPerPart.map(len => len / totalLength)\n let iteratingLength = 0\n textToPrint = parts.map((part, i) => {\n let startingLength = iteratingLength\n const endingLength = iteratingLength = startingLength + percentageSpacePerPart[i]\n const middle = (startingLength + endingLength) / 2;\n if (part === 'hedron') {\n const scaleFactor = totalLength > 1 ? .7 : 1\n return `${part}`\n } else {\n return `${part}`\n }\n }).join('')\n } else {\n // simple form: just some text\n textToPrint = `${text}`;\n }\n return textToPrint\n }\n\n static renderVPIcon(printedRules) {\n const printVP = vp => {\n const rect = lively.rect(0, 0, 10, 10)\n const center = rect.center();\n \n let textToPrint = this.__textOnIcon__(vp, rect, center);\n \n Math.sqrt(.5) \n return `${SVG.inlineSVG(`\n \n \n\n${textToPrint}\n`)}`;\n }\n\n return printedRules.replace(/(\\-?\\+?(?:\\d+|\\*|d+\\*|\\d+(?:x|y|z|hedron)|(?:x|y|z|hedron)|\\b)\\-?\\+?)VP\\b/gmi, function replacer(match, vp, offset, string, groups) {\n return printVP(vp);\n });\n }\n \n static renderCardIcon(printedRules) {\n var that = this;\n function inlineCardCost(cost) {\n const rect = CARD_COST_ONE_VIEWBOX\n const center = rect.center();\n \n let textToPrint = that.__textOnIcon__(cost, rect, center);\n return SVG.inlineSVG(`${cardCostOneSVG.innerHTML}\n${textToPrint}`, CARD_COST_ONE_VIEWBOX, 'x=\"10%\" y=\"10%\" width=\"80%\" height=\"80%\"', '')\n }\n\n return printedRules.replace(/\\(\\(((?:[*0-9xyz+-]|hedron)*)\\)\\)/gmi, (match, pElement, offset, string, groups) => inlineCardCost(pElement));\n }\n \n static renderCoinIcon(printedRules) {\n const coin = text => {\n const rect = lively.rect(0, 0, 10, 10)\n const center = rect.center();\n \n let textToPrint = this.__textOnIcon__(text, rect, center);\n \n return SVG.inlineSVG(`${SVG.circle(center, 5, `fill=\"goldenrod\"`)}\n${SVG.circleRing(center, 4.75, 5, `fill=\"darkviolet\"`)}\n${textToPrint}`);\n }\n\n return printedRules.replace(/\\(((?:[*0-9xyz+-]|hedron)*)\\)/gmi, function replacer(match, p1, offset, string, groups) {\n return coin(p1);\n });\n }\n \n static renderBracketIcon(printedRules) {\n const bracket = text => {\n const rect = lively.rect(0, 0, 10, 10)\n const center = rect.center();\n \n let textToPrint = this.__textOnIcon__(text, rect, center);\n\n return SVG.inlineSVG(`\n\n\n${textToPrint}`, undefined, undefined, 'transform:scale(1);');\n }\n\n return printedRules.replace(/\\[((?:[*0-9xyz+-]|hedron)*)\\]/gmi, function replacer(match, p1, offset, string, groups) {\n return bracket(p1);\n });\n }\n \n static renderCastIcon(printedRules) {\n return printedRules.replace(/t?3x(fire|water|earth|wind|gray)\\:?/gi, (match, pElement, offset, string, groups) => {\n return `${castIcon} Cast:`;\n });\n }\n\n static async renderToDoc(rulesTextElement, printedRules, cardDesc) {\n const elements = ['fire', 'water', 'earth', 'wind'];\n elements.forEach(element => rulesTextElement.content.classList.remove(element))\n\n const cardElements = rulesTextElement.getElementsFromCard(cardDesc, false);\n cardElements.forEach(element => rulesTextElement.content.classList.add(element))\n\n rulesTextElement.content.innerHTML = printedRules\n }\n}\n\nconst OUTSIDE_BORDER_ROUNDING = lively.pt(3, 3)\n\nexport default class UbgRulesText extends Morph {\n \n applyRulesText(cardDesc) {\n this.cardDesc = cardDesc\n this.renderRuleText(cardDesc)\n }\n \n /*MD ## Filter MD*/\n get assetsFolder() {\n return this.src.replace(/(.*)\\/.*$/i, '$1/assets/');\n }\n\n /*MD ## Build MD*/\n async fetchAssetsInfo() {\n return (await this.assetsFolder.fetchStats()).contents;\n }\n\n /*MD ## Extract Card Info MD*/\n colorsForCard(card) {\n const BOX_FILL_OPACITY = 0.7;\n\n const currentVersion = card.versions.last;\n \n if (card.getType() === 'character') {\n return ['#efc241', '#b8942d', BOX_FILL_OPACITY];\n }\n\n const multiElement = Array.isArray(card.getElement());\n if (multiElement) {\n return ['#ff88ff', '#ff00ff', BOX_FILL_OPACITY];\n }\n\n const singleElementColors = {\n fire: ['#ffaaaa', '#dd0000', BOX_FILL_OPACITY],\n water: ['#aaaaff', '#0000ff', BOX_FILL_OPACITY],\n earth: ['#eeee88', '#cccc00', BOX_FILL_OPACITY],\n wind: ['#88ff88', '#00bb00', BOX_FILL_OPACITY]\n }[currentVersion.element && currentVersion.element.toLowerCase && currentVersion.element.toLowerCase()];\n if (singleElementColors) {\n return singleElementColors;\n }\n\n return ['#ffffff', '#888888', BOX_FILL_OPACITY];\n }\n\n getNameFromCard(cardDesc) {\n const currentVersion = cardDesc.versions.last;\n return currentVersion.name || ''\n }\n \n getElementsFromCard(cardDesc, grayIfEmpty) {\n const element = cardDesc.getElement();\n if (Array.isArray(element)) {\n return element\n } else if (element) {\n return [element]\n } else {\n return grayIfEmpty ? ['gray'] : []\n }\n }\n\n /*MD ## Debugging MD*/\n debugPoint(pt, color = 'red') {\n this.content.append(
);\n }\n \n debugRect(rect, color = 'red') {\n return this.roundedRect(rect, 'transparent', color, 1 / 3.7795275591, 0);\n }\n \n /*MD ## Rendering Helpers MD*/\n line(start, end, color, width) {\n const startX = start.x;\n const startY = start.y;\n const endX = end.x;\n const endY = end.y;\n\n const length = Math.sqrt(Math.pow(endX - startX, 2) + Math.pow(endY - startY, 2));\n const angle = Math.atan2(endY - startY, endX - startX) * (180 / Math.PI);\n\n const line = document.createElement('div');\n line.style.width = length + 'mm';\n line.style.transform = `rotate(${angle}deg) translateY(${-width / 2}mm)`;\n line.style.position = 'absolute';\n line.style.top = startY + 'mm';\n line.style.left = startX + 'mm';\n line.style.height = width + 'mm';\n line.style.backgroundColor = color;\n line.style.transformOrigin = 'top left';\n\n this.content.append(line)\n }\n\n roundedRect(rect, fill, stroke, strokeWidth, borderRadius) {\n const element =
;\n\n this.content.append(element)\n \n return element;\n }\n \n colorWithOpacity(color, opacity) {\n return `color-mix(in srgb, ${color} ${opacity * 100}%, transparent)`\n }\n\n /*MD ## Background Images MD*/\n filePathForBackgroundImage(cardDesc, assetsInfo) {\n const id = cardDesc.id;\n if (id) {\n const possibleFileNames = ['jpg', 'png'].map(ending => `${id}.${ending}`);\n const foundEntry = assetsInfo.find(entry => entry.type === 'file' && possibleFileNames.includes(entry.name));\n if (foundEntry) {\n return this.assetsFolder + foundEntry.name;\n }\n }\n\n const typeString = cardDesc.getType() && cardDesc.getType().toLowerCase && cardDesc.getType().toLowerCase()\n const defaultFiles = {\n gadget: 'default-gadget.jpg',\n character: 'default-character.jpg',\n spell: 'default-spell.jpg'\n };\n return this.assetsFolder + (defaultFiles[typeString] || 'default.jpg');\n }\n\n async loadImage(filePath) {\n return new Promise((resolve, reject) => {\n const image = new Image();\n image.addEventListener('load', resolve);\n image.addEventListener('error', reject);\n image.src = filePath;\n });\n }\n\n /*MD ## Rendering MD*/\n async renderMagicStyle(cardDesc, outsideBorder, assetsInfo) {\n const [BOX_FILL_COLOR, BOX_STROKE_COLOR, BOX_FILL_OPACITY] = this.colorsForCard(cardDesc);\n\n // black border\n this.roundedRect(outsideBorder, 'black', 'transparent', '0', '0')\n\n // innerBorder\n const INNER_INSET = 3;\n const innerBorder = outsideBorder.insetBy(INNER_INSET);\n\n // card image\n const filePath = this.filePathForBackgroundImage(cardDesc, assetsInfo);\n const newBG =
;\n this.content.append(newBG)\n\n // title bar\n const TITLE_BAR_HEIGHT = 7;\n const titleBar = innerBorder.insetBy(1);\n titleBar.height = TITLE_BAR_HEIGHT;\n const TITLE_BAR_BORDER_WIDTH = 0.200025;\n this.roundedRect(titleBar, this.colorWithOpacity(BOX_FILL_COLOR, .5), BOX_STROKE_COLOR, TITLE_BAR_BORDER_WIDTH, 1)\n\n // card name\n {\n const pos = titleBar.leftCenter().addX(2);\n const fontSize = .6 * titleBar.height::mmToPoint();\n\n const cardName = this.getNameFromCard(cardDesc);\n this.content.append({cardName})\n }\n\n // cost\n const COIN_RADIUS = 4;\n const coinPos = titleBar.bottomLeft().addY(1).addXY(COIN_RADIUS, COIN_RADIUS);\n this.renderCost(cardDesc, coinPos, COIN_RADIUS)\n\n // type & elements\n const typePos = coinPos.addY(COIN_RADIUS * 1.5)\n this.renderType(cardDesc, typePos, BOX_FILL_COLOR, BOX_FILL_OPACITY)\n\n // rule box\n const ruleBox = outsideBorder.copy()\n const height = outsideBorder.height * .4;\n ruleBox.y = ruleBox.bottom() - height;\n ruleBox.height = height;\n // this.debugRect(ruleBox)\n const ruleBoxInset = 1 + INNER_INSET;\n const ruleTextInset = 2;\n await this.renderRuleText(cardDesc, outsideBorder, ruleBox, {\n insetBoxBy: ruleBoxInset,\n insetTextBy: ruleTextInset,\n innerStrokeColor: BOX_STROKE_COLOR,\n innerFillColor: BOX_FILL_COLOR,\n innerFillOpacity: BOX_FILL_OPACITY,\n outerStrokeColor: 'transparent',\n outerFillColor: 'transparent',\n outerFillOpacity: 0,\n });\n \n // tags\n const tagsAnchor = titleBar.bottomRight().addY(1);\n this.renderTags(cardDesc, tagsAnchor, outsideBorder)\n }\n\n async renderFullBleedStyle(cardDesc, outsideBorder, assetsInfo) {\n const type = cardDesc.getType();\n const typeString = type && type.toLowerCase && type.toLowerCase() || '';\n\n if (typeString === 'spell') {\n await this.renderSpell(cardDesc, outsideBorder, assetsInfo)\n } else if (typeString === 'gadget') {\n await this.renderGadget(cardDesc, outsideBorder, assetsInfo)\n } else if (typeString === 'character') {\n await this.renderCharacter(cardDesc, outsideBorder, assetsInfo)\n } else {\n await this.renderMagicStyle(cardDesc, outsideBorder, assetsInfo)\n }\n \n this.renderIsBad(cardDesc, outsideBorder)\n }\n \n maskedCircle(outsideBorder, center, radius, strokeWidth, fillColor, fillOpacity, strokeColor) {\n strokeWidth *= 2; // half covered by mask\n\n const svg = \n \n \n \n \n \n \n \n \n ;\n\n this.content.insertAdjacentHTML('beforeend', svg.outerHTML)\n \n }\n /*MD ### Rendering Card Types MD*/\n // #important\n async renderSpell(cardDesc, outsideBorder, assetsInfo) {\n const [BOX_FILL_COLOR, BOX_STROKE_COLOR, BOX_FILL_OPACITY] = this.colorsForCard(cardDesc);\n\n // spell circle\n {\n const CIRCLE_BORDER = -3;\n const radius = (outsideBorder.width - CIRCLE_BORDER) / 2;\n const center = outsideBorder.center().withY(outsideBorder.top() + CIRCLE_BORDER + radius)\n const strokeWidth = 1;\n this.maskedCircle(outsideBorder, center, radius, strokeWidth, BOX_FILL_COLOR, BOX_FILL_OPACITY, BOX_STROKE_COLOR)\n }\n \n // innerBorder\n const innerBorder = outsideBorder.insetBy(3);\n\n // title\n const TITLE_BAR_HEIGHT = 7;\n const COST_COIN_RADIUS = 4;\n const COST_COIN_MARGIN = 2;\n \n const titleBorder = innerBorder.insetBy(1);\n titleBorder.height = TITLE_BAR_HEIGHT;\n\n this.renderTitleBarAndCost(cardDesc, titleBorder, COST_COIN_RADIUS, COST_COIN_MARGIN)\n\n // rule box\n const ruleBox = outsideBorder.copy()\n const height = outsideBorder.height * .3;\n ruleBox.y = ruleBox.bottom() - height;\n ruleBox.height = height;\n // this.debugRect(ruleBox)\n \n // rule text\n const RULE_BOX_INSET = 1;\n const RULE_TEXT_INSET = 1;\n await this.renderRuleText(cardDesc, outsideBorder, ruleBox, {\n insetBoxBy: RULE_BOX_INSET,\n insetTextBy: RULE_TEXT_INSET,\n outerStrokeColor: 'transparent',\n outerFillColor: 'transparent',\n outerFillOpacity: 0,\n });\n \n // tags\n const tagsAnchor = lively.pt(titleBorder.right(), titleBorder.bottom()).addXY(-RULE_TEXT_INSET, 1);\n this.renderTags(cardDesc, tagsAnchor, outsideBorder)\n }\n\n // #important\n async renderGadget(cardDesc, outsideBorder, assetsInfo) {\n const [BOX_FILL_COLOR, BOX_STROKE_COLOR, BOX_FILL_OPACITY] = this.colorsForCard(cardDesc);\n\n // innerBorder\n const innerBorder = outsideBorder.insetBy(3);\n // this.roundedRect(innerBorder, 'steelblue', 'red', 3, 0)\n\n // top box\n const topBox = outsideBorder.copy()\n {\n topBox.height = 13;\n const box = this.roundedRect(topBox, this.colorWithOpacity(BOX_FILL_COLOR, BOX_FILL_OPACITY), 'transparent', 0, 0);\n box.style.backdropFilter = 'blur(4px)';\n \n this.line(topBox.bottomLeft(), topBox.bottomRight(), BOX_STROKE_COLOR, 1)\n }\n \n // title\n {\n const TITLE_BAR_HEIGHT = 7;\n const COST_COIN_RADIUS = 4;\n const COST_COIN_MARGIN = 2;\n \n const titleBorder = innerBorder.insetBy(1);\n titleBorder.height = TITLE_BAR_HEIGHT;\n \n this.renderTitleBarAndCost(cardDesc, titleBorder, COST_COIN_RADIUS, COST_COIN_MARGIN)\n }\n \n // rule box border calc\n const ruleBox = outsideBorder.copy()\n const height = outsideBorder.height * .4;\n ruleBox.y = ruleBox.bottom() - height;\n ruleBox.height = height;\n // this.debugRect(ruleBox)\n \n // rule text\n const RULE_BOX_INSET = 1;\n const RULE_TEXT_INSET = 1;\n await this.renderRuleText(cardDesc, outsideBorder, ruleBox, {\n insetBoxBy: RULE_BOX_INSET,\n insetTextBy: RULE_TEXT_INSET,\n outerStrokeColor: BOX_STROKE_COLOR,\n outerFillColor: BOX_FILL_COLOR,\n outerFillOpacity: BOX_FILL_OPACITY,\n });\n \n // tags\n const tagsAnchor = lively.pt(topBox.right(), topBox.bottom()).addXY(-RULE_TEXT_INSET, 1);\n this.renderTags(cardDesc, tagsAnchor, outsideBorder)\n }\n\n // #important\n async renderCharacter(cardDesc, outsideBorder, assetsInfo) {\n const [BOX_FILL_COLOR, BOX_STROKE_COLOR, BOX_FILL_OPACITY] = this.colorsForCard(cardDesc);\n\n // Zohar design\n {\n const ZOHAR_DESIGN_BORDER_WIDTH = .5;\n [[outsideBorder.topLeft(), lively.pt(1, 0)], [outsideBorder.topRight(), lively.pt(-1, 0)]].forEach(([startingPt, direction]) => {\n const dirX = direction.x;\n startingPt = startingPt.subY(5)\n const topMost = startingPt.addXY(dirX*8, 0);\n const triangleTop = topMost.addXY(0, 15 + 5);\n const triangleOuter = triangleTop.addXY(-dirX*15, 15);\n const triangleBottom = triangleOuter.addXY(dirX*15, 15);\n const bottom = triangleBottom.addXY(0, 100);\n const bottomOuter = bottom.addXY(-dirX*10, 0);\n const diamondPoints = `${startingPt.x} ${startingPt.y}, ${topMost.x} ${topMost.y}, ${triangleTop.x} ${triangleTop.y}, ${triangleOuter.x} ${triangleOuter.y}, ${triangleBottom.x} ${triangleBottom.y}, ${bottom.x} ${bottom.y}, ${bottomOuter.x} ${bottomOuter.y}`;\n\n const svg = \n \n ;\n\n this.content.insertAdjacentHTML('beforeend', svg.outerHTML)\n })\n }\n \n // innerBorder\n const innerBorder = outsideBorder.insetBy(3);\n // this.roundedRect(innerBorder, 'steelblue', 'red', 3, 0)\n\n // title\n const TITLE_BAR_HEIGHT = 7;\n const COST_COIN_RADIUS = 4;\n const COST_COIN_MARGIN = 2;\n \n const titleBorder = innerBorder.insetBy(1);\n titleBorder.height = TITLE_BAR_HEIGHT;\n \n this.renderTitleBarAndCost(cardDesc, titleBorder, COST_COIN_RADIUS, COST_COIN_MARGIN)\n \n // rule box border calc\n const ruleBox = outsideBorder.copy()\n const height = outsideBorder.height * .4;\n ruleBox.y = ruleBox.bottom() - height;\n ruleBox.height = height;\n \n // rule text\n const RULE_BOX_INSET = 1;\n const RULE_TEXT_INSET = 1;\n await this.renderRuleText(cardDesc, outsideBorder, ruleBox, {\n insetBoxBy: RULE_BOX_INSET,\n insetTextBy: RULE_TEXT_INSET,\n outerStrokeColor: BOX_STROKE_COLOR,\n outerFillColor: BOX_FILL_COLOR,\n outerFillOpacity: BOX_FILL_OPACITY,\n });\n \n // tags\n const tagsAnchor = lively.pt(titleBorder.right(), titleBorder.bottom()).addXY(-RULE_TEXT_INSET, 1);\n this.renderTags(cardDesc, tagsAnchor, outsideBorder)\n }\n \n /*MD ### Rendering Card Components MD*/\n renderTitleBarAndCost(cardDesc, border, costCoinRadius, costCoinMargin) {\n const TITLE_BAR_BORDER_WIDTH = 0.200025;\n\n const titleBar = border.copy()\n const coinLeftCenter = titleBar.leftCenter()\n const spacingForCoin = 2*costCoinRadius + costCoinMargin\n titleBar.x += spacingForCoin\n titleBar.width -= spacingForCoin\n\n const [BOX_FILL_COLOR, BOX_STROKE_COLOR, BOX_FILL_OPACITY] = this.colorsForCard(cardDesc);\n\n // title space\n // this.roundedRect(border, this.colorWithOpacity('#ffffff', .5), 'transparent', 0, 0)\n this.roundedRect(titleBar, this.colorWithOpacity('#ffffff', .5), 'transparent', 0, 1)\n \n // title bar\n this.roundedRect(titleBar, this.colorWithOpacity(BOX_FILL_COLOR, .5), BOX_STROKE_COLOR, TITLE_BAR_BORDER_WIDTH, 1)\n \n // card name\n {\n const pos = titleBar.leftCenter().addX(2);\n const fontSize = .6 * titleBar.height::mmToPoint();\n this.content.append({this.getNameFromCard(cardDesc)})\n }\n\n const coinCenter = coinLeftCenter.addX(costCoinRadius);\n this.renderInHandSymbols(cardDesc, border, costCoinRadius, costCoinMargin, coinCenter)\n }\n \n renderInHandSymbols(cardDesc, border, costCoinRadius, costCoinMargin, coinCenter) {\n let currentCenter = coinCenter;\n\n // cost\n this.renderCost(cardDesc, currentCenter, costCoinRadius)\n\n if ((cardDesc.getType() || '').toLowerCase() !== 'character') {\n // vp\n currentCenter = currentCenter.addY(costCoinRadius * 2.75);\n this.renderBaseVP(cardDesc, currentCenter, costCoinRadius)\n\n // element (list)\n currentCenter = currentCenter.addY(costCoinRadius * 2.75);\n const elementListDirection = 1;\n currentCenter = this.renderElementList(cardDesc, currentCenter, costCoinRadius, elementListDirection)\n } else {\n currentCenter = currentCenter.addY(costCoinRadius * 1);\n }\n\n // type\n currentCenter = currentCenter.addY(costCoinRadius * .75)\n const [BOX_FILL_COLOR, BOX_STROKE_COLOR, BOX_FILL_OPACITY] = this.colorsForCard(cardDesc);\n this.renderType(cardDesc, currentCenter, BOX_FILL_COLOR, BOX_FILL_OPACITY)\n }\n\n renderElementList(cardDesc, pos, radius, direction) {\n const elements = this.getElementsFromCard(cardDesc, true);\n for (let element of elements) {\n this.renderElementSymbol(element, pos, radius)\n pos = pos.addY(direction * radius * .75);\n }\n return pos.addY(direction * radius * .25);\n }\n\n renderCost(cardDesc, pos, coinRadius) {\n const costSize = coinRadius / 3;\n\n const costDesc = cardDesc.getCost();\n const cost = Array.isArray(costDesc) ? costDesc.first : costDesc;\n\n const coinCenter = pos;\n const strokeWidth = .2 * costSize;\n const size = `${2 * coinRadius}mm`;\n const svg = ;\n this.content.insertAdjacentHTML('beforeend', svg.outerHTML)\n\n// {\n// const r = ;\n// const str = {r};\n// this.content.append(str);\n// }\n\n this.renderIconText(coinCenter, costSize, cost, CSS_FONT_FAMILY_CARD_COST)\n }\n\n renderBaseVP(cardDesc, pos, coinRadius) {\n const costSize = coinRadius / 3;\n \n const vp = cardDesc.getBaseVP() || 0;\n const fillColor = vp === 0 ? VP_FILL_ZERO : VP_FILL\n const fillOpacity = .9\n const strokeColor = vp === 0 ? VP_STROKE_ZERO : VP_STROKE\n const strokeWidth = .2 * costSize;\n\n const iconCenter = pos;\n\n // diamond shape\n const diagonal = coinRadius * .9 * Math.sqrt(2)\n const down = pos.addY(diagonal)\n const left = pos.addX(-diagonal)\n const up = pos.addY(-diagonal)\n const right = pos.addX(diagonal)\n const diamondPoints = `${down.x} ${down.y}, ${left.x} ${right.y}, ${up.x} ${up.y}, ${right.x} ${right.y}`;\n\n const svg = \n \n ;\n this.content.insertAdjacentHTML('beforeend', svg.outerHTML)\n this.renderIconText(iconCenter, costSize, vp, CSS_FONT_FAMILY_CARD_VP)\n }\n\n renderIconText(centerPos, size, text, font) {\n if (text === undefined) {\n return\n }\n \n const iconText = {'' + text};\n \n this.content.append(iconText)\n }\n \n // #important\n async renderRuleText(cardDesc) {\n lively.notify('render rules text')\n return RuleTextRenderer.renderRuleText(this, cardDesc)\n }\n\n renderType(cardDesc, anchorPt, color, opacity) {\n // function curate() {\n // return this.toLower().upperFirst();\n // }\n // function prepend(other) {\n // return other + ' ' + this;\n // }\n // const element = cardDesc.getElement();\n let fullText = (cardDesc.getType() || '').toLower().upperFirst()\n // if (Array.isArray(element)) {\n // element.forEach(element => {\n // fullText = fullText::prepend(element::curate())\n // })\n // } else if (element) {\n // fullText = fullText::prepend(element::curate())\n // }\n\n this.content.append({fullText});\n }\n \n renderQRCode(cardDesc, qrAnchor, outsideBorder) {\n return;\n const canvas = ;\n const { QrCode } = qrcodegen\n var qr = QrCode.encodeText(\"\" + cardDesc.getId(), QrCode.Ecc.HIGH);\n\n function drawCanvas(qr, scale, border, lightColor, darkColor, canvas) {\n if (scale <= 0 || border < 0)\n throw new RangeError(\"Value out of range\");\n const width = (qr.size + border * 2) * scale;\n canvas.width = width;\n canvas.height = width;\n let ctx = canvas.getContext(\"2d\");\n for (let y = -border; y < qr.size + border; y++) {\n for (let x = -border; x < qr.size + border; x++) {\n ctx.fillStyle = qr.getModule(x, y) ? darkColor : lightColor;\n ctx.fillRect((x + border) * scale, (y + border) * scale, scale, scale);\n }\n }\n }\n\n const [BOX_FILL_COLOR, BOX_STROKE_COLOR, BOX_FILL_OPACITY] = this.colorsForCard(cardDesc);\n\n drawCanvas(qr, 3, 1, \"white\", BOX_STROKE_COLOR, canvas);\n this.content.append(canvas)\n }\n \n renderTags(cardDesc, tagsAnchor, outsideBorder) {\n const tags = cardDesc.getTags().sortBy(i => i, true).map(tag =>
#{tag}
);\n const FONT_SIZE = 7;\n \n this.content.append({...tags})\n }\n\n renderElementSymbol(element, pos, radius) {\n const innerBounds = lively.rect(0, 0, 10, 10)\n const svgInnerPos = innerBounds.center();\n const svgInnerRadius = innerBounds.width / 2;\n const outerBounds = lively.rect(pos.x - radius, pos.y - radius, radius * 2, radius * 2);\n const yourSvgString = SVG.outerSVG(SVG.elementSymbol(element, svgInnerPos, svgInnerRadius), innerBounds, outerBounds);\n\n this.content.insertAdjacentHTML('beforeend', yourSvgString)\n }\n\n get content() {\n return this.get('#content');\n }\n \n renderIsBad(cardDesc, outsideBorder) {\n const slash = (color, width=2, offset=lively.pt(0,0)) => {\n const start = outsideBorder.topRight().addPt(offset);\n const end = outsideBorder.bottomLeft().addPt(offset);\n this.line(start, end, color, width)\n }\n \n if (cardDesc.hasTag('duplicate')) {\n slash('#bbbbbb', 2, lively.pt(-3, -3))\n }\n if (cardDesc.hasTag('unfinished')) {\n slash('#888888', 2, lively.pt(-2, -2))\n }\n if (cardDesc.hasTag('bad')) {\n slash('#ff0000', 2)\n }\n if (cardDesc.hasTag('deprecated')) {\n slash('#ff00ff', 2, lively.pt(2, 2))\n }\n if (cardDesc.getRating() === 'remove') {\n slash('#999999', 5, lively.pt(-5, -5))\n }\n }\n\n /*MD ## Basic Web Components MD*/\n initialize() {\n if (this.hasAttribute('for-preload')) {\n return;\n }\n\n this.windowTitle = \"UbgCard\";\n \n this._attrObserver = new MutationObserver(mutations => {\n mutations.forEach(mutation => {\n let rerenderRequired = false;\n if (mutation.type == \"attributes\") {\n // console.log(\"observation\", mutation.attributeName,mutation.target.getAttribute(mutation.attributeName));\n rerenderRequired = true;\n this.attributeChangedCallback(mutation.attributeName, mutation.oldValue, mutation.target.getAttribute(mutation.attributeName));\n }\n if (rerenderRequired) {\n this.rerender()\n }\n });\n });\n this._attrObserver.observe(this, { attributes: true });\n }\n \n rerender() {\n lively.notify('RERENDER')\n }\n \n static get observedAttributes() {\n return [\"card\", \"src\", \"is-cardback\"];\n }\n \n attributeChangedCallback(name, oldValue, newValue) {\n lively.notify(`${oldValue} -> ${newValue}`, name)\n }\n \n /*MD ## External API MD*/\n setSrc(src) {\n return this.src = src;\n }\n\n setCard(card) {\n return this.card = card;\n }\n\n setCards(cards) {\n return this.cards = cards;\n }\n\n async render() {\n this._checkOptionsSet()\n const assetsInfo = await this.fetchAssetsInfo();\n const outsideBorder = lively.pt(0,0).extent(POKER_CARD_SIZE_MM);\n const cardToPrint = this.card;\n lively.error('FULL RENDER')\n await this.renderFullBleedStyle(cardToPrint, outsideBorder, assetsInfo)\n }\n\n _checkOptionsSet() {\n if (!this.src) {\n lively.warn('cannot render: \"src\" not set')\n }\n if (!this.card) {\n lively.warn('cannot render: \"card\" not set')\n }\n if (!this.cards) {\n lively.warn('cannot render: \"cards\" not set')\n }\n }\n\n /*MD ## Lively-specific API MD*/\n livelyPrepareSave() {\n // this.setAttribute(\"data-mydata\", this.get(\"#textField\").value)\n }\n \n livelyPreMigrate() {\n // is called on the old object before the migration\n }\n \n livelyMigrate(other) {\n lively.notify('migrate rules text')\n this.applyRulesText(other.cardDesc)\n }\n \n async livelyExample() {\n }\n \n}\n"} -{"from":48503,"to":48503,"name":"color","color":"#9ecae1"} -{"from":48505,"to":48505,"name":"color","color":"#9ecae1"} -{"from":48511,"to":48511,"name":"color","color":"#9ecae1"} \ No newline at end of file diff --git a/src/components/widgets/ubg-utils.js b/src/components/widgets/ubg-utils.js new file mode 100644 index 000000000..56151555a --- /dev/null +++ b/src/components/widgets/ubg-utils.js @@ -0,0 +1,154 @@ +/* global globalThis */ + +import paper from 'src/client/paperjs-wrapper.js' + +export const fire = ; +export const water = ; +export const earth = ; +export const wind = ; +export const gray = ; +export const question = ; + +export class PathDataScaleCache { + static getPathData(element, size = lively.pt(10, 10)) { + if (!this.cache) { + this.cache = {} + } + + const key = `${element}-${size.x}-${size.y}`; + if (!this.cache[key]) { + // lively.notify(`${element}-${size.x}-${size.y}`, 'cache miss') + this.cache[key] = this._scalePathData(element, size) + } + + return this.cache[key] + } + + static _scalePathData(element, size) { + const { glyph } = forElement(element); + const path = new paper.Path(glyph.getAttribute('d')); + + path.scale(1, -1); + + const margin = size.scaleBy(0.1); + const boundingRect = new paper.Path.Rectangle({ + point: margin.toPair(), + size: size.subPt(margin.scaleBy(2)).toPair() + }); + path.fitBounds(boundingRect.bounds); + + return path.pathData; + } +} + +export function tenTenPathData(element) { + return PathDataScaleCache.getPathData(element, lively.pt(10, 10)); +} + +export const elementInfo = { + fire: { + name: 'fire', + faIcon: 'book', + glyph: fire, + get pathData() { return tenTenPathData('fire') }, + pathWidth: parseInt(fire.getAttribute('horiz-adv-x')), + pathHeight: parseInt(fire.getAttribute('vert-adv-y')), + fill: '#ffbbbb', + stroke: '#ff0000', + others: ['water', 'earth', 'wind'] + }, + water: { + name: 'water', + faIcon: 'droplet', + glyph: water, + get pathData() { return tenTenPathData('water') }, + pathWidth: parseInt(water.getAttribute('horiz-adv-x')), + pathHeight: parseInt(water.getAttribute('vert-adv-y')), + fill: '#8888ff', + stroke: '#0000ff', + others: ['fire', 'earth', 'wind'] + }, + earth: { + name: 'earth', + faIcon: 'mountain', + glyph: earth, + get pathData() { return tenTenPathData('earth') }, + pathWidth: parseInt(earth.getAttribute('horiz-adv-x')), + pathHeight: parseInt(earth.getAttribute('vert-adv-y')), + fill: 'rgb(255, 255, 183)', + stroke: '#ffd400', + others: ['fire', 'water', 'wind'] + }, + wind: { + name: 'wind', + faIcon: 'cloud', + glyph: wind, + get pathData() { return tenTenPathData('wind') }, + pathWidth: parseInt(wind.getAttribute('horiz-adv-x')), + pathHeight: parseInt(wind.getAttribute('vert-adv-y')), + fill: '#bbffbb', + stroke: '#00ff00', + others: ['fire', 'water', 'earth'] + }, + gray: { + name: 'gray', + faIcon: 'circle', + glyph: gray, + get pathData() { return tenTenPathData('gray') }, + pathWidth: parseInt(gray.getAttribute('horiz-adv-x')), + pathHeight: parseInt(gray.getAttribute('vert-adv-y')), + fill: '#dddddd', + stroke: '#5A5A5A', + others: ['gray', 'gray', 'gray'] + }, + unknown: { + name: 'unknown', + faIcon: 'question', + glyph: question, + get pathData() { return tenTenPathData('question') }, + pathWidth: parseInt(question.getAttribute('horiz-adv-x')), + pathHeight: parseInt(question.getAttribute('vert-adv-y')), + fill: 'pink', + stroke: 'violet', + others: ['question', 'question', 'question'] + } +}; + +export function forElement(element) { + const cleanElement = (element || '').toLowerCase(); + return elementInfo[cleanElement] || elementInfo.unknown; +} + +export class SVG { + + static outerSVG(children, innerBounds, outerBounds, attrs = '', style = '') { + return `${children}`; + } + + static inlineSVG(children, bounds = lively.rect(0, 0, 10, 10), attrs = '', style = '') { + return `${children}`; + } + + /*MD ## Basic Shapes MD*/ + static circleRing(center, innerRadius, outerRadius, attrs) { + return `` + } + + static circle(center, radius, attrs) { + return `` + } + + /*MD ## Icons MD*/ + static elementGlyph(element, center, radius, attrs) { + const pathData = PathDataScaleCache.getPathData(element, lively.pt(2 * radius, 2 * radius)); + return `` + } + + static elementSymbol(element, center, radius) { + const { name: elementName, fill, stroke } = forElement(element); + const innerRadius = .9 * radius; + return `${SVG.circle(center, innerRadius, `fill="${fill}"`)} +${SVG.elementGlyph(elementName, center, innerRadius, `fill="${stroke}"`)} + ${SVG.circleRing(center, innerRadius, radius, `fill="${stroke}"`)}` + } +}