From bb50e7315c7fc5cc67bd54a1bde3684b40a61324 Mon Sep 17 00:00:00 2001 From: onsetsu Date: Sun, 14 Jan 2024 14:36:56 +0100 Subject: [PATCH] paperjs wrapper, ubg cards range SQUASHED: AUTO-COMMIT-src-client-boot.js,AUTO-COMMIT-src-client-paperjs-wrapper.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,AUTO-COMMIT-templates-livelystyle.css, --- src/client/paperjs-wrapper.js | 8 + src/components/widgets/ubg-cards-entry.js | 15 +- src/components/widgets/ubg-cards.html | 11 +- src/components/widgets/ubg-cards.js | 467 +++++++++++++--------- templates/livelystyle.css | 4 + 5 files changed, 317 insertions(+), 188 deletions(-) create mode 100644 src/client/paperjs-wrapper.js diff --git a/src/client/paperjs-wrapper.js b/src/client/paperjs-wrapper.js new file mode 100644 index 000000000..f489f4738 --- /dev/null +++ b/src/client/paperjs-wrapper.js @@ -0,0 +1,8 @@ +/* global paper */ + +const paperJSURL = lively4url + '/src/external/paper-core.js'; +await lively.loadJavaScriptThroughDOM("paper-core.js", paperJSURL); +const canvas = document.createElement('canvas') +paper.setup(canvas); + +export {paper as default}; diff --git a/src/components/widgets/ubg-cards-entry.js b/src/components/widgets/ubg-cards-entry.js index 011fa7e9c..f94e602ec 100644 --- a/src/components/widgets/ubg-cards-entry.js +++ b/src/components/widgets/ubg-cards-entry.js @@ -121,7 +121,7 @@ export default class UBGCardEntry extends Morph { keep: 'green', unsure: 'yellow', remove: 'red', - }[card.getRating()] || 'gray'); + }[card.getRating()] || 'lightgray'); id.innerHTML = card.id || '???'; const type = v.type && v.type.toLowerCase(); @@ -142,6 +142,19 @@ export default class UBGCardEntry extends Morph { this.get('#art').innerHTML = card.getArtDirection() || '-'; } + isVisible() { + return !this.classList.contains('hidden') && !this.classList.contains('out-of-range') + } + + updateToRange(start, end) { + const card = this.card; + const id = +card.getId(); + + const inRange = (!start || start <= id) && (!end || id <= end); + this.classList.toggle('in-range', inRange); + this.classList.toggle('out-of-range', !inRange); + } + updateToFilter(filter) { filter = filter.toLowerCase(); diff --git a/src/components/widgets/ubg-cards.html b/src/components/widgets/ubg-cards.html index b5e90fa46..327e6cac0 100644 --- a/src/components/widgets/ubg-cards.html +++ b/src/components/widgets/ubg-cards.html @@ -132,17 +132,22 @@ background: var(--sort-background); } + input#rangeStart, input#rangeEnd { + width: 4ex; + }
+ + + - - - + + diff --git a/src/components/widgets/ubg-cards.js b/src/components/widgets/ubg-cards.js index b4a21d559..8aed38301 100644 --- a/src/components/widgets/ubg-cards.js +++ b/src/components/widgets/ubg-cards.js @@ -7,6 +7,8 @@ import "src/external/pdf.js"; import { shake } from 'utils'; import { Point } from 'src/client/graphics.js' +import paper from 'src/client/paperjs-wrapper.js' + import { serialize, deserialize } from 'src/client/serialize.js'; import Card from 'demos/stefan/untitled-board-game/ubg-card.js'; @@ -257,6 +259,7 @@ const hedronSVG = do { 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; - - - - + + + + ; }; @@ -315,6 +318,8 @@ const SORT_BY = { const VP_FILL = 'violet'; const VP_STROKE = '#9400d3'; // darkviolet +const VP_FILL_ZERO = '#ddd'; +const VP_STROKE_ZERO = 'gray'; const AFFECT_ALL_COLOR = 'rgba(255, 0, 0, 0.2)'; import 'src/external/dom-to-image.js' @@ -421,6 +426,7 @@ ${SVG.elementSymbol(others[2], lively.pt(12.5, 8.5), 1.5)}`, lively.rect(0, 0, 1 printedRules = this.renderCoinIcon(printedRules) printedRules = this.renderBracketIcon(printedRules) + printedRules = this.renderKeywords(printedRules) printedRules = this.renderHedronIcon(printedRules) return this.renderToDoc(ruleBox, insetTextBy, printedRules, beforeRenderRules, doc) @@ -433,33 +439,85 @@ ${SVG.elementSymbol(others[2], lively.pt(12.5, 8.5), 1.5)}`, lively.rect(0, 0, 1 return printedRules.replace(/\bremind(?:er)?(\w+(?:\-(\w|\(|\))+)*)\b/gmi, (match, myMatch, offset, string, groups) => { const keywords = { - quest: () => { - return 'As a free action, you may play this if you fulfill its condition.' - }, actionquest: () => { return 'You may play this when you perform the action.' }, - countingquest: () => { - return 'If you fulfill its condition (track with ()), as a free action you may trash this to create an Achievement Token.' - }, - instant: () => { - return 'You may buy this as a free action.' + 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') }, - flashback: (...args) => { - return 'Passive As a free action, you may trash this to exec its blitz effects.' + 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.` }, - quickcast: (...args) => { + brittle: (...args) => { if (args.includes('all')) { // keyword granted - return 'Blitz You may cast it.' + return 'Trash brittle cards after casting them.' } - return 'Blitz You may cast this.' + return 'Trash this after casting it.' }, + + countingquest: () => { + return 'If you fulfill its condition (track with ()), as a free action you may trash this to create an Achievement Token.' + }, + + cycle: (...args) => { + return 'To cycle a card, trash it to play a card of equal or lower cost.' + }, + + cycling: (cost, who) => { + let whoToPrint = 'this' + if (who === 'acard') { + whoToPrint = 'a card' + } + if (cost) { + return `Passive As a free action, you may pay (${cost}) and trash ${whoToPrint} to play a card of equal or lower cost.` + } + return `Passive As a free action, you may trash ${whoToPrint} to play a card of equal or lower cost.` + }, + + discover: (howMany) => { + return `To discover ${howMany}, reveal top ${howMany} cards of any piles. Add 1 to your hand, trash the rest.` + }, + emerge: (...args) => { if (args.includes('all')) { // keyword granted @@ -473,11 +531,54 @@ ${SVG.elementSymbol(others[2], lively.pt(12.5, 8.5), 1.5)}`, lively.rect(0, 0, 1 return 'Passive As a free action, you may buy this by trashing a card for a discount equal to its cost.' }, - + + evoke: (cost, who) => { + if (who === 'all') { + return `As a free action, pay the cost and trash a card from hand to exec its blitz effects.` + } + if (who === 'one') { + return `As a free action, pay the cost and trash that card from hand to exec its blitz effects.` + } + return `As a free action, pay (${cost}) and trash this from hand to exec its blitz effects.` + }, + + flashback: (...args) => { + return 'Passive As a free action, you may trash this to exec its blitz effects.' + }, + + instant: () => { + return 'You may buy this as a free action.' + }, + invoke: () => { return 'You may trash this from hand or field to exec the effect.' }, + manaburst: () => { + return 'Only activate manaburst abilities if x is 4+.' + }, + + meld: () => { + return 'The melded card has all effects and combined stats (element, vp, type) of its pieces.' + }, + + 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, play this.` + }, + + quest: () => { + return 'As a free action, you may play this if you fulfill its condition.' + }, + + quickcast: (...args) => { + if (args.includes('all')) { + // keyword granted + return 'Blitz You may cast it.' + } + + return 'Blitz You may cast this.' + }, + resonance: (...args) => { if (args.includes('all')) { // keyword granted @@ -506,85 +607,13 @@ ${SVG.elementSymbol(others[2], lively.pt(12.5, 8.5), 1.5)}`, lively.rect(0, 0, 1 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, play 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 play a card of equal or lower cost.' - }, - - cycling: (cost, who) => { - let whoToPrint = 'this' - if (who === 'acard') { - whoToPrint = 'a card' - } - - if (cost) { - return `Passive As a free action, you may pay (${cost}) and trash ${whoToPrint} to play a card of equal or lower cost.` - } - return `Passive As a free action, you may trash ${whoToPrint} to play a card of equal or lower cost.` - }, - upgrade: (diff, who) => { let whoText = 'this' if (who === 'one') { @@ -593,20 +622,6 @@ ${SVG.elementSymbol(others[2], lively.pt(12.5, 8.5), 1.5)}`, lively.rect(0, 0, 1 return `To upgrade, trash ${whoText} to play a card costing up to (${diff}) more.` }, - - discover: (howMany) => { - return `To discover ${howMany}, reveal top ${howMany} cards of any piles. Add 1 to your hand, trash the rest.` - }, - - evoke: (cost, who) => { - if (who === 'all') { - return `As a free action, pay the cost and trash a card from hand to exec its blitz effects.` - } - if (who === 'one') { - return `As a free action, pay the cost and trash that card from hand to exec its blitz effects.` - } - return `As a free action, pay (${cost}) and trash this from hand to exec its blitz effects.` - }, }; const modifiers = myMatch.split('-') @@ -621,6 +636,17 @@ ${SVG.elementSymbol(others[2], lively.pt(12.5, 8.5), 1.5)}`, lively.rect(0, 0, 1 }); } + static renderKeywords(printedRules) { + function makeBold(text) { + return `${text}` + } + + printedRules = printedRules.replace(/manaburst:?/gmi, (match, pElement, offset, string, groups) => makeBold(match)); + printedRules = printedRules.replace(/\b(un)?meld(ed)?\b/gmi, (match, pElement, offset, string, groups) => makeBold(match)); + + return printedRules + } + static renderElementIcon(printedRules) { function inlineElement(element) { return SVG.inlineSVG(SVG.elementSymbol(element, lively.pt(5, 5), 5)); @@ -806,6 +832,8 @@ export default class Cards extends Morph { this.addEventListener('contextmenu', evt => this.onMenuButton(evt), false); this.filter.value = this.filterValue; + this.rangeStart.value = this.rangeStartValue; + this.rangeEnd.value = this.rangeEndValue; this.updateView(); lively.html.registerKeys(this); @@ -814,29 +842,66 @@ export default class Cards extends Morph { this.cardFrameStyle.addEventListener('input', evt => this.updateCardInEditor(this.card), false); for (let eventName of ['input']) { this.filter.addEventListener(eventName, evt => this.filterChanged(evt), false); + this.rangeStart.addEventListener(eventName, evt => this.rangeChanged(evt), false); + this.rangeEnd.addEventListener(eventName, evt => this.rangeChanged(evt), false); } } - + /*MD ## Filter MD*/ get filter() { return this.get('#filter'); } - get filterValue() { return this.getAttribute('filter-Value') || ''; } - set filterValue(value) { this.setAttribute('filter-Value', value); } + get rangeStart() { + return this.get('#rangeStart'); + } + get rangeStartValue() { + return this.getAttribute('rangeStart-Value') || ''; + } + set rangeStartValue(value) { + this.setAttribute('rangeStart-Value', value); + } + get rangeEnd() { + return this.get('#rangeEnd'); + } + get rangeEndValue() { + return this.getAttribute('rangeEnd-Value') || ''; + } + set rangeEndValue(value) { + this.setAttribute('rangeEnd-Value', value); + } + + rangeChanged(evt) { + this.rangeStartValue = this.rangeStart.value; + this.rangeEndValue = this.rangeEnd.value; + + this.updateItemsToRange(); + this.updateSelectedItemToFilterAndRange(); + return; + } + filterChanged(evt) { this.filterValue = this.filter.value; + this.updateItemsToFilter(); - this.updateSelectedItemToFilter(); + this.updateSelectedItemToFilterAndRange(); return; } + updateItemsToRange() { + const start = this.rangeStartValue; + const end = this.rangeEndValue; + this.allEntries.forEach(entry => { + entry.updateToRange(start, end); + }); + } + updateItemsToFilter() { const filterValue = this.filterValue; this.allEntries.forEach(entry => { @@ -844,10 +909,10 @@ export default class Cards extends Morph { }); } - updateSelectedItemToFilter() { + updateSelectedItemToFilterAndRange() { const selectedEntry = this.selectedEntry; if (selectedEntry) { - if (selectedEntry.classList.contains('hidden')) { + if (!selectedEntry.isVisible()) { selectedEntry.classList.remove('selected'); const downwards = this.findNextVisibleItem(selectedEntry, false, false); if (downwards) { @@ -909,7 +974,7 @@ export default class Cards extends Morph { // might be -1, if no reference item is given (which start the search from the beginning) const referenceIndex = listItems.indexOf(referenceItem); - const firstPass = listItems.find((item, index) => index > referenceIndex && !item.classList.contains('hidden')); + const firstPass = listItems.find((item, index) => index > referenceIndex && item.isVisible()); if (firstPass) { return firstPass; } @@ -918,7 +983,7 @@ export default class Cards extends Morph { return; } - return listItems.find((item, index) => index <= referenceIndex && !item.classList.contains('hidden')); + return listItems.find((item, index) => index <= referenceIndex && item.isVisible()); } async onKeyDown(evt) { @@ -938,6 +1003,18 @@ export default class Cards extends Morph { return; } + if (evt.ctrlKey && !evt.repeat && evt.key == "p") { + evt.stopPropagation(); + evt.preventDefault(); + + if (evt.altKey) { + this.onPrintChanges(evt) + } else { + this.onPrintSelected(evt) + } + return; + } + if (evt.ctrlKey && evt.key == "s") { evt.stopPropagation(); evt.preventDefault(); @@ -1572,10 +1649,6 @@ export default class Cards extends Morph { insetTextBy: 2 }); - // type & elements - 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) @@ -1657,10 +1730,6 @@ export default class Cards extends Morph { } }); - // type & elements - 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) @@ -1739,10 +1808,6 @@ export default class Cards extends Morph { } }); - // type & elements - 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) @@ -1779,10 +1844,10 @@ export default class Cards extends Morph { // doc.rect(...border::xYWidthHeight(), 'F'); // }); + // title bar doc::withGraphicsState(() => { const [BOX_FILL_COLOR, BOX_STROKE_COLOR, BOX_FILL_OPACITY] = this.colorsForCard(cardDesc); - doc.setGState(new doc.GState({ opacity: .5 })); doc.setFillColor(BOX_FILL_COLOR); doc.setDrawColor(BOX_STROKE_COLOR); @@ -1801,23 +1866,37 @@ export default class Cards extends Morph { }); }); + const coinCenter = coinLeftCenter.addX(costCoinRadius); + await this.renderInHandSymbols(doc, cardDesc, border, costCoinRadius, costCoinMargin, coinCenter) + } + + async renderInHandSymbols(doc, cardDesc, border, costCoinRadius, costCoinMargin, coinCenter) { + let currentCenter = coinCenter; + // cost - const coinCenter = coinLeftCenter.addXY(costCoinRadius, 0); - this.renderCost(doc, cardDesc, coinCenter, costCoinRadius) - - // vp - const vpCenter = coinCenter.addY(costCoinRadius * 2.75); - this.renderBaseVP(doc, cardDesc, vpCenter, costCoinRadius) - - // element (list) - let elementCenter = vpCenter.addY(costCoinRadius * 2.75); - const lastElementCenter = elementCenter.addY(costCoinRadius * 3.5); - const elements = this.getElementsFromCard(cardDesc, true); - let i = 0; - for (let element of elements) { - await this.renderElementSymbol(doc, element, elementCenter.lerp(lastElementCenter, i), costCoinRadius) - i += 1 / elements.length + this.renderCost(doc, cardDesc, currentCenter, costCoinRadius) + + if ((cardDesc.getType() || '').toLowerCase() !== 'character') { + // vp + currentCenter = currentCenter.addY(costCoinRadius * 2.75); + this.renderBaseVP(doc, cardDesc, currentCenter, costCoinRadius) + + // element (list) + currentCenter = currentCenter.addY(costCoinRadius * 2.75); + const elements = this.getElementsFromCard(cardDesc, true); + for (let element of elements) { + await this.renderElementSymbol(doc, element, currentCenter, costCoinRadius) + currentCenter = currentCenter.addY(costCoinRadius * .75); + } + currentCenter = currentCenter.addY(costCoinRadius * .25); + } 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); + await this.renderType(doc, cardDesc, currentCenter, BOX_FILL_COLOR, BOX_FILL_OPACITY) } renderCost(doc, cardDesc, pos, coinRadius) { @@ -1841,19 +1920,16 @@ export default class Cards extends Morph { renderBaseVP(doc, cardDesc, pos, coinRadius) { const costSize = coinRadius / 4; - const vp = cardDesc.getBaseVP(); - if (!vp) { - return; - } + const vp = cardDesc.getBaseVP() || 0; const iconCenter = pos; doc::withGraphicsState(() => { doc.setGState(new doc.GState({ opacity: 0.9 })) // doc.setFillColor('#b8942d'); - doc.setDrawColor(VP_STROKE) + doc.setDrawColor(vp === 0 ? VP_STROKE_ZERO : VP_STROKE) doc.setLineWidth(0.2 * costSize) // doc.circle(...coinCenter.toPair(), coinRadius, 'DF'); - doc.setFillColor(VP_FILL) + doc.setFillColor(vp === 0 ? VP_FILL_ZERO : VP_FILL) // doc.rect(coinCenter.x - coinRadius, coinCenter.y - coinRadius, 2 * coinRadius, 2 * coinRadius, 'DF'); // diamond shape @@ -1886,37 +1962,35 @@ export default class Cards extends Morph { return RuleTextRenderer.renderRuleText(this, cardDesc, doc, ruleBox, options) } - // type & elements - async renderTypeAndElement(doc, cardDesc, anchorPt, color, opacity) { - const typeAndElementAnchor = anchorPt - let textHeight + // type + async renderType(doc, cardDesc, anchorPt, color, opacity) { + // const typeAndElementAnchor = anchorPt doc::withGraphicsState(() => { doc.setGState(new doc.GState({ opacity: opacity })); doc.setFillColor(color); - function curate() { - return this.toLower().upperFirst(); - } - function prepend(other) { - return other + ' ' + this; - } - const element = cardDesc.getElement(); + // 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()) - } + // if (Array.isArray(element)) { + // element.forEach(element => { + // fullText = fullText::prepend(element::curate()) + // }) + // } else if (element) { + // fullText = fullText::prepend(element::curate()) + // } doc.setFontSize(7); - const { w, h } = doc.getTextDimensions(fullText); - textHeight = h + const { w, h: textHeight } = doc.getTextDimensions(fullText); - const typeElementTextBox = typeAndElementAnchor.extent(lively.pt(w, h)) + const typeElementTextBox = anchorPt.subX(w/2).extent(lively.pt(w, textHeight)) const typeElementTextBoxExpansion = 1 const typeElementBox = typeElementTextBox.expandBy(typeElementTextBoxExpansion) - const roundedCorner = h/2 + typeElementTextBoxExpansion + const roundedCorner = textHeight/2 + typeElementTextBoxExpansion doc.roundedRect(...typeElementBox::xYWidthHeight(), roundedCorner, roundedCorner, 'F'); doc.setTextColor('000'); @@ -2157,6 +2231,21 @@ export default class Cards extends Morph { this.sortEntries(); } + + async onCopyIDs(evt) { + var begin = this.cards.maxProp('id') + 1 + const numIds = 100; + const idsText = numIds.times(i => begin + i).join('\n') + + try { + await this.copyTextToClipboard(idsText); + lively.success('copied ids for google docs!'); + } catch (e) { + shake(this.get('#copyIDs')); + lively.error('copying failed', e.message); + } + } + async onImportNewCards(evt) { lively.notify('onImportNewCards' + evt.shiftKey); if (that && that.localName === 'lively-code-mirror' && document.contains(that)) { @@ -2282,26 +2371,22 @@ export default class Cards extends Morph { }).join('\n') }; - const type = "text/plain"; - const blob = new Blob([text], { type }); - // evt.clipboardData.setData('text/html', html); - const data = [new ClipboardItem({ [type]: blob })]; - try { - await navigator.clipboard.write(data); + await this.copyTextToClipboard(text) lively.success('copied art description!'); } catch (e) { shake(this.get('#artDesc')); lively.error('copying failed', e.message); } } + + async copyTextToClipboard(text) { + const type = "text/plain"; + const blob = new Blob([text], { type }); + // evt.clipboardData.setData('text/html', html); + const data = [new ClipboardItem({ [type]: blob })]; - async onPrintAll(evt) { - if (!this.cards) { - return; - } - - await this.printForExport(this.cards, evt.shiftKey); + return await navigator.clipboard.write(data); } async onPrintSelected(evt) { @@ -2309,10 +2394,13 @@ export default class Cards extends Morph { return; } - const filteredEntries = this.allEntries.filter(entry => !entry.classList.contains('hidden')) + const filteredEntries = this.allEntries.filter(entry => entry.isVisible()) const cardsToPrint = filteredEntries.map(entry => entry.card) - await this.printForExport(cardsToPrint, evt.shiftKey); - } + + if (await this.checkForLargePrinting(cardsToPrint)) { + await this.printForExport(cardsToPrint, evt.shiftKey); + } + } async onPrintChanges(evt) { if (!this.cards) { @@ -2320,7 +2408,18 @@ export default class Cards extends Morph { } const cardsToPrint = this.cards.filter(card => !card.getIsPrinted()); - await this.printForExport(cardsToPrint, evt.shiftKey); + + if (await this.checkForLargePrinting(cardsToPrint)) { + await this.printForExport(cardsToPrint, evt.shiftKey); + } + } + + async checkForLargePrinting(cardsToPrint) { + if (cardsToPrint.length > 30) { + return await lively.confirm(`Print ${cardsToPrint.length} cards?
${cardsToPrint.slice(0, 30).map(c => c.getName()).join(', ')}, ...`); + } + + return true; } async printForExport(cards, quickSavePDF) { diff --git a/templates/livelystyle.css b/templates/livelystyle.css index cf5ea3854..d724f084a 100644 --- a/templates/livelystyle.css +++ b/templates/livelystyle.css @@ -12,6 +12,10 @@ display: none; } +ubg-cards-entry.out-of-range { + display: none; +} + button { padding: 4px; margin: 2px;