From 58668a34ef482b816b6cdb9f613aa9a94a37f54c Mon Sep 17 00:00:00 2001 From: onsetsu Date: Sat, 16 Dec 2023 18:28:48 +0100 Subject: [PATCH] added tags and reminder texts to UBG SQUASHED: AUTO-COMMIT-demos-stefan-fw-structure.md,AUTO-COMMIT-src-client-graphics.js,AUTO-COMMIT-src-components-widgets-ubg-cards-editor.js,AUTO-COMMIT-src-components-widgets-ubg-cards-entry.html,AUTO-COMMIT-src-components-widgets-ubg-cards-entry.js,AUTO-COMMIT-src-components-widgets-ubg-cards.html,AUTO-COMMIT-src-components-widgets-ubg-cards.js, --- src/client/graphics.js | 177 +++++---------- src/components/widgets/ubg-cards-editor.js | 6 +- src/components/widgets/ubg-cards-entry.html | 40 +++- src/components/widgets/ubg-cards-entry.js | 3 +- src/components/widgets/ubg-cards.html | 1 + src/components/widgets/ubg-cards.js | 235 ++++++++++++++++++-- 6 files changed, 308 insertions(+), 154 deletions(-) diff --git a/src/client/graphics.js b/src/client/graphics.js index 7ff7c27ef..640507290 100644 --- a/src/client/graphics.js +++ b/src/client/graphics.js @@ -35,16 +35,12 @@ export class Point { get isPoint() { return true } - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - // accessing - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + /*MD ## accessing MD*/ getX() { return this.x; } getY() { return this.y; } toPair() { return [this.x, this.y]; } - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - // arithmetic - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + /*MD ## arithmetic MD*/ toFixed(val) { return new Point(this.x.toFixed(val), this.y.toFixed(val)) } @@ -65,10 +61,6 @@ export class Point { return new Point(this.x, this.y + dy); } - midPt(p) { - return new Point((this.x + p.x) / 2, (this.y + p.y) / 2); - } - subPt(p) { return new Point(this.x - p.x, this.y - p.y); } @@ -77,9 +69,19 @@ export class Point { return new Point(this.x - dx, this.y - dy); } - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - // transforming - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + subX(dx) { + return new Point(this.x - dx, this.y); + } + + subY(dy) { + return new Point(this.x, this.y - dy); + } + + midPt(p) { + return new Point((this.x + p.x) / 2, (this.y + p.y) / 2); + } + + /*MD ## transforming MD*/ scaleBy(scaleX, scaleYOrUndefined) { return new Point(this.x * scaleX, this.y * (scaleYOrUndefined||scaleX)); } @@ -104,9 +106,7 @@ export class Point { return Point.polar(this.r(), this.theta() + angle) } - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - // comparing - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + /*MD ## comparing MD*/ lessPt(p) { return this.x < p.x && this.y < p.y; } @@ -123,9 +123,7 @@ export class Point { return this.x == p.x && this.y == p.y; } - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - // instance creation - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + /*MD ## instance creation MD*/ withX(x) { return pt(x, this.y); } @@ -149,9 +147,7 @@ export class Point { random() { return Point.random(this); } - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - // point functions - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + /*MD ## point functions MD*/ normalized() { var r = this.r(); return pt(this.x / r, this.y / r); @@ -186,9 +182,7 @@ export class Point { // return pt(this.x - (this.x % grid.x), this.y - (this.y % grid.y)) // } - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - // geometry computation - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + /*MD ## geometry computation MD*/ // roundTo(quantum) { // return new Point(num.roundTo(this.x, quantum), num.roundTo(this.y, quantum)); @@ -225,9 +219,7 @@ export class Point { return pt(x1 + (t * x21), y1 + (t * y21)); } - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - // vector math - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + /*MD ## vector math MD*/ magnitude() { return Math.hypot(this.x, this.y); @@ -272,9 +264,7 @@ export class Point { return pt(this.x * (1-t) + other.x * t, this.y * (1-t) + other.y * t); } - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - // polar coordinates - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + /*MD ## polar coordinates MD*/ r() { // Polar coordinates (theta=0 is East on screen, and increases in CCW // direction @@ -292,9 +282,7 @@ export class Point { theta() { return Math.atan2(this.y, this.x); } - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - // converting - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + /*MD ## converting MD*/ asRectangle() { return new Rectangle(this.x, this.y, 0, 0); } extent(ext) { return new Rectangle(this.x, this.y, ext.x, ext.y); } @@ -307,9 +295,7 @@ export class Point { toLiteral() { return {x: this.x, y: this.y}; } - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - // debugging - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + /*MD ## debugging MD*/ toString() { return "pt("+ this.x +"," + this.y + ")" // return string.format("pt(%1.f,%1.f)", this.x, this.y); @@ -321,9 +307,7 @@ export class Point { return
{this.toString()}
} - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - // serialization - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + /*MD ## serialization MD*/ // __serialize__() { // return {__expr__: this.toString(), bindings: {pt: "lively.graphics/geometry-2d.js"}} // } @@ -332,9 +316,7 @@ export class Point { export class Rectangle { - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - // factory methods - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + /*MD ## factory methods MD*/ static fromAny(ptA, ptB) { return rect(ptA.minPt(ptB), ptA.maxPt(ptB)); @@ -392,9 +374,7 @@ export class Rectangle { return new Rectangle(left, top, right - left, bottom - top); } - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - // initialize - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + /*MD ## initialize MD*/ get corners() { return ["topLeft","topRight","bottomRight","bottomLeft"]; } get sides() { return ["leftCenter","rightCenter","topCenter","bottomCenter"]; } @@ -407,17 +387,13 @@ export class Rectangle { get isRectangle() { return true } - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - // accessing - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + /*MD ## accessing MD*/ getX() { return this.x; } getY() { return this.y; } getWidth() { return this.width; } getHeight() { return this.height; } - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - // instance creation - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + /*MD ## instance creation MD*/ copy() { return new Rectangle(this.x, this.y, this.width, this.height); } @@ -513,9 +489,7 @@ export class Rectangle { orig.height*relRect.height); }); } - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - // converting - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + /*MD ## converting MD*/ toTuple() { return [this.x, this.y, this.width, this.height]; } @@ -529,9 +503,7 @@ export class Rectangle { return start && end && start.lineTo(end); } - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - // comparing - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + /*MD ## comparing MD*/ equals(other) { if (!other) { return false; @@ -539,14 +511,10 @@ export class Rectangle { return this.x == other.x && this.y == other.y && this.width == other.width && this.height == other.height; } - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - // debugging - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + /*MD ## debugging MD*/ inspect() { return JSON.stringify(this); } - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - // accessing - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + /*MD ## accessing MD*/ topLeft() { return new Point(this.x, this.y) } @@ -623,9 +591,7 @@ export class Rectangle { return points; } - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - // testing - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + /*MD ## testing MD*/ isNonEmpty(rect) { return this.width > 0 && this.height > 0; } @@ -642,9 +608,7 @@ export class Rectangle { return this.x <= p.x && p.x <= this.x + this.width && this.y <= p.y && p.y <= this.y + this.height; } - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - // transforming - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + /*MD ## transforming MD*/ translatedBy(d) { return new Rectangle(this.x + d.x, this.y + d.y, this.width, this.height); } @@ -748,9 +712,7 @@ export class Rectangle { (r.left() + r.right()), this.height + (r.top() + r.bottom())); } - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - // relations - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + /*MD ## relations MD*/ intersection(rect) { var nx = Math.max(this.x, rect.x); @@ -790,9 +752,7 @@ export class Rectangle { return pt(Math.min(Math.max(this.x, p.x), this.maxX()), Math.min(Math.max(this.y, p.y), this.maxY())); } - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - // properties - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + /*MD ## properties MD*/ maxX() { return this.x + this.width; } @@ -823,9 +783,7 @@ export class Rectangle { return pt.maxPt(this.topLeft()).minPt(this.bottomRight()); } - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - // SVG interface - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + /*MD ## SVG interface MD*/ // modeled after the CSS box model: http://www.w3.org/TR/REC-CSS2/box.html left() { return this.x; @@ -869,9 +827,7 @@ export class Rectangle { return this.center().y } - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - // part support - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + /*MD part support MD*/ partNamed(partName) { return this[partName].call(this); } @@ -902,16 +858,12 @@ export class Rectangle { return nearest; } - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - // printing - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + /*MD printing MD*/ toString() { return "rect(" + this.x +"," + this.y + "," +this.width +","+ this.height +")" } - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - // serialization - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + /*MD serialization MD*/ // __serialize__() { // return {__expr__: this.toString(), bindings: {rect: "lively.graphics/geometry-2d.js"}} // } @@ -963,9 +915,7 @@ export class Transform { this.f = this.ensureNumber(mx.f); } - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - // accessing - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + /*MD ## accessing MD*/ // getRotation() { // in degrees // // Note the ambiguity with negative scales is resolved by assuming @@ -1000,18 +950,14 @@ export class Transform { getTranslation() { return pt(this.e, this.f); } - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - // testing - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + /*MD ## testing MD*/ isTranslation() { // as specified in: // http://www.w3.org/TR/SVG11/coords.html#InterfaceSVGTransform return (this.a==1 && this.b==0 && this.c==0 && this.d==1) } - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - // converting - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + /*MD ## converting MD*/ toSVGAttributeValue() { var delta = this.getTranslation(), attr = "translate(" + delta.x + "," + delta.y +")", @@ -1064,9 +1010,7 @@ export class Transform { toMatrix() { return this.copy(); } - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - // transforming - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + /*MD ## transforming MD*/ transformPoint(p, acc) { return p.matrixTransform(this, acc); } transformDirection(p, acc) { @@ -1095,9 +1039,7 @@ export class Transform { } - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - // matrix operations - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + /*MD ## matrix operations MD*/ preConcatenate(t) { var m = this.matrix_ || this.toMatrix(); this.a = t.a * m.a + t.c * m.b; @@ -1133,9 +1075,7 @@ export class Transform { return result; } - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - // helper - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + /*MD ## helper MD*/ ensureNumber(value) { // note that if a,b,.. f are not numbers, it's usually a // problem, which may crash browsers (like Safari) that don't @@ -1144,9 +1084,7 @@ export class Transform { return value; } - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - // serialization - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + /*MD ## serialization MD*/ // __serialize__() { // return { // __expr__: `new Transform({a: ${this.a}, b: ${this.b}, c: ${this.c}, d: ${this.d}, e: ${this.e}, f: ${this.f}})`, @@ -1169,9 +1107,7 @@ export class Line { get isLine() { return true } - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - // accessing - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + /*MD ## accessing MD*/ sampleN(n) { // return n points that are collinear with this and are between // this.start and this.end @@ -1193,9 +1129,7 @@ export class Line { return this.start.dist(this.end); } - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - // testing - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + /*MD ## testing MD*/ equals(otherLine) { if (!otherLine) return false; return this.start.eqPt(otherLine.start) && this.end.eqPt(otherLine.end); @@ -1219,9 +1153,7 @@ export class Line { return xMin <= x3 && x3 <= xMax && yMin < y3 && y3 <= yMax; } - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - // intersection - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + /*MD ## intersection MD*/ intersection(otherLine, constrained=true) { // returns true if the line from (a,b)->(c,d) intersects with (p,q)->(r,s) @@ -1272,9 +1204,7 @@ export class Line { } - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - // debugging - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + /*MD ## debugging MD*/ toString() { return 'Line((' + this.start.x + ',' + this.start.y +'), ('+ this.end.x+','+this.end.y+'))' @@ -1283,9 +1213,7 @@ export class Line { // this.end.x, this.end.y) } - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - // serialization - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + /*MD ## serialization MD*/ // __serialize__() { // return { // __expr__: `Line.fromCoords(${this.start.x}, ${this.start.y}, ${this.end.x}, ${this.end.y})`, @@ -1295,6 +1223,7 @@ export class Line { } +/*MD # Factory Methods MD*/ export function rect(arg1, arg2, arg3, arg4) { // arg1 and arg2 can be location and corner or // arg1/arg2 = location x/y and arg3/arg4 = extent x/y diff --git a/src/components/widgets/ubg-cards-editor.js b/src/components/widgets/ubg-cards-editor.js index 565320e38..47500a362 100644 --- a/src/components/widgets/ubg-cards-editor.js +++ b/src/components/widgets/ubg-cards-editor.js @@ -579,7 +579,7 @@ export default class UBGCardsEditor extends Morph { async delayedUpdateCardPreview() { this.setAttribute('preview-queued', true); - this._delayedUpdateCardPreview = this._delayedUpdateCardPreview || _.debounce(() => this.updateCardPreview(), 150); + this._delayedUpdateCardPreview = this._delayedUpdateCardPreview || _.debounce(() => this.updateCardPreview(), 500); this._delayedUpdateCardPreview(); } @@ -989,6 +989,10 @@ export default class UBGCardsEditor extends Morph { } } + focusOnText() { + this.$text.focus() + } + livelyMigrate(other) { this.src = other.src; } diff --git a/src/components/widgets/ubg-cards-entry.html b/src/components/widgets/ubg-cards-entry.html index 47b549e26..e8c10f793 100644 --- a/src/components/widgets/ubg-cards-entry.html +++ b/src/components/widgets/ubg-cards-entry.html @@ -10,7 +10,7 @@ #root { position: relative; background: rgba(150, 150, 150, .2); - border-bottom: 1px solid green; +/* border-bottom: 1px solid green; */ } :host(.is-bad) { @@ -31,14 +31,34 @@ outline: 1px solid rgba(150,150,255,0.3); } - :host(:hover) #root { - background-color: rgba(150,250,255,0.1); - outline: 1px solid rgba(150,150,255,0.3); - } - - :host([selected]) #root { - background-color: rgba(150,150,255,0.1); - outline: 1px solid rgba(150,150,255,0.3); + #inner-background { + position: absolute; + z-index: -100; + top: 0; + right: 0; + bottom: 0; + left: 0; + border: 2px solid transparent; + } + :host(:hover) #inner-background { +/* border-color: orange; */ + background: rgba(255,255,0,.3); + } + :host([selected]) #inner-background { +/* border-color: green; */ + background: rgba(0,255,0,.3); + } + :host(:hover):host([selected]) #inner-background { +/* border-color: color-mix( + in srgb, + green 70%, + orange + ); */ + background: color-mix( + in srgb, + rgba(0,255,0,.3) 70%, + rgba(255,255,0,.3) + ); } #content { @@ -148,9 +168,9 @@ #art { font-size: x-small; } -
+
diff --git a/src/components/widgets/ubg-cards-entry.js b/src/components/widgets/ubg-cards-entry.js index 6266ef2f3..8d84c85f3 100644 --- a/src/components/widgets/ubg-cards-entry.js +++ b/src/components/widgets/ubg-cards-entry.js @@ -147,7 +147,8 @@ export default class UBGCardEntry extends Morph { const cost = card.getCost(); const text = card.getText(); const notes = card.getNotes(); - const aspects = [id, name, cardType, element, cost, text, notes]; + const tags = card.getTags().join(' '); + const aspects = [id, name, cardType, element, cost, text, notes, tags]; const matching = aspects.some(aspect => (aspect + '').toLowerCase().match(new RegExp(filter, 'gmi'))); diff --git a/src/components/widgets/ubg-cards.html b/src/components/widgets/ubg-cards.html index d03787af2..b5e90fa46 100644 --- a/src/components/widgets/ubg-cards.html +++ b/src/components/widgets/ubg-cards.html @@ -141,6 +141,7 @@ + diff --git a/src/components/widgets/ubg-cards.js b/src/components/widgets/ubg-cards.js index 4de6d44b1..f5ac00032 100644 --- a/src/components/widgets/ubg-cards.js +++ b/src/components/widgets/ubg-cards.js @@ -344,7 +344,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) + printedRules = this.renderReminderText(printedRules, cardEditor, cardDesc) printedRules = printedRules.replace(/blitz/gmi, ''); printedRules = printedRules.replace(/\btap\b/gmi, ''); @@ -373,33 +373,179 @@ ${SVG.elementSymbol(others[2], lively.pt(12.5, 8.5), 1.5)}`, lively.rect(0, 0, 1 return this.renderToDoc(ruleBox, insetTextBy, printedRules, beforeRenderRules, doc) } - static renderReminderText(printedRules) { + static renderReminderText(printedRules, cardEditor, cardDesc) { function italic(text) { return `${text}` } - return printedRules.replace(/\bremind(?:er)?(\w+)\b/gmi, (match, keyword, offset, string, groups) => { + return printedRules.replace(/\bremind(?:er)?(\w+(?:\-(\w|\(|\))+)*)\b/gmi, (match, myMatch, offset, string, groups) => { const keywords = { - quest: 'As a free action, you may gain this if you fulfill its condition.', - actionquest: 'You may gain this when you perform the action.', - countingquest: 'If you fulfill its condition (track with ()), as a free action you may trash this to gain an Achievement Token.', + quest: () => { + return 'As a free action, you may gain this if you fulfill its condition.' + }, + actionquest: () => { + return 'You may gain this when you perform the action.' + }, + countingquest: () => { + return 'If you fulfill its condition (track with ()), as a free action you may trash this to gain an Achievement Token.' + }, - instant: 'You may buy this as a free action.', - quickcast: 'Blitz You may cast this.', - quickcastall: 'Blitz You may cast it.', - emerge: 'Passive As a free action, you may buy this by trashing a card for a discount equal to its cost.', - emergeall: 'Passive As a free action, you may buy a card by trashing a card for a discount equal to its cost.', + instant: () => { + return 'You may buy this as a free action.' + }, - invoke: 'You may trash this from hand or field to gain the effect.' + flashback: (...args) => { + return 'Passive As a free action, you may trash this to exec its blitz effects.' + }, + + quickcast: (...args) => { + if (args.includes('all')) { + // keyword granted + return 'Blitz You may cast it.' + } + + return 'Blitz You may cast this.' + }, + + emerge: (...args) => { + if (args.includes('all')) { + // keyword granted + return 'Passive As a free action, you may buy a card by trashing a card for a discount equal to its cost.' + } + + if (args.includes('one')) { + // keyword granted + return 'Passive As a free action, you may buy the card by trashing a card for a discount equal to its cost.' + } + + return 'Passive As a free action, you may buy this by trashing a card for a discount equal to its cost.' + }, + + invoke: () => { + return 'You may trash this from hand or field to gain the effect.' + }, + + resonance: (...args) => { + if (args.includes('all')) { + // keyword granted + return 'While a card\'s element is called, you may cast it along your main spell.' + } + if (args.includes('one')) { + // keyword granted + return 'While that card\'s element is called, you may cast it along your main spell.' + } + const elements = cardEditor.getElementsFromCard(cardDesc, false) + let elementString; + if (elements.length === 0 || (elements.length === 1 && elements.first === 'gray')) { + elementString = 'this card\'s element'; + } else if (elements.length === 1) { + elementString = elements.first; + } else { + elementString = `${elements.slice(0, -1).join(', ')} or ${elements.last}`; + } + + // Goal: While wind is called, you may cast this as a free action. + // While wind is called, you may cast this along another spell. + return `While ${elementString} is called, you may cast this along your main spell.` + }, + + brittle: (...args) => { + if (args.includes('all')) { + // keyword granted + return 'Trash brittle cards after casting them.' + } + + return 'Trash this after casting it.' + }, + + postpone: (cost, delay) => { + 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, gain this.` + }, + + blueprint: (cost) => { + 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.` + }, + + saga: (...args) => { + return 'Blitz and Start of Turn Put (1) here. Then, exec the corresponding chapter\'s effect.' + }, + + affinity: (...args) => { + let subject = 'This costs' + if (args.includes('all')) { + args = args.filter(arg => arg !== 'all') + // keyword granted + subject = 'They cost' + } + + if (args.includes('power')) { + return subject + ' (x) less.' + } + + if (args.includes('vpchips')) { + return subject + ' (1) less per collected vp.' + } + + if (args.includes('coins')) { + return subject + ' (1) less per () you have.' + } + + if (args.includes('cards')) { + return subject + ' (1) less for each of those cards.' + } + + if (args.includes('mana')) { + const elements = args.filter(arg => arg !== 'mana') + let elementString + if (elements.length === 1) { + elementString = elements.first; + } else { + elementString = `${elements.slice(0, -1).join(', ')} or ${elements.last}`; + } + return subject + ` (1) less for each mana on ${elementString}.` + } + + throw new Error('unspecified type of Affinity') + }, + + stuncounter: (...args) => { + return 'Casting a card with a stun counter removes the counter instead of the effect.' + }, + + cycle: (...args) => { + return 'To cycle a card, trash it to gain a card of equal or lower cost.' + }, + + cycling: (cost) => { + if (cost) { + return `Passive As a free action, you may pay (${cost}) and trash this to gain a card of equal or lower cost.` + } + return `Passive As a free action, you may trash this to gain a card of equal or lower cost.` + }, + + upgrade: (diff, who) => { + let whoText = 'this' + if (who === 'one') { + whoText = 'the card' + } + + return `To upgrade, trash ${whoText} to gain a card costing up to (${diff}) more.` + }, + + evoke: (cost) => { + return `As a free action, pay (${cost}) and trash this from hand to exec its blitz effects.` + }, }; + const modifiers = myMatch.split('-') + const keyword = modifiers.shift() const reminderText = keywords[keyword.toLowerCase()]; if (!reminderText) { lively.error(keyword, 'unknown reminder text') return `unknown reminder text '${keyword}''`; } - return italic(`(${reminderText})`); + return italic(`(${reminderText(...modifiers)})`); }); } @@ -434,7 +580,7 @@ ${SVG.circleRing(center, 4.75, 5, `fill="darkviolet"`)} ${text}`); } - return printedRules.replace(/\(([*0-9x+-]*)\)/gmi, function replacer(match, p1, offset, string, groups) { + return printedRules.replace(/\(([*0-9xy+-]*)\)/gmi, function replacer(match, p1, offset, string, groups) { return coin(p1); }); } @@ -705,11 +851,15 @@ export default class Cards extends Morph { return; } - if (evt.ctrlKey && evt.key == "/") { + if (evt.ctrlKey && !evt.repeat && evt.key == "/") { evt.stopPropagation(); evt.preventDefault(); - this.filter.select(); + if (this.filter.matches(':focus')) { + this.editor.focusOnText() + } else { + this.filter.select(); + } return; } @@ -1179,6 +1329,10 @@ export default class Cards extends Morph { await this.renderRuleText(doc, cardDesc, ruleBox, { insetTextBy: 2 }); + + // tags + const tagsAnchor = ruleBox.topRight().subY(1); + await this.renderTags(doc, cardDesc, tagsAnchor) } async renderFullBleedStyle(doc, cardDesc, outsideBorder, assetsInfo) { @@ -1287,6 +1441,10 @@ export default class Cards extends Morph { const typeAndElementAnchor = ruleTextBox.topLeft().addY(-4); await this.renderTypeAndElement(doc, cardDesc, typeAndElementAnchor, BOX_FILL_COLOR, BOX_FILL_OPACITY) + // tags + const tagsAnchor = ruleTextBox.topRight(); + await this.renderTags(doc, cardDesc, tagsAnchor) + // id this.renderId(doc, cardDesc, outsideBorder, innerBorder) } @@ -1342,11 +1500,12 @@ export default class Cards extends Morph { // rule text const RULE_TEXT_INSET = 2; + let effectiveRuleBox const ruleTextBox = await this.renderRuleText(doc, cardDesc, ruleBox, { insetTextBy: RULE_TEXT_INSET, beforeRenderRules: ruleTextBox => { // rule box render - const effectiveRuleBox = ruleTextBox.insetBy(-RULE_TEXT_INSET) + effectiveRuleBox = ruleTextBox.insetBy(-RULE_TEXT_INSET) this.withinCardBorder(doc, outsideBorder, () => { doc::withGraphicsState(() => { doc.setGState(new doc.GState({ opacity: BOX_FILL_OPACITY })); @@ -1367,6 +1526,10 @@ export default class Cards extends Morph { const typeAndElementAnchor = ruleTextBox.topLeft().addY(-8); await this.renderTypeAndElement(doc, cardDesc, typeAndElementAnchor, BOX_FILL_COLOR, BOX_FILL_OPACITY) + // tags + const tagsAnchor = lively.pt(ruleTextBox.right(), effectiveRuleBox.top()).subY(1); + await this.renderTags(doc, cardDesc, tagsAnchor) + // id this.renderId(doc, cardDesc, outsideBorder, innerBorder) } @@ -1419,11 +1582,12 @@ export default class Cards extends Morph { // rule text const RULE_TEXT_INSET = 2; + let effectiveRuleBox const ruleTextBox = await this.renderRuleText(doc, cardDesc, ruleBox, { insetTextBy: RULE_TEXT_INSET, beforeRenderRules: ruleTextBox => { // rule box render - const effectiveRuleBox = ruleTextBox.insetBy(-RULE_TEXT_INSET) + effectiveRuleBox = ruleTextBox.insetBy(-RULE_TEXT_INSET) this.withinCardBorder(doc, outsideBorder, () => { doc::withGraphicsState(() => { doc.setGState(new doc.GState({ opacity: BOX_FILL_OPACITY })); @@ -1444,6 +1608,10 @@ export default class Cards extends Morph { const typeAndElementAnchor = ruleTextBox.topLeft().addY(-8); await this.renderTypeAndElement(doc, cardDesc, typeAndElementAnchor, BOX_FILL_COLOR, BOX_FILL_OPACITY) + // tags + const tagsAnchor = lively.pt(ruleTextBox.right(), effectiveRuleBox.top()).subY(1); + await this.renderTags(doc, cardDesc, tagsAnchor) + // id this.renderId(doc, cardDesc, outsideBorder, innerBorder) } @@ -1621,6 +1789,21 @@ export default class Cards extends Morph { }) } + renderTags(doc, cardDesc, tagsAnchor) { + const tags = cardDesc.getTags().sortBy(i => i, false).map(tag => '#' + tag); + doc::withGraphicsState(() => { + const FONT_SIZE = 7; + doc.setFontSize(FONT_SIZE); + // text dimensions only work well for single-line text + const { w, h } = doc.getTextDimensions(tags.first || ''); + doc.setTextColor('black'); + for (let text of tags) { + doc.text(text, ...tagsAnchor.toPair(), { align: 'right', baseline: 'bottom' }); + tagsAnchor = tagsAnchor.subY(h) + } + }); + } + async renderElementSymbol(doc, element, pos, radius) { const svgInnerPos = lively.pt(5, 5); const svgInnerRadius = 5; @@ -1669,6 +1852,12 @@ export default class Cards extends Morph { }); } + 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) } @@ -1977,6 +2166,16 @@ export default class Cards extends Morph { await this.printForExport(this.cards, evt.shiftKey); } + async onPrintSelected(evt) { + if (!this.cards) { + return; + } + + const filteredEntries = this.allEntries.filter(entry => !entry.classList.contains('hidden')) + const cardsToPrint = filteredEntries.map(entry => entry.card) + await this.printForExport(cardsToPrint, evt.shiftKey); + } + async onPrintChanges(evt) { if (!this.cards) { return;