Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- <!DOCTYPE html>
- <meta charset="utf-8">
- <meta name="referrer" content="no-referrer">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <script src="https://cdn.jsdelivr.net/combine/npm/[email protected],npm/jszip@3/dist/jszip.min.js,npm/jquery@3,npm/[email protected]/browser/lib/jimp.js"></script>
- <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
- <link rel="stylesheet" href="https://use.typekit.net/dkw1lrx.css">
- <link href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH5AkREwo3+IbxEwAAAjZJREFUeNrtmz9LJDEchh/vLD28ZgsbBZtDUBBbLRVld7lDRc/mvoQfxUqwEbXwHwoLesVZXnuchTaChVaid3hy2glaGGEY3Ux2J+Pml+SFsGSSTPI+bDKTTAJRUUGrLSN9FhgDSgZ5XdMD8Ac4ANYbLTwA3Kib+BD+A4Om5vs9Mp4OgyZd4B/Q6WmXvwU+JC+8S2WY9tg8QAcwpwMwGsDAP6oDUAoAQEkHILhHf4gAiAAigAhAFIDfwApwVMTN2x03PwXsJuLfFIwg/gHVlHmAVeBrCAAqwF6dtE01TfcWwAywn5FnS+XzEsCdYb5tNSZ4A+BE/e4Dw4Zl1oBjHwBUgU/Aoor/bADCL5sN2ebtV2nKqTYsJdJMIBw2WF/NJQDlOu1YTuQZ0bR3sok6nQFQNujfOgjjTdbrBICKYZfcqANhIkfdLQdQaXBc2kmVnc5Zf0sBVJocnGsW21Br1WOwqnm9zdJn4EzydDiP+ec+3yMVgA3z36UuiDhtvmgAzpsvEoAI80UBEGO+CACizNsGIM68TQAizdsCMC/VvC0Ah1LN2wJwLtW8LQBnUs3bAnAv1bwNABeSzdsAcC7Z/FsCGHfRPOT/PD5U5/p7oFstYgwACziqvAB6gVOevs50q9CFINnYINGrgkjFPUIRQAQQAYSmBx2AqwAAXOoA/AgAwEEy8tqRmWvgo6fmM4/MgH5HhnQZe+sD/uLPabFrnk7DvVDWYcgvaiYn9eDklRrXdomKinpNj+Ks7+3FrzuXAAAAAElFTkSuQmCC" rel="shortcut icon">
- <title>Ghetto PCIO Editor v14</title>
- <!--
- BUTTONS
- The buttons are defined in "var buttons".
- The key is an icon from https://material.io/resources/icons/?style=baseline .
- There are three different buttons: If there is a "ONE_SHOT" after the button tooltip (for example folder_open) it's a ONE_SHOT function.
- The defined function gets called once when the button is pressed.
- If there is a "TOGGLE_TOOL" after the button tooltip (for example notes) it toggles the tool that comes afterwards.
- If there isn't any number, then there is a "function(allSelected, i, widget)" (for example align_vertical_top).
- The given function gets called once for every selected widget.
- The parameters:
- allSelected - an array of all selected widgets (the same for every call of the function)
- i - a simple counter incremented each function call
- widget - one of the selected widgets.
- Some buttons are a bit hacky. They are of the latter type but only do their stuff once (by doing i === 0).
- For example mediation. I do that if I want to work with the selected widgets but a normal iteration doesn't quite work.
- TOOLS
- Some buttons need further settings and open a tool. The <div id="tools"> contains all those tools as separate divs
- that are hidden by default and get toggled by the button function.
- Then just add buttons or whatever to that div and add a normal jQuery click handler somewhere at the bottom where the others are:
- $("#editImages [type=button]").on("click", function() {
- ...
- });
- For multiple buttons it's probably a good idea to give them IDs instead.
- -->
- <div class="primary toolbar"></div>
- <div class="secondary toolbarContainer"></div>
- <div id="room"></div>
- <div id="tools">
- <input type="file" id="file">
- <input type="file" id="fileStripped">
- <input type="file" id="fileImportWidgets">
- <input type="file" id="fileCustomBoard">
- <div id="toolBox">
- <h2>Standard widgets creation</h2>
- <h3>Cards</h3>
- <input type=button id=WCardHolder value="Empty Card Holder"><br>
- <input type=button id=WdeckStandard value="Standard 52 Card Deck">
- <input type=button id=WdeckExtended value="Extended Card Deck (1-15)">
- <input type=button id=WdeckPiquet value="Piquet Pack (7-A)">
- <input type=button id=WdeckEuchre value="Euchre Deck (9-A)">
- <input type=button id=WdeckSpanish value="Spanish Deck"><br>
- <input type=button id=WdeckCustom value="Custom Card Deck">
- <h3>Pieces</h3>
- <input type=button id=WSpinner value="Random Spinner">
- <input type=button id=WCounter value="Counter"><br>
- <input type=button class=WPiece data-type=classic value="Game Piece">
- <input type=button class=WPiece data-type=checkers value="Checkers Piece">
- <input type=button class=WPiece data-type=pin value="Pin Piece"><br>
- <input type=button id=WAutomationButton value="Automation Button">
- <h3>Game Boards</h3>
- <input type=button id=WBoardChess value="Chess Board">
- <input type=button id=WBoardBackgammon value="Backgammon Board">
- <input type=button id=WBoardCribbage value="Cribbage Board"><br>
- <input type=button id=WBoardCustom value="Custom Board">
- <h2>Ghetto widgets creation</h2>
- <h3>Cards</h3>
- <input type=button id=WdeckStandardOld value="Old 52 Card Deck">
- <input type=button id=WdeckPiquetOld value="Old Piquet Pack (7-A)">
- <input type=button id=WdeckEuchreOld value="Old Euchre Deck (9-A)"><br>
- <input type=button id=WdeckCustomOld value="Old Custom Card Deck">
- <h3>Pieces</h3>
- <input type=button id=WMainHand value="Main Hand">
- <input type=button id=WExtraHand value="Extra Hand"><br>
- <input type=button id=WGhettoSpinner value="Custom Spinner"><br>
- <h5>Game Pieces</h5>
- <select name="Color" id="GPColor">
- <option value="red">Red</option>
- <option value="orange">Orange</option>
- <option value="yellow">Yellow</option>
- <option value="green">Green</option>
- <option value="blue">Blue</option>
- <option value="purple">Purple</option>
- <option value="black">Black</option>
- <option value="white">White pin or dark black classic/checkers</option>
- </select>
- <input type=button class=WColorPiece value="Classic">
- <input type=button class=WColorPiece value="Checkers">
- <input type=button class=WColorPiece value="Pin"><br>
- <h5>Chess pieces</h5>
- <select name="Color" id="ChessColor">
- <option value="white">White</option>
- <option value="black">Black</option>
- </select>
- <input type=button class=WChess value="Pawn">
- <input type=button class=WChess value="Rook">
- <input type=button class=WChess value="Knight">
- <input type=button class=WChess value="Bishop">
- <input type=button class=WChess value="Queen">
- <input type=button class=WChess value="King"><br>
- <h3>Game Boards</h3>
- <input type=button id=WBoardFull value="1600*1000 Board">
- </div>
- <div id="tabletopImporter">
- <h2>Tabletop Simulator importer</h2>
- <ol>
- <li><a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS">CORS</a> prevents downloading images. You have to install (and enable for the editor) an <a href="https://addons.mozilla.org/en-US/firefox/addon/cors-everywhere/?utm_source=addons.mozilla.org&utm_medium=referral&utm_content=search">extension for Firefox</a> or <a href="https://stackoverflow.com/questions/3102819/disable-same-origin-policy-in-chrome">start Chrome in a special way</a> to disable it. <b>I'm looking for a way around that</b>.</li>
- <li>Search for a mod in <a href="https://steamcommunity.com/app/286160/workshop/">the steam workshop</a>.</li>
- <li>Use <a href="https://steamworkshopdownloader.io/">the steam workshop downloader</a> to download it.</li>
- <li>Select the file here: <input type="file" id="fileImportTabletop"></li>
- <li>Wait. Seriously.</li>
- </ol>
- <div id="tabletopPreview" class="fullWidth">
- </div>
- </div>
- <div id="typeFilter">
- <h2>Filter widget types</h2>
- <input type="checkbox" id="automationButton" checked><label for="automationButton" class="type-automationButton"> Automation Buttons</label><span class="hotkey"> <Shift>+A</span><br>
- <input type="checkbox" id="board" checked><label for="board" class="type-board"> Boards</label><span class="hotkey"> <Shift>+B</span><br>
- <input type="checkbox" id="card"><label for="card" class="type-card"> Cards</label><span class="hotkey"> <Shift>+C</span><br>
- <input type="checkbox" id="cardDeck" checked><label for="cardDeck" class="type-cardDeck"> Card Decks</label><span class="hotkey"> <Shift>+D</span><br>
- <input type="checkbox" id="cardPile" checked><label for="cardPile" class="type-cardPile"> Card Piles</label><span class="hotkey"> <Shift>+P</span><br>
- <input type="checkbox" id="counter" checked><label for="counter" class="type-counter"> Counters</label><span class="hotkey"> <Shift>+T</span><br>
- <input type="checkbox" id="gamePiece" checked><label for="gamePiece" class="type-gamePiece"> Game Pieces</label><span class="hotkey"> <Shift>+G</span><br>
- <input type="checkbox" id="hand" checked><label for="hand" class="type-hand"> Hands</label><span class="hotkey"> <Shift>+N</span><br>
- <input type="checkbox" id="spinner" checked><label for="spinner" class="type-spinner"> Spinners</label><span class="hotkey"> <Shift>+I</span><br>
- <input type="checkbox" id="label"><label for="label" class="type-label"> Label</label> (virtual widget type for attached labels)<span class="hotkey"> <Shift>+L</span>
- </div>
- <div id="jsonEdit">
- <h2>Direct JSON editing</h2>
- <p><input type="checkbox" id="compactAB"><label for="compactAB"> Compact Automation Button clickRoutine</label></p>
- <div class="hideWithMulti">
- <textarea id="json"></textarea><br><input type="button" value="Set" class="set"><br>
- Default size<span id="defaultSize"></span>
- </div>
- <p>(only visible if exactly one widget is selected)</p>
- <p>See <a href="https://www.reddit.com/user/RaphaelAlvez/">u/RaphaelAlvez'</a> <a href="https://www.notion.so/Playingcards-io-s-documentation-1ae08cee1f6043f599e92229ae122cd5">unoffical documentation</a>.</p>
- </div>
- <div id="macros">
- <h2>Macros</h2>
- Preset: <select></select><br>
- <textarea id="generateDefinition"></textarea><br><input type="button" value="Go">
- <h3>Some notes</h3>
- <ul>
- <li>This shit is pretty powerful but requires some coding knowledge. Ask for help in <a href="https://www.reddit.com/r/PlayingCardsIO/comments/inuocb/pcio_editor/">my reddit post</a>. Also please share your code there.</li>
- <li>The current representation of <tt>widgets.json</tt> is available as the variable <tt>widgets</tt>.</li>
- <li>A card pile is 8px bigger than the cards in each dimension.</li>
- </ul>
- </div>
- <div id="cloudUpload">
- <h2>Replace packaged assets by links</h2>
- <p>The packaged assets (images inside the pcio file) will be replaced by the online version (firebasestorage.googleapis.com links) the game created.</p>
- <ol>
- <li>Start Firefox (this does not work on Chrome).</li>
- <li>Open a <a href="https://playingcards.io/room/new/generic">new playingcards.io room</a>.</li>
- <li>Press <F12> to open the dev tools and select the "Network" tab.</li>
- <li>Import your PCIO file into the room (and wait for it to finish).</li>
- <li>Right-click somewhere in the list of network requests.</li>
- <li>Select "Copy" and "Copy All As HAR".</li>
- <li>Paste it into the text box below (takes a while) and press "Replace".</li>
- <li>Save the PCIO as an "online" version. There's no way to undo this so save the original PCIO as well.</li>
- </ol>
- <p><textarea id="firefoxHAR"></textarea></p>
- <p><input type="button" id="checkHAR" value="Replace"></p>
- </div>
- <div id="editCardDecks">
- <h2>Edit card decks</h2>
- <h3>Card deck settings</h3>
- <input type="checkbox" id="cardOverlapH" checked><label for="cardOverlapH"> Overlap in hand</label><br>
- <input type="checkbox" id="onRemoveFromHand" checked><label for="onRemoveFromHand"> Flip out of hand</label><br>
- <input type="checkbox" id="confirmRecall"><label for="confirmRecall"> Recall confirmation</label><br>
- <input type="checkbox" id="confirmRecallAll" checked><label for="confirmRecallAll"> Recall all confirmation</label><br>
- <input type="checkbox" id="enlarge"><label for="enlarge"> Enlarge</label><br>
- <input type="button" class="set" value="Set">
- <h3>Edit cards</h3>
- <p>Here you can merge card types, move/copy card types to a new deck and add simulated card rotation.</p>
- <input type="button" class="preview" value="Preview">
- <div id="editCardDecksPreview" class="fullWidth">
- </div>
- </div>
- <div id="editImages">
- <h2>Edit images</h2>
- <p>This operates on the selected card decks and boards.</p>
- <p>It uses JIMP which has <a href="https://github.com/oliver-moran/jimp/blob/master/packages/jimp/README.md#Methods">many editing functions</a>. Tell me if you need one not implemented here.</p>
- <h3>Compress images</h3>
- <p>Note: all other actions keep the dimensions and convert to PNG. Use compression to turn images back to JPG.</p>
- Type: <select id="compressType"><option value="image/jpeg">JPG</option><option value="image/png">PNG</option></select> - Quality (JPG only): <input id="compressQuality" value="0.7" class="short"><br>
- Width: <input id="compressWidth" class="short" value="200"><br>
- <input type="button" class="compress" value="Preview">
- <h3>Rotate images</h3>
- Degrees: <input id="rotateDegrees" class="short" value="90"><br>
- <input type="checkbox" id="rotateResizeDeck" checked><label for="rotateResizeDeck"> Resize the deck/board</label><br>
- <input type="button" class="rotate" value="Preview">
- <h3>Crop images</h3>
- X: <input id="cropX" class="short" value="10"> Y: <input id="cropY" class="short" value="10"><br>
- Width: <input id="cropWidth" class="short" value="100"> Height: <input id="cropHeight" class="short" value="100"><br>
- <input type="checkbox" id="cropResizeDeck" checked><label for="cropResizeDeck"> Resize the deck/board</label><br>
- <input type="button" class="crop" value="Preview">
- <div id="editPreview" class="fullWidth">
- </div>
- </div>
- <div id="help">
- <h2>Hotkeys</h2>
- <ul>
- <li>tooltips for the toolbar buttons show hotkeys (capital letters means press <b>Shift</b>)</li>
- <li>see "filter widget types" for filter hotkeys</li>
- <li><b>Ctrl</b>: hold while resizing to keep aspect ratio</li>
- <li><b>Shift+Arrows</b>: move widgets by 1px</li>
- <li><b>Ctrl+Shift+Arrows</b>: move widgets by 5px</li>
- <li><b>Escape</b>: close all tools</li>
- </ul>
- <h2>Changelog</h2>
- <h3>Updates</h3>
- <p>For updates check <a href="https://www.reddit.com/r/PlayingCardsIO/comments/inuocb/pcio_editor/">my reddit post</a>.</p>
- <!--
- TODO LIST
- "ruler"
- fix "bla-copy01-copy01-copy02"
- improve rotation for Bears vs Babies
- add hotkeys for toolbar inputs
- set coordinates and ID with ENTER
- change "new" button to an init function
- check uniqueID("gpe-board") - that shouldn't work
- check https://imgur.com/a/1iIJvpc
- paste into prompt only works if nothing was copied before
- move/copy merge duplicate assets to unlink?
- status bar is too small on narrow views (remove the headers?)
- RaphaelAlvez:
- - make widgets creation look better
- KNOWN BUGS
- WISH LIST
- only add toolbar linebreaks between icon groups
- It would be better to have copy and paste buttons and to make it possible to move all selected widgets with the mouse of course.
- RaphaelAlvez:
- -"maybe you should consider changing the merge cards into deck edditor. It only lacks changing the number of cards and renaming card types. but it's not really necessary"
- -reset button on deck eddit. (the option don't match the deck's current properties)
- -As a request, I saw that merging cards keep the cards with the same back face.
- -I think you should get rid of the vertical menu for square scales
- -->
- <h3>v14 - 2020-10-20</h3>
- <ul>
- <li>Tabletop Simulator importer: added support for card decks with unique back images.</li>
- <li>Tabletop Simulator importer: only importing the selected cards works now.</li>
- <li>Tabletop Simulator importer: selected cards of different decks can now be imported into the same deck.</li>
- <li>New secondary toolbars. Most features were there already but you can directly change widget IDs now.</li>
- <li>Copy and paste.</li>
- <li>"Direct coordinate editing" can now be set to use right/bottom as a reference.</li>
- <li>Added a new button that replaces packaged images by links.</li>
- <li>New macro to create a movement loop between piles.</li>
- <li>Made the realistic preview more realistic.</li>
- </ul>
- <h3>v13 - 2020-10-07</h3>
- <ul>
- <li>Standard widgets can be added like in the vanilla editor thanks to <a href="https://www.reddit.com/user/RaphaelAlvez/">u/RaphaelAlvez</a>.</li>
- <li>"Edit images" can also crop images now. And the resize checkboxes work.</li>
- <li>"Merge card types" and "edit card deck settings" are now "edit card decks". It can also copy and split decks now and it can create card decks with rotated images.</li>
- <li>Added <Shift>+<Arrows> as hotkeys for moving selected widgets by 1px (5px with <Shift>+<Ctrl>).</li>
- <li>Added hotkeys for filtering several widget types.</li>
- <li>Pressing <Ctrl> while resizing keeps the aspect ratio.</li>
- <li>"Direct coordinate editing" can now also edit width and height.</li>
- <li>The changelog button is now a help button and shows hotkeys.</li>
- </ul>
- <h3>v12 - 2020-09-28</h3>
- <ul>
- <li>"Compress images" is now "edit images". It can also rotate images now.</li>
- <li>Tabletop Simulator importer: all import buttons should do something now.</li>
- <li>Tabletop Simulator importer: back images work for simple cases.</li>
- <li>Tabletop Simulator importer: custom mesh textures can be imported (probably useless).</li>
- <li>Tabletop Simulator importer: you can set the desired height before importing.</li>
- </ul>
- <h3>v11 - 2020-09-27</h3>
- <ul>
- <li>Integrated Tabletop Simulator importer (still WIP).</li>
- <li>Widgets can now also be resized with the mouse after toggling a button.</li>
- <li>Added hotkeys to buttons.</li>
- <li>Labels and recall buttons can now have a visual representation thanks to <a href="https://www.reddit.com/user/RaphaelAlvez/">u/RaphaelAlvez</a>.</li>
- <li>JSON edit can now show a compressed version of the automation button clickRoutine.</li>
- <li>Added a button that reselects previous widgets.</li>
- <li>Added a button that toggles a pretty realistic preview (most parts thanks to <a href="https://www.reddit.com/user/RaphaelAlvez/">u/RaphaelAlvez</a>).</li>
- </ul>
- <h3>v10 - 2020-09-21</h3>
- <ul>
- <li>Renamed "generate widgets" to "macros" and added a lot of presets by <a href="https://www.reddit.com/user/RaphaelAlvez/">u/RaphaelAlvez</a>.</li>
- <li>Active tools are now highlighted (and there's a button to close them all).</li>
- <li>The filename can now be changed when saving.</li>
- <li>Added favicon.</li>
- </ul>
- <h3>v9 - 2020-09-16</h3>
- <ul>
- <li>Turn all HTTP links into packaged assets.</li>
- <li>Merge multiple card types with the same image into one.</li>
- <li>Status bar with coordinates and status of some tools.</li>
- <li>Board images can now also be compressed.</li>
- <li>Widgets can now be moved with the mouse after toggling the button.</li>
- <li>Saving works now without loading anything prior.</li>
- <li>More colors for widget types (also in the filter selection).</li>
- <li>Several widget generation presets by <a href="https://www.reddit.com/user/RaphaelAlvez/">u/RaphaelAlvez</a>.</li>
- </ul>
- <h3>v8 - 2020-09-15</h3>
- <ul>
- <li>Procedurally generate new widgets.</li>
- </ul>
- <h3>v7 - 2020-09-14</h3>
- <ul>
- <li>Import widgets from another PCIO file.</li>
- </ul>
- <h3>v6 - 2020-09-14</h3>
- <ul>
- <li>Undo.</li>
- </ul>
- <h3>v5 - 2020-09-13</h3>
- <ul>
- <li>Mass compress card images.</li>
- <li>Remove unused card type variables and unused images.</li>
- <li>Set card deck options.</li>
- </ul>
- <h3>v4 - 2020-09-12</h3>
- <ul>
- <li>Added "stripped" save and import buttons for quickly testing and changing things in the official editor without loading hundreds of card images.</li>
- <li>Mass set X or Y spacing.</li>
- <li>Direct coordinate editing now supports formulas.</li>
- <li>Delete widgets.</li>
- <li>Filter what widget types are being displayed.</li>
- <li>Made the editor layout responsive.</li>
- </ul>
- <h3>v3 - 2020-09-08</h3>
- <ul>
- <li>Merge card decks.</li>
- <li>Cleaned it up a bit.</li>
- <li>Added a few descriptions and links.</li>
- </ul>
- <h3>v2 - 2020-09-07</h3>
- <ul>
- <li>Mass offset X or Y coordinates.</li>
- <li>Alignment buttons.</li>
- <li>Direct JSON editing.</li>
- <li>Added a pretty awkward selection rectangle.</li>
- <li>Board images are now being displayed.</li>
- </ul>
- <h3>v1 - 2020-09-06</h3>
- <ul>
- <li>Loading and saving PCIO files.</li>
- <li>Simple rendering and selecting of widgets.</li>
- <li>Mass set X or Y coordinates.</li>
- </ul>
- </div>
- </div>
- <div id="statusbar">
- <span>Coordinates: <span id="statusCoordinates">0, 0</span></span>
- <span>Widget: <span id="statusWidget">-</span></span>
- <span>Tool status: <span id="statusTool">-</span></span>
- </div>
- <div id="selectionDiv"></div>
- <style>
- body, #editCardDecks {
- padding-bottom: calc(8px + 3vw);
- -webkit-user-select: none;
- -moz-user-select: none;
- user-select: none;
- }
- body {
- min-height: 1200px;
- }
- body, .tooltip {
- font-family: ubuntu, sans;
- }
- #x, #y , #height, #width {
- width: 50px;
- }
- textarea {
- width: 95%;
- height: 400px;
- }
- .error {
- color: red;
- font-weight: bold;
- font-size: 200%;
- }
- .short {
- width: 50px;
- }
- #macros select {
- width: 95%;
- }
- #tools {
- margin-right: 1610px;
- }
- .toolbar {
- margin-bottom: 0;
- }
- .toolbar > span {
- display: none;
- }
- .toolbar span + button {
- margin-left: 16px;
- }
- button.material-icons {
- padding: 1px;
- border: 1px solid black;
- margin: 2px;
- position: relative;
- }
- button.material-icons.activated {
- background: #888;
- }
- button.material-icons.toolbarButton.activated, .toolbarContainer .toolbar {
- background: #aaa;
- }
- .toolbarContainer .toolbar {
- border: 1px solid black;
- }
- button.material-icons.toolbarButton.activated {
- border-bottom: 0;
- top: 4px;
- }
- .toolbarContainer button.material-icons.toolbarButton.activated {
- top: 5px;
- }
- .tooltip {
- display: none;
- font-size: 12px;
- position: absolute;
- text-align: left;
- background: #ccc;
- border: 1px solid black;
- padding: 4px;
- top: 31px;
- left: -1px;
- width: 200px;
- white-space: normal;
- z-index: 999999;
- }
- .tooltip ul + br {
- display: none;
- }
- button:nth-child(n+14) .tooltip {
- left: -183px;
- }
- button:hover > .tooltip {
- display: block;
- }
- button.toolbarButton.activated:hover > .tooltip {
- display: none;
- }
- .toolbar label:nth-child(1) {
- margin-left: 6px;
- }
- .toolbar label {
- vertical-align: super;
- }
- .toolbar input {
- height: 26px;
- padding: 0 6px;
- text-align: right;
- margin: 2px;
- border: 1px solid black;
- width: 50px;
- vertical-align: top;
- }
- .toolbar input.text {
- width: 200px;
- text-align: left;
- }
- input.inactive {
- color: #ccc;
- }
- #room {
- position: absolute;
- margin-top: 8px;
- right: 8px;
- width: 1600px;
- height: 1000px;
- border: 1px solid black;
- }
- #statusbar {
- color: white;
- background: black;
- position: fixed;
- right: 0;
- bottom: 0;
- padding: 4px;
- left: 0;
- font-size: 1vw;
- }
- #statusbar > * {
- display: inline-block;
- }
- #statusbar > span:nth-child(1) {
- width: 15%;
- }
- #statusbar > span:nth-child(2) {
- width: 30%;
- }
- #statusbar > span:nth-child(3) {
- width: 50%;
- }
- .fullWidth {
- margin-top: 1000px;
- margin-right: -1600px;
- }
- .hotkey {
- color: #ccc;
- }
- .hotkey:hover {
- color: initial;
- }
- .cardPreview {
- display: inline-block;
- position: relative;
- vertical-align: middle;
- border: 5px solid #ccc;
- font-size: 0;
- margin: 0 !important;
- }
- .cardPreview > img {
- width: 200px;
- }
- .cardPreview > img.back {
- width: 100px;
- }
- .cardPreview > div {
- position: absolute;
- left: 0;
- right: 0;
- top: 40px;
- font-size: 60px;
- display: inline;
- text-align: center;
- color: white;
- text-shadow: -2px 2px 0 #000, 2px 2px 0 #000, 2px -2px 0 #000, -2px -2px 0 #000;
- }
- .cardPreview.cardSelected {
- border: 5px solid red;
- }
- #tabletopPreview div {
- margin-left: 20px;
- }
- #tabletopPreview .empty > h4, #tabletopPreview .empty > button {
- display: inline;
- font-size: 8px;
- padding: 0;
- margin: 0 4px;
- border: 0;
- opacity: 0.5;
- }
- .targetHeight {
- width: 50px;
- }
- #selectionDiv, .hideWithMulti, #tools > * {
- display: none;
- }
- #selectionDiv {
- border: 1px dotted #000;
- position: fixed;
- z-index: -1;
- }
- .widget, .widgetLabel {
- position: absolute;
- background: black;
- opacity: 0.5;
- background-size: cover;
- font-size: 0;
- }
- .selected.widget {
- opacity: 1;
- }
- .type-board {
- background-color: #ccc;
- z-index: 0 !important;
- }
- .type-automationButton { background: red; color: white; }
- .type-card { background: teal; }
- .type-cardDeck { background: blue; color: white; }
- .type-cardPile { background: black; color: white; }
- .type-counter { background: green; }
- .type-gamePiece { background: orange; }
- .type-hand { background: sienna; }
- .type-spinner { background: purple; color: white; }
- .type-label, .widgetLabel { background: cyan; }
- .realistic {background :#f0f0f0;}
- .realistic .widget, .realistic .widgetLabel { opacity: 1; box-sizing: border-box; border-width: 0; font-size: initial; }
- .realistic .type-cardDeck { display: none; }
- .realistic .type-hand.disabled { display: none; }
- .realistic .type-cardPile { background:white; border-top: 1px solid #d8d8d8; border-left: 1px solid #d8d8d8; border-bottom: 1px solid #ccc; border-right: 1px solid #ccc; background: #fff; border-radius: 8px; }
- .realistic .type-hand { background: #fff; border-top: 1px solid #d8d8d8; border-left: 1px solid #d8d8d8; border-bottom: 1px solid #ccc; border-right: 1px solid #ccc; border-bottom: 0; border-top-left-radius: 6px; border-top-right-radius: 6px; bottom: 0;}
- .realistic .widgetLabel {
- background:white;
- text-align: center;
- font-size: 14px;
- font-weight: normal;
- letter-spacing: 0.1px;
- line-height: 1em;
- padding: 0;
- background: linear-gradient(to bottom, #ffffff 0%,#f7f7f7 100%);
- border-top: 1px solid #d8d8d8;
- border-left: 1px solid #d8d8d8;
- border-bottom: 1px solid #ccc;
- border-right: 1px solid #ccc;
- border-radius: 4px;
- box-shadow: 1px 1px 1px #dedede;
- color: #8e8e8e;
- font-family: gill-sans-nova, sans-serif;
- text-transform: uppercase;
- }
- .realistic .type-counter { background: url(https://s1.gifyu.com/images/counter.png); }
- .realistic .type-card { background: url(https://playingcards.io/img/cardback-red.svg) #A23B2A; background-size: contain; background-repeat: no-repeat; background-position: center; border-radius: 8px; }
- .realistic .type-spinner { border-radius: 800px; background:url(https://svgshare.com/i/Pzs.svg;) #e1e1e1; background-size:cover;}
- .realistic .type-automationButton { border-radius: 800px; background: radial-gradient(circle, #a9e9e2 0%, #a9e9e2 64%, #93d0c9 64%); color: #6d6d6d; display:flex;justify-content:center;align-items:center;}
- .realistic .type-gamePiece.subtype-pin.color-black { background: url(https://playingcards.io/img/pieces/pin-black.svg); }
- .realistic .type-gamePiece.subtype-pin.color-blue { background: url(https://playingcards.io/img/pieces/pin-blue.svg); }
- .realistic .type-gamePiece.subtype-pin.color-purple { background: url(https://playingcards.io/img/pieces/pin-purple.svg); }
- .realistic .type-gamePiece.subtype-pin.color-red { background: url(https://playingcards.io/img/pieces/pin-red.svg); }
- .realistic .type-gamePiece.subtype-pin.color-yellow { background: url(https://playingcards.io/img/pieces/pin-yellow.svg); }
- .realistic .type-gamePiece.subtype-pin.color-green { background: url(https://playingcards.io/img/pieces/pin-green.svg); }
- .realistic .type-gamePiece.subtype-pin.color-orange { background: url(https://playingcards.io/img/pieces/pin-orange.svg); }
- .realistic .type-gamePiece.subtype-pin.color-white { background: url(https://playingcards.io/img/pieces/pin-white.svg); }
- .realistic .type-gamePiece.subtype-pin { width:35.85px !important; height:43.83px !important;}
- .realistic .type-gamePiece.subtype-classic { background:url(https://svgshare.com/i/PzL.svg); width:90px !important; height:90px !important;}
- .realistic .type-gamePiece.subtype-checkers { border-radius: 800px; width:90px !important; height:90px !important;}
- .realistic .type-gamePiece.subtype-classic.color-black { background: url(https://svgshare.com/i/Q1P.svg); }
- .realistic .type-gamePiece.subtype-classic.color-blue { background: url(https://svgshare.com/i/PzK.svg); }
- .realistic .type-gamePiece.subtype-classic.color-purple { background: url(https://svgshare.com/i/Q0v.svg); }
- .realistic .type-gamePiece.subtype-classic.color-red { background: url(https://svgshare.com/i/Pzi.svg); }
- .realistic .type-gamePiece.subtype-classic.color-yellow { background: url(https://svgshare.com/i/Pzg.svg); }
- .realistic .type-gamePiece.subtype-classic.color-green { background: url(https://svgshare.com/i/Q0h.svg); }
- .realistic .type-gamePiece.subtype-classic.color-orange { background: url(https://svgshare.com/i/Q0i.svg); }
- .realistic .type-gamePiece.subtype-checkers { background: radial-gradient(circle, #000000 0%, #000000 33%, #525252 33%, #525252 46%, #000000 46%, #000000 58%, rgba(0, 0, 0, 0) 58%); }
- .realistic .type-gamePiece.subtype-checkers.color-black { background: radial-gradient(circle, #4a4a4a 0%, #4a4a4a 33%, #848484 33%, #848484 46%, #4a4a4a 46%, #4a4a4a 58%, rgba(0, 0, 0, 0) 58%); }
- .realistic .type-gamePiece.subtype-checkers.color-blue { background: radial-gradient(circle, #4c5fea 0%, #4c5fea 33%, #8693f1 33%, #8693f1 46%, #4c5fea 46%, #4c5fea 58%, rgba(0, 0, 0, 0) 58%); }
- .realistic .type-gamePiece.subtype-checkers.color-purple { background: radial-gradient(circle, #bc5bee 0%, #bc5bee 33%, #d290f4 33%, #d290f4 46%, #bc5bee 46%, #bc5bee 58%, rgba(0, 0, 0, 0) 58%); }
- .realistic .type-gamePiece.subtype-checkers.color-red { background: radial-gradient(circle, #e84242 0%, #e84242 33%, #f07f7f 33%, #f07f7f 46%, #e84242 46%, #e84242 58%, rgba(0, 0, 0, 0) 58%); }
- .realistic .type-gamePiece.subtype-checkers.color-yellow { background: radial-gradient(circle, #e0cb0b 0%, #e0cb0b 33%, #eadc59 33%, #eadc59 46%, #e0cb0b 46%, #e0cb0b 58%, rgba(0, 0, 0, 0) 58%); }
- .realistic .type-gamePiece.subtype-checkers.color-green { background: radial-gradient(circle, #23ca5b 0%, #23ca5b 33%, #6adb90 33%, #6adb90 46%, #23ca5b 46%, #23ca5b 58%, rgba(0, 0, 0, 0) 58%); }
- .realistic .type-gamePiece.subtype-checkers.color-orange { background: radial-gradient(circle, #e2a633 0%, #e2a633 33%, #ecc375 33%, #ecc375 46%, #e2a633 46%, #e2a633 58%, rgba(0, 0, 0, 0) 58%); }
- @media (max-width: 2048px) {
- body {
- min-height: 600px;
- }
- #room {
- border: 2px solid black;
- transform: scale(0.5, 0.5);
- transform-origin: top right;
- }
- .fullWidth {
- margin-top: 500px;
- margin-right: -800px;
- }
- #tools {
- margin-right: 810px;
- }
- }
- @media (max-width: 801px) { /* adjust for button count (32 per button) */
- button.material-icons {
- font-size: 20px;
- padding: 0;
- margin: 2px 1px 2px 0;
- }
- .toolbarContainer button:nth-child(1) {
- margin-left: 2px;
- }
- .toolbar span + button {
- margin-left: 5px;
- }
- .toolbar input {
- height: 20px;
- font-size: 14px;
- padding: 0 2px;
- width: 32px;
- }
- button.material-icons.toolbarButton.activated {
- top: 5px;
- }
- .tooltip {
- top: 22px;
- }
- button:nth-child(n+14) .tooltip {
- left: -189px;
- }
- }
- @media (max-width: 563px) { /* adjust for button count (23 per button) */
- button.material-icons.toolbarButton.activated {
- top: 0;
- border: 1px solid black;
- }
- .tooltip {
- position: fixed;
- left: 0 !important;
- right: 0;
- top: unset;
- margin: auto;
- margin-top: 2px;
- }
- }
- @media (max-width: 1248px) {
- #tools {
- margin-right: 0;
- margin-top: 516px;
- }
- .fullWidth {
- margin-top: initial;
- margin-right: initial;
- }
- }
- @media (max-width: 828px) {
- body {
- min-height: 300px;
- }
- #room {
- border: 4px solid black;
- transform: scale(0.25, 0.25);
- transform-origin: top right;
- }
- #tools {
- margin-top: 266px;
- }
- }
- @media (max-width: 900px) {
- body, #editCardDecks, #tools {
- padding-bottom: calc(8px + 6vw);
- }
- #statusbar {
- font-size: 2vw;
- }
- #statusbar > * {
- display: inline-block;
- }
- #statusbar > span:nth-child(1) {
- width: 30%;
- }
- #statusbar > span:nth-child(2) {
- width: 65%;
- }
- #statusbar > span:nth-child(3) {
- width: 95%;
- }
- }
- @media (max-width: 460px) {
- body {
- min-height: 150px;
- }
- #room {
- border: 8px solid black;
- transform: scale(0.125, 0.125);
- transform-origin: top right;
- }
- #tools {
- margin-top: 142px;
- }
- }
- @media (max-width: 218px) {
- body * {
- display: none;
- }
- body:before {
- content: "UNREASONABLE";
- }
- }
- @media (max-width: 160px) {
- body:before {
- content: "JUST NO!";
- }
- }
- @media (max-width: 60px) {
- body {
- padding: 0;
- margin: 0;
- }
- body:before {
- content: "FU!";
- font-size: 60vw;
- }
- }
- </style>
- <script>
- var zipFile = dataURLtoFile("data:application/zip;base64,UEsDBAoAAAAAAGRkMFHZQGDUQgAAAEIAAAAMAAAAd2lkZ2V0cy5qc29uW3siaWQiOiJoYW5kIiwidHlwZSI6ImhhbmQiLCJ4Ijo1MCwieSI6ODIwLCJ6IjoxLCJkcmFnZ2luZyI6bnVsbH1dUEsBAhQACgAAAAAAZGQwUdlAYNRCAAAAQgAAAAwAAAAAAAAAAAAAAAAAAAAAAHdpZGdldHMuanNvblBLBQYAAAAAAQABADoAAABsAAAAAAA=", "unnamed.pcio");
- var widgets = [{"id":"hand","type":"hand","x":50,"y":820,"z":1,"dragging":null}];
- var moveWidgets = false;
- var resizeWidgets = false;
- var selectionOrder = 1;
- var previouslySelected = [];
- var lastFilename = null;
- var imageCache = {};
- var boardImageCache = {};
- var compressedImageCache = {};
- var removeFromZip = {};
- var defaultSizes = {
- automationButton: [ 80, 80 ],
- card: [ 103, 160 ],
- cardDeck: [ 86, 86 ],
- cardPile: [ 111, 168 ],
- counter: [ 140, 44 ],
- gamePiece: {
- checkers: [ 90, 90 ],
- classic: [ 90, 90 ],
- pin: [ 35.85, 43.83 ],
- pawn: [ 46.6536, 80.6544 ],
- rook: [ 46.6536, 87.4008 ],
- knight: [ 54.4344, 98.2632 ],
- bishop: [ 48.2304, 108.3336 ],
- queen: [ 52.3200, 124.8000 ],
- king: [ 52.3512, 132.396 ]
- },
- hand: [ 1500, 180 ],
- spinner: [ 110, 110 ]
- };
- var generateWidgetPresets = {
- "Widgets Creation": {
- "Grid of card piles": `
- number: 10,
- widget: function(i) {
- var x = 20;
- var y = 40;
- var dx = 120;
- var dy = 170;
- var perRow = 5;
- if(i == 0)
- idPrefix = uniqueID("grid", true);
- return {
- "id": idPrefix + "-" + (Math.floor(i/perRow)+1) + "-" + (i%perRow+1),
- "x": x + (i%perRow)*dx,
- "y": y + Math.floor(i/perRow)*dy,
- "z": 400 + i,
- "type": "cardPile",
- "dragging": null,
- "label": "",
- "hasShuffleButton": false,
- "width": 111,
- "height": 168
- };
- }
- `,
- "Simple Card Pile Creation": `
- number: 1,
- widget: function(i) {
- return {
- "id": uniqueID("pile"),
- "x": 20,
- "y": 40,
- "z": 400,
- "type": "cardPile",
- "width": 111,
- "height": 168
- };
- }
- `,
- "Spinner with N options":`
- // contributed by u/RaphaelAlvez
- number: 1, /* ONLY change this if you want to create a grid */
- widget: function(i) {
- var x = 20;
- var y = 40;
- var numberOfOptions = 6;
- /* ONLY change this if you want to create a grid */
- var dx = 0;
- var dy = 0;
- var perRow = 1;
- return {
- "id": uniqueID("spinner"),
- "x": x + (i%perRow)*dx,
- "y": y + Math.floor(i/perRow)*dy,
- "z": i,
- "type": "spinner",
- "options": Array.from({length: numberOfOptions}, (_, i) => i + 1),
- "rotation": 0,
- "value": "",
- "dragging": null
- };
- }
- `,
- "Normal Game Piece":`
- // contributed by u/RaphaelAlvez
- number: 1, /* ONLY change this if you want to create a grid */
- widget: function(i) {
- var x = 20;
- var y = 40;
- /* ONLY change this if you want to create a grid */
- var dx = 0;
- var dy = 0;
- var perRow = 1;
- return {
- "id": uniqueID("gamePiece"),
- "x": x + (i%perRow)*dx,
- "y": y + Math.floor(i/perRow)*dy,
- "z": i,
- "type":"gamePiece",
- "pieceType":"checkers", /* can be "classic", "checkers" or "pin" */
- "color":"red", /* can be "red", "orange", "yellow", "green", "blue", "purple" or "black" */
- "dragging":null,
- "kinged":true /* can be true or false */
- };
- }
- `,
- "Normal Counter":`
- // contributed by u/RaphaelAlvez
- number: 1, /* ONLY change this if you want to create a grid */
- widget: function(i) {
- var x = 20;
- var y = 40;
- /* ONLY change this if you want to create a grid */
- var dx = 0;
- var dy = 0;
- var perRow = 1;
- return {
- "id": uniqueID("counter"),
- "x": x + (i%perRow)*dx,
- "y": y + Math.floor(i/perRow)*dy,
- "z": i,
- "type":"counter",
- "counterValue":0,
- "dragging":null,
- "label":""
- };
- }
- `
- },
- "Widgets Duplication": {
- "Duplicate the selected widgets": `
- number: $(".selected").length,
- widget: function(i) {
- w = widgets[$(".selected").eq(i).data("index")];
- newW = JSON.parse(JSON.stringify(w)); // deep clone
- newW.id = uniqueID(w.id + "-copy");
- newW.x += 50;
- newW.y += 50;
- return newW;
- }
- `,
- "Duplicate a selected automation button 5x adding 1 to its label and quantities": `
- number: 5,
- widget: function(i) {
- w = widgets[$(".selected.type-automationButton").eq(0).data("index")];
- newW = JSON.parse(JSON.stringify(w)); // deep clone
- newW.id = uniqueID(w.id + "-copy");
- newW.x += 85 * (i+1);
- newW.label = +newW.label + i+1;
- for(var r in newW.clickRoutine)
- if(newW.clickRoutine[r].args.quantity != undefined)
- newW.clickRoutine[r].args.quantity.value += i+1;
- return newW;
- }
- `
- },
- "Automation Button (AB) Routines": {
- "Create Clean AB": `
- // contributed by u/RaphaelAlvez
- number: 1, /* ONLY change this if you want to create a grid */
- widget: function(i) {
- var x = 20;
- var y = 40;
- /* ONLY change this if you want to create a grid */
- var dx = 0;
- var dy = 0;
- var perRow = 1;
- return {
- "id": uniqueID("button"),
- "x": x + (i%perRow)*dx,
- "y": y + Math.floor(i/perRow)*dy,
- "z": i,
- "type": "automationButton",
- "label": "Button "+i,
- "clickRoutine": [],
- "dragging": null
- };
- }
- `,
- "Clean ALL routines of selected ABs": `
- // contributed by u/RaphaelAlvez
- number: $(".selected").length,
- widget: function(i) {
- w = widgets[$(".selected").eq(i).data("index")];
- if (w.clickRoutine!=null){w.clickRoutine=[]};
- }
- `,
- "Move Routine": `
- // contributed by u/RaphaelAlvez
- number: 1,
- widget: function(i) {
- /* You should first select the source card piles then the automation button and finally the destination card piles. Do not select any other kind of widgets because it will get introduced in the routine */
- /* Choose here the number of cards that should be moved */
- var Quantity=1 ;
- var selected=$(".selected").get().sort((a, b) => $(a).data("selectionOrder") - $(b).data("selectionOrder"));
- /* finds the AB */
- var abIndex = selected.map((o) => widgets[$(o).data("index")].type).indexOf("automationButton");
- /* gets the ids of the widgets selected before the AB */
- From=selected.slice(0, abIndex).map((o)=>widgets[$(o).data("index")].id);
- /* gets the ids of the widgets selected after the AB */
- To=selected.slice(abIndex+1).map((o)=>widgets[$(o).data("index")].id);
- /* Checks if you did something wrong */
- if(abIndex==-1||From.length==0||To.length==0){return};
- /* Add routine */
- widgets[$(selected[abIndex]).data("index")].clickRoutine.push({
- "func": "MOVE_CARDS_BETWEEN_HOLDERS",
- "args": {
- "from": {
- "type": "literal",
- "value": From
- },
- "to": {
- "type": "literal",
- "value": To
- },
- "quantity": {
- "type": "literal",
- "value": Quantity
- } ,
- "moveFlip": {
- "type": "literal",
- "value": "none" /* Can be "none","faceUp" or "faceDown" */
- }
- }
- });
- }
- `,
- "Shuffle Routine": `
- // contributed by u/RaphaelAlvez
- /*Add Shuffle to selected piles from selected AB*/
- number: 1,
- widget: function(i) {
- shuffle=[];
- for (var n = 1; n <= $(".selected").length; n++) {
- a=widgets[$(".selected").eq(n-1).data("index")];
- if(a.type=="cardPile"){
- shuffle.push(a.id)}
- else{if (a.type="automationButton"){
- b=a}
- }
- };
- b.clickRoutine.push({
- "func": "SHUFFLE_CARDS",
- "args": {
- "holders": {
- "type": "literal",
- "value": shuffle
- }
- }
- });
- return ;
- }
- `,
- "Flip Routines": `
- // contributed by u/RaphaelAlvez
- /*Flip cards of selected piles from selected AB*/
- number: 1,
- widget: function(i) {
- Flip=[];
- for (var n = 1; n <= $(".selected").length; n++) {
- a=widgets[$(".selected").eq(n-1).data("index")];
- if(a.type=="cardPile"){
- Flip.push(a.id)}
- else{if (a.type="automationButton"){
- b=a}
- }
- };
- b.clickRoutine.push({
- "func": "FLIP_CARDS",
- "args": {
- "flipMode": {
- "type": "literal",
- "value": "pile" /* can be "pile" or "top" */
- },
- "holders": {
- "type": "literal",
- "value": Flip
- }
- }
- });
- return ;
- }
- `,
- "Set Flip Routines": `
- // contributed by u/RaphaelAlvez
- /* Set flip of cards of selected piles from selected AB */
- number: 1,
- widget: function(i) {
- FlipSet=[];
- for (var n = 1; n <= $(".selected").length; n++) {
- a=widgets[$(".selected").eq(n-1).data("index")];
- if(a.type=="cardPile"){
- FlipSet.push(a.id)}
- else{if (a.type="automationButton"){
- b=a}
- }
- };
- b.clickRoutine.push({
- "func": "FLIP_CARDS",
- "args": {
- "flipFace": {
- "type": "literal",
- "value": "faceUp" /* can be "faceUp" or "faceDown" */
- },
- "flipMode": {
- "type": "literal",
- "value": "pile" /* can be "pile" or "top" */
- },
- "holders": {
- "type": "literal",
- "value": FlipSet
- }
- }
- });
- return ;
- }
- `,
- "Counters Routines": `
- // contributed by u/RaphaelAlvez
- /* Add automation to selected counters from selected AB */
- number: 1,
- widget: function(i) {
- Counters=[];
- for (var n = 1; n <= $(".selected").length; n++) {
- a=widgets[$(".selected").eq(n-1).data("index")];
- if(a.type=="counter"){
- Counters.push(a.id)}
- else{if (a.type="automationButton"){
- b=a}
- }
- };
- b.clickRoutine.push({
- "func": "CHANGE_COUNTER",
- "args": {
- "counters": {
- "type": "literal",
- "value": Counters
- },
- "changeMode": {
- "type": "literal",
- "value": "set" /* can be "set", "inc" or "dec"*/
- },
- "changeNumber": {
- "type": "literal",
- "value": 10
- }
- }
- });
- return ;
- }
- `,
- "Create a movement loop between piles":`
- // contributed by u/RaphaelAlvez
- number: 1,
- widget: function(i) {
- var Quantity=1 ;
- var selected=$(".selected").get().sort((a, b) => $(a).data("selectionOrder") - $(b).data("selectionOrder"));
- loop=[];
- for (var n = 1; n <= $(".selected").length; n++) {
- a=widgets[$(".selected").eq(n-1).data("index")];
- if(a.type=="cardPile"){
- loop.push(a.id)}
- else if (a.type=="automationButton"){
- b=a};
- };
- /* finds the AB */
- var abIndex = selected.map((o) => widgets[$(o).data("index")].type).indexOf("automationButton");
- /* Checks if you did something wrong */
- if(abIndex==-1||loop.length==0){return};
- buffer=uniqueID("buffer")
- addWidget({
- "id": buffer,
- "x": 1650,
- "y": 0,
- "type": "cardPile",
- "width": loop[0].width,
- "height": loop[0].height
- })
- widgets[$(selected[abIndex]).data("index")].clickRoutine.push({
- "func": "MOVE_CARDS_BETWEEN_HOLDERS",
- "args": {
- "from": {
- "type": "literal",
- "value": loop[0]
- },
- "to": {
- "type": "literal",
- "value": [buffer]
- },
- "quantity": {
- "type": "literal",
- "value": Quantity
- } ,
- "moveFlip": {
- "type": "literal",
- "value": "none" /* Can be "none","faceUp" or "faceDown" */
- }
- }
- });
- for (var n = 1; n <= loop.length; n++) {
- /* Add routine */
- widgets[$(selected[abIndex]).data("index")].clickRoutine.push({
- "func": "MOVE_CARDS_BETWEEN_HOLDERS",
- "args": {
- "from": {
- "type": "literal",
- "value": [loop[n]]
- },
- "to": {
- "type": "literal",
- "value": [loop[n-1]]
- },
- "quantity": {
- "type": "literal",
- "value": Quantity
- } ,
- "moveFlip": {
- "type": "literal",
- "value": "none" /* Can be "none","faceUp" or "faceDown" */
- }
- }
- });
- };
- widgets[$(selected[abIndex]).data("index")].clickRoutine.push({
- "func": "MOVE_CARDS_BETWEEN_HOLDERS",
- "args": {
- "from": {
- "type": "literal",
- "value": [buffer]
- },
- "to": {
- "type": "literal",
- "value": [loop[loop.length-1]]
- },
- "quantity": {
- "type": "literal",
- "value": Quantity
- } ,
- "moveFlip": {
- "type": "literal",
- "value": "none" /* Can be "none","faceUp" or "faceDown" */
- }
- }
- });
- }
- `
- },
- "Other": {
- "Smart Rename":`
- // contributed by u/RaphaelAlvez
- // please note that you can edit the ID of widgets directly now (hotkey e)
- /* Changes the Id of the seleted Widget (just one) and makes the same change wherever the selected widget is cited */
- number: $(".selected").length,
- widget: function(i) {
- var newId ='"'+prompt("New ID?", widgets[$(".selected").eq(i).data("index")].id)+'"'
- widgets=JSON.parse(replaceall(JSON.stringify(widgets[$(".selected").eq(i).data("index")].id), newId, JSON.stringify(widgets)));
- }
- `,
- "Smart resize card deck":`
- /* select a card deck and this will resize its templates, cards and piles */
- number: $(".selected.type-cardDeck").length,
- widget: function(i) {
- var newHeight = 160;
- var w = widgets[$(".selected.type-cardDeck").eq(i).data("index")];
- var newWidth = newHeight/w.cardHeight*w.cardWidth;
- // this uses a function I needed somewhere else
- resizeCardDeck(w, newWidth, newHeight);
- }
- `,
- "Set pile as parents":`
- // contributed by u/RaphaelAlvez
- number: 1,
- widget: function(i) {
- Cards=[];
- Decks = [];
- b = null;
- for (var n = 1; n <= $(".selected").length; n++) {
- a=widgets[$(".selected").eq(n-1).data("index")];
- if(a.type=="card"){
- Cards.push(a)};
- if (a.type=="cardPile"){
- b=a};
- if (a.type=="cardDeck"){
- Decks.push(a)};
- };
- b.z=0;
- for (var n = 1; n <= Decks.length; n++) {
- Decks[n-1].parent=b.id;
- Decks[n-1].x= 13+(b.x);
- Decks[n-1].y= 44+(b.y);
- Decks[n-1].z= n;
- };
- for (var n = 1; n <= Cards.length; n++) {
- Cards[n-1].parent=b.id;
- Cards[n-1].x= 4+(b.x);
- Cards[n-1].y= 4+(b.y);
- Cards[n-1].z= n+(Decks.length);
- };
- return ;
- }
- `,
- "Reset height and width":`
- // contributed by u/RaphaelAlvez
- number: $(".selected").length,
- widget: function(i) {
- w = widgets[$(".selected").eq(i).data("index")];
- if (w.type=="gamePiece"){[w.width,w.height]=defaultSizes["gamePiece"][w.pieceType]}
- else {[w.width,w.height]=defaultSizes[w.type]}
- }
- `,
- "Human-Readable Widget IDs":`
- // contributed by u/RaphaelAlvez
- /* Changes the Id of all Widgets to something like "spinner001" and makes the same change wherever the widgets' id is cited. */
- number: widgets.length,
- widget: function(i) {
- if(widgets[i].id!="hand") {
- var newId ='"'+uniqueID(widgets[i].type)+'"'
- /* The uniqueID() function can be used to create an unique ID using a string as input. Here I used the widget's type as input. */
- widgets=JSON.parse(replaceall(JSON.stringify(widgets[i].id), newId, JSON.stringify(widgets)));
- }
- }
- `,
- "Empty for tests": `
- // contributed by u/RaphaelAlvez
- number: 1,
- widget: function(i) {
- return ;
- }
- `
- }
- };
- var buttons = {
- folder_open: [ "open a PCIO file", "o", "ONE_SHOT", function() {
- $("#file").click();
- } ],
- save_alt: [ "save as PCIO file (triggers browser download)<br><br>this takes around 30s for a 50MiB file", "s", "ONE_SHOT", function() {
- lastFilename = prompt("Name?", lastFilename || zipFile.name);
- if(!lastFilename)
- return;
- JSZip.loadAsync(zipFile).then(function(zip) {
- zip.file("widgets.json", JSON.stringify(widgets));
- for(var name in imageCache)
- if(name != "widgets.json")
- zip.file(name, imageCache[name], { base64: true });
- for(var name in removeFromZip)
- zip.remove(name);
- zip.generateAsync({type:"base64"}).then(function (base64) {
- downloadURI("data:application/zip;base64," + base64, lastFilename);
- }, function (e) {
- console.log("error saving zip file", e);
- });
- });
- } ],
- import_export: [ "advanced file operations<ul><li>import from second PCIO</li><li>import from Tabletop Simulator</li><li>quick testing in game</li><li>turn links into assets and vice versa</li></ul>", "X", {
- insert_drive_file: [ "start from scratch", "0", "ONE_SHOT", function() {
- clearUndoStack();
- zipFile = dataURLtoFile("data:application/zip;base64,UEsDBAoAAAAAAGRkMFHZQGDUQgAAAEIAAAAMAAAAd2lkZ2V0cy5qc29uW3siaWQiOiJoYW5kIiwidHlwZSI6ImhhbmQiLCJ4Ijo1MCwieSI6ODIwLCJ6IjoxLCJkcmFnZ2luZyI6bnVsbH1dUEsBAhQACgAAAAAAZGQwUdlAYNRCAAAAQgAAAAwAAAAAAAAAAAAAAAAAAAAAAHdpZGdldHMuanNvblBLBQYAAAAAAQABADoAAABsAAAAAAA=", "unnamed.pcio");
- widgets = [{"id":"hand","type":"hand","x":50,"y":820,"z":1,"dragging":null}];
- lastFilename = "unnamed";
- imageCache = {};
- boardImageCache = {};
- compressedImageCache = {};
- removeFromZip = {};
- $("#editPreview").empty();
- $(".selected").removeClass("selected");
- drawWidgets(widgets);
- } ],
- sep1: 1,
- exit_to_app: [ "add all widgets from another PCIO file<br><br>delete everything you don't want from it first", "O", "ONE_SHOT", function() {
- $("#fileImportWidgets").click();
- } ],
- sep2: 1,
- casino: [ "import Tabletop Simulator mod", "t", "TOGGLE_TOOL", "#tabletopImporter" ],
- sep3: 1,
- layers_clear: [ "save as stripped PCIO file<br><br>no card images for fast testing of everything else<br><br>don't touch the contents of card decks with the vanilla editor", "S", "ONE_SHOT", function() {
- lastFilename = prompt("Name?", lastFilename || zipFile.name);
- if(!lastFilename)
- return;
- var zip = new JSZip();
- zip.file("widgets.json", JSON.stringify(widgets));
- for(var name in boardImageCache)
- zip.file(name, imageCache[name], { base64: true });
- zip.generateAsync({type:"base64"}).then(function (base64) {
- downloadURI("data:application/zip;base64," + base64, lastFilename);
- }, function (e) {
- console.log("error saving zip file", e);
- });
- } ],
- assignment_returned: [ "import changes from a re-exported stripped PCIO file", "i", "ONE_SHOT", function() {
- $("#fileStripped").click();
- } ],
- sep4: 1,
- cloud_download: [ "turn all http links into packaged assets", "p", "ONE_SHOT", function() {
- var urls = JSON.stringify(widgets).match(/https?:\/\/[^"]+/g);
- if(urls)
- $("#statusTool").text("0/" + urls.length + " downloaded");
- else
- $("#statusTool").text("no HTTP links found");
- var downloaded = 0;
- $.each(urls, function(i, url) {
- var filename = "userassets/gpe-dl-" + hashFnv32a(url, true);
- toDataURL(url, function(data) {
- imageCache[filename] = data.replace(/data.*?,/, '');
- widgets = JSON.parse(JSON.stringify(widgets).replace(url, "package://" + filename));
- ++downloaded;
- $("#statusTool").text(downloaded + "/" + urls.length + " downloaded");
- drawWidgets(widgets);
- });
- });
- } ],
- cloud_upload: [ "turn all packaged assets into http links", "W", "TOGGLE_TOOL", "#cloudUpload" ]
- } ],
- sep1: 1,
- undo: [ "undo<br><br>only widgets.json is supported - changes to images will remain/break", "z", "ONE_SHOT", function() {
- popUndoStack();
- } ],
- content_copy: [ "copy", "c", "ONE_SHOT", function() {
- copy=[];
- $(".selected").each(function() {
- copy.push(JSON.parse(JSON.stringify(widgets[$(this).data("index")])));
- });
- copyTextToClipboard(JSON.stringify(copy));
- } ],
- content_paste: [ "paste", "v", "ONE_SHOT", function(isHotkey) {
- pushUndoStack();
- if(typeof copy !== "undefined" && copy.length)
- paste=JSON.parse(JSON.stringify(copy));
- else
- paste=JSON.parse(prompt("Paste here:"));
- var b = $("#room").get(0).getBoundingClientRect();
- var scale = 1600 / (b.width - 2);
- var x = Math.floor((x2 - b.x) * scale);
- var y = Math.floor((y2 - b.y) * scale);
- if(!isHotkey) {
- x = 100;
- y = 100;
- }
- var minX = 1600;
- var minY = 1000;
- paste.forEach(function(w){
- if (minX>w.x){minX=w.x};
- if (minY>w.y){minY=w.y};
- })
- var idsToSelect = [];
- paste.forEach(function(w){
- w.x += x-minX;
- w.y += y-minY;
- delete w.z;
- var newID = uniqueID(w.id + "-copy");
- renameID(paste, w.id, newID);
- addWidget(w);
- idsToSelect.push(newID);
- });
- drawWidgets(widgets);
- $(".widget").each(function() {
- if(idsToSelect.indexOf(widgets[$(this).data("index")].id) != -1)
- $(this).click();
- });
- } ],
- delete: [ "delete<br><br>it's a good idea to use the button 'remove unused card type variables,[...]' afterwards", "Delete", "ONE_SHOT", function() {
- pushUndoStack();
- var selected = $.map($(".selected"), (s) => widgets[$(s).data("index")].id);
- widgets = widgets.filter((w) => selected.indexOf(w.id) == -1);
- drawWidgets(widgets);
- } ],
- select_all: [ "select the previously selected widgets", "-", "ONE_SHOT", function() {
- $(".widget").each(function() {
- $(this).toggleClass("selected", previouslySelected.indexOf(widgets[$(this).data("index")].id) != -1);
- $("#statusTool").text($(".selected").length + " widgets selected");
- });
- } ],
- sep2: 1,
- business_center: [ "widgets<br><br>add widgets similarly to the standard editor", "w", "TOGGLE_TOOL", "#toolBox" ],
- edit: [ "edit widgets", "e", {
- html1: "<label for='quickSetID'>ID </label><input id='quickSetID' class='text'>",
- fingerprint: [ "set ID", "none", function(allSelected, i, widget) {
- if(widgets.map((w) => w.id).indexOf($("#quickSetID").val()) == -1)
- renameID(widgets, widget.id, $("#quickSetID").val());
- else
- alert("That ID already exists.");
- } ],
- sep1: 1,
- notes: [ "edit the JSON representation of a widget directly", "j", "TOGGLE_TOOL", "#jsonEdit" ],
- sep2: 1,
- mediation: [ "merge selected card decks into one<br><br>this doesn't do magic<br>they should be similar", "+", "ONE_SHOT", function() {
- pushUndoStack();
- var allSelected = $.map($(".selected"), (s) => widgets[$(s).data("index")]);
- var widget = allSelected.shift();
- allSelected.forEach(function(w) {
- Object.assign(widget.cardTypes, w.cardTypes);
- widgets = widgets.filter((v) => v.id != w.id);
- for(var wi in widgets)
- if(widgets[wi].deck !== undefined)
- widgets[wi].deck = widgets[wi].deck == w.id ? widget.id : widgets[wi].deck;
- });
- drawWidgets(widgets);
- } ],
- settings_applications: [ "edit card decks<ul><li>edit settings</li><li>merge card types</li><li>split decks</li><li>add rotation</li></ul>", "d", "TOGGLE_TOOL", "#editCardDecks" ]
- } ],
- architecture: [ "align the selected widgets<br><br>manually set or offset the coordinates of the selected widgets<br><br>the input fields support formulas like 12+32", ".", {
- html1: `
- <label for='quickSetX'>X </label><input id='quickSetX' class='inactive quickSet x'>
- <label for='quickSetY'>Y </label><input id='quickSetY' class='inactive quickSet y'>
- <label for='quickSetW'>W </label><input id='quickSetW' class='inactive quickSet width'>
- <label for='quickSetH'>H </label><input id='quickSetH' class='inactive quickSet height'>
- `,
- highlight_alt: [ "align widgets to the right/bottom", "b", "ONE_SHOT", function() {
- $(".highlight_alt").toggleClass("activated");
- fillDetails($(".selected").get(0));
- } ],
- sep1: 1,
- check: [ "set values directly to the widgets", "none", function(allSelected, i, widget) {
- [ "x", "y", "width", "height" ].forEach(function(field) {
- if(!$(".quickSet." + field).is(".inactive")) {
- var value = +eval($(".quickSet." + field).val());
- if($(".highlight_alt").is(".activated") && [ "x", "y" ].indexOf(field) > -1)
- widget[field] = (field == "x" ? 1600 : 1000) - allSelected[i][field == "x" ? "width" : "height"] - value;
- else
- widget[field] = value;
- if($(".highlight_alt").is(".activated") && field == "width")
- widget.x -= widget.width - allSelected[i].width;
- if($(".highlight_alt").is(".activated") && field == "height")
- widget.y -= widget.height - allSelected[i].height;
- }
- });
- if(i == allSelected.length - 1)
- $(".quickSet").addClass("inactive");
- } ],
- double_arrow: [ "add values to current values of the widgets", "none", function(allSelected, i, widget) {
- [ "x", "y", "width", "height" ].forEach(function(field) {
- if(!$(".quickSet." + field).is(".inactive")) {
- var value = +eval($(".quickSet." + field).val());
- if($(".highlight_alt").is(".activated") && [ "x", "y" ].indexOf(field) > -1)
- widget[field] = allSelected[i][field] - value;
- else
- widget[field] = allSelected[i][field] + value;
- if($(".highlight_alt").is(".activated") && field == "width")
- widget.x -= value;
- if($(".highlight_alt").is(".activated") && field == "height")
- widget.y -= value;
- }
- });
- if(i == allSelected.length - 1)
- $(".quickSet").addClass("inactive");
- } ],
- settings_ethernet: [ "set the space between the selected widgets to the entered value", "none", "ONE_SHOT", function() {
- pushUndoStack();
- var allSelected = [];
- $(".selected").each(function() {
- allSelected.push(addDimensions(widgets[$(this).data("index")]));
- });
- [ "x", "y" ].forEach(function(dir) {
- if($(".quickSet." + dir).is(".inactive"))
- return;
- var dim = dir == "x" ? "width" : "height";
- var minCoord = Math.min(...allSelected.map((w) => w[dir]));
- var maxCoord = Math.max(...allSelected.map((w) => w[dir]+w[dim]));
- var spacing = +eval($(".quickSet." + dir).val());
- if($(".highlight_alt").is(".activated"))
- allSelected.sort((a, b) => a[dir]+a[dim] > b[dir]+b[dim] ? 1 : a[dir]+a[dim] < b[dir]+b[dim] ? -1 : 0);
- else
- allSelected.sort((a, b) => a[dir] > b[dir] ? 1 : a[dir] < b[dir] ? -1 : 0);
- $(".selected").each(function() {
- var i = allSelected.findIndex((w) => w.id == widgets[$(this).data("index")].id);
- var w = widgets[$(this).data("index")];
- if($(".highlight_alt").is(".activated")) {
- var after = allSelected.slice(i);
- w[dir] = maxCoord - after.map((w) => w[dim] + spacing).reduce((a, b) => a+b, 0) + spacing;
- } else {
- var before = allSelected.slice(0, i);
- w[dir] = minCoord + before.map((w) => w[dim] + spacing).reduce((a, b) => a+b, 0);
- }
- });
- });
- $(".quickSet").addClass("inactive");
- drawWidgets(widgets);
- } ],
- sep2: 1,
- align_vertical_top: [ "align elements to the highest top edge", "ArrowUp", function(allSelected, i, widget) {
- widget.y = Math.min.apply(Math, allSelected.map((w) => w.y));
- } ],
- align_vertical_center: [ "align element centers to the middle between the highest top edge and lowest bottom edge", "V", function(allSelected, i, widget, wd) {
- var minY = Math.min.apply(Math, allSelected.map((w) => w.y))
- var maxY = Math.max.apply(Math, allSelected.map((w) => w.y + w.height))
- widget.y = minY + (maxY-minY)/2 - wd.height/2;
- } ],
- align_vertical_bottom: [ "align elements to the lowest bottom edge", "ArrowDown", function(allSelected, i, widget, wd) {
- widget.y = Math.max.apply(Math, allSelected.map((w) => w.y + w.height)) - wd.height;
- } ],
- vertical_distribute: [ "equalize the spacing between all the elements vertically", "k", function(allSelected, i, widget, wd) {
- var minY = Math.min.apply(Math, allSelected.map((w) => w.y))
- var maxY = Math.max.apply(Math, allSelected.map((w) => w.y + w.height))
- var heights = allSelected.map((w) => w.height).reduce((a, b) => a+b, 0)
- var spacing = (maxY-minY-heights)/(allSelected.length-1);
- allSelected.sort((a, b) => a.y > b.y ? 1 : a.y < b.y ? -1 : 0);
- var before = allSelected.slice(0, allSelected.findIndex((w) => w.id == widget.id));
- widget.y = minY + before.map((w) => w.height + spacing).reduce((a, b) => a+b, 0);
- } ],
- align_horizontal_left: [ "align elements to the leftmost left edge", "ArrowLeft", function(allSelected, i, widget) {
- widget.x = Math.min.apply(Math, allSelected.map((w) => w.x));
- } ],
- align_horizontal_center: [ "align element centers to the middle between the leftmost left edge and rightmost right edge", "H", function(allSelected, i, widget, wd) {
- var minX = Math.min.apply(Math, allSelected.map((w) => w.x))
- var maxX = Math.max.apply(Math, allSelected.map((w) => w.x + w.width))
- widget.x = minX + (maxX-minX)/2 - wd.width/2;
- } ],
- align_horizontal_right: [ "align elements to the rightmost right edge", "ArrowRight", function(allSelected, i, widget, wd) {
- widget.x = Math.max.apply(Math, allSelected.map((w) => w.x + w.width)) - wd.width;
- } ],
- horizontal_distribute: [ "equalize the spacing between all the elements horizontally", "h", function(allSelected, i, widget) {
- var minX = Math.min.apply(Math, allSelected.map((w) => w.x));
- var maxX = Math.max.apply(Math, allSelected.map((w) => w.x + w.width));
- var widths = allSelected.map((w) => w.width).reduce((a, b) => a+b, 0);
- var spacing = (maxX-minX-widths)/(allSelected.length-1);
- allSelected.sort((a, b) => a.x > b.x ? 1 : a.x < b.x ? -1 : 0);
- var before = allSelected.slice(0, allSelected.findIndex((w) => w.id == widget.id));
- widget.x = minX + before.map((w) => w.width + spacing).reduce((a, b) => a+b, 0);
- } ],
- } ],
- memory: [ "macros<br><br>write custom Javascript or use presets as powerful editing tools", "#", "TOGGLE_TOOL", "#macros" ],
- sep3: 1,
- library_add_check: [ "align all card decks and cards with their parent pile", "a", "ONE_SHOT", function() {
- pushUndoStack();
- for(var w in widgets) {
- if(typeof widgets[w].parent === "string") {
- var p = widgets.filter((v) => v.id == widgets[w].parent)[0];
- if(widgets[w].type == "cardDeck") {
- if(p) {
- widgets[w].x = p.x + 13;
- widgets[w].y = p.y + 44;
- } else {
- widgets[w].parent = null;
- }
- }
- if(widgets[w].type == "card" && widgets[w].parent) {
- p = widgets.filter((v) => v.id == widgets[w].parent)[0];
- if(p) {
- widgets[w].x = p.x + 4;
- widgets[w].y = p.y + 4;
- } else {
- widgets[w].parent = null;
- }
- }
- }
- }
- drawWidgets(widgets);
- } ],
- photo_size_select_large: [ "edit images<ul><li>resize and compress</li><li>rotate</li><li>crop</li></ul>", "x", "TOGGLE_TOOL", "#editImages" ],
- link_off: [ "remove unused card type variables, cards without a deck and unreferenced images", "u", "ONE_SHOT", function() {
- var keysDeleted = 0;
- var imagesDeleted = 0;
- var widgetIDsToRemove = [];
- for(var w in widgets) {
- if(widgets[w].type === "cardDeck") {
- var validVariables = [ "label" ];
- for(var field in { faceTemplate: 1, backTemplate: 1 }) {
- validVariables = validVariables.concat(widgets[w][field].objects.map((v) => v.valueType == "dynamic" && v.value).filter((v) => v !== false));
- }
- for(var c in widgets[w].cardTypes) {
- for(var key in widgets[w].cardTypes[c]) {
- if(validVariables.indexOf(key) == -1) {
- ++keysDeleted;
- delete widgets[w].cardTypes[c][key];
- }
- }
- }
- }
- if(widgets[w].type === "card")
- if(!widgets.filter((v) => v.id == widgets[w].deck).length)
- widgetIDsToRemove.push(widgets[w].id);
- }
- widgets = widgets.filter((w) => widgetIDsToRemove.indexOf(w.id) == -1);
- var imagesFound = JSON.stringify(widgets).match(/"package:\/\/[^"]+/g);
- imagesFound = imagesFound && imagesFound.map((x) => x.substr(11)) || [];
- $("#statusTool").text("deleted " + widgetIDsToRemove.length + " cards, " + keysDeleted + " variables and 0 images");
- JSZip.loadAsync(zipFile).then(function(zip) {
- for(var file in zip.files) {
- if(file != "widgets.json" && file != "userassets/" && imagesFound.indexOf(file) == -1 && removeFromZip[file] == undefined) {
- console.log("Deleting image " + file);
- ++imagesDeleted;
- removeFromZip[file] = true;
- delete imageCache[file];
- $("#statusTool").text("deleted " + widgetIDsToRemove.length + " cards, " + keysDeleted + " variables and " + imagesDeleted + " images");
- }
- }
- });
- for(var file in imageCache) {
- if(file != "widgets.json" && file != "userassets/" && imagesFound.indexOf(file) == -1 && removeFromZip[file] == undefined) {
- console.log("Deleting image " + file);
- ++imagesDeleted;
- removeFromZip[file] = true;
- delete imageCache[file];
- $("#statusTool").text("deleted " + widgetIDsToRemove.length + " cards, " + keysDeleted + " variables and " + imagesDeleted + " images");
- }
- }
- drawWidgets(widgets);
- } ],
- sep4: 1,
- open_with: [ "move widgets with the mouse instead of selecting them", "m", "ONE_SHOT", function() {
- if(resizeWidgets)
- $(".settings_overscan").click();
- moveWidgets = !moveWidgets;
- $(".open_with").toggleClass("activated", moveWidgets);
- } ],
- settings_overscan: [ "resize widgets with the mouse instead of selecting them", "r", "ONE_SHOT", function() {
- if(moveWidgets)
- $(".open_with").click();
- resizeWidgets = !resizeWidgets;
- $(".settings_overscan").toggleClass("activated", resizeWidgets);
- } ],
- filter_alt: [ "filter widget types", "f", "TOGGLE_TOOL", "#typeFilter" ],
- filter_b_and_w: [ "toggle realistic visualization<br><br>more or less", "R", "ONE_SHOT", function() {
- $("#room").toggleClass("realistic");
- $(".filter_b_and_w").toggleClass("activated", $("#room").is(".realistic"));
- drawWidgets(widgets);
- } ],
- sep5: 1,
- build_circle: [ "start a new custom playingcards.io room", "none", "ONE_SHOT", function() {
- window.open("https://playingcards.io/room/new/generic", "_blank");
- } ],
- brush: [ "open Photopea", "none", "ONE_SHOT", function() {
- window.open("https://www.photopea.com/", "_blank");
- } ],
- help: [ "hotkeys & changelog", "?", "TOGGLE_TOOL", "#help" ]
- };
- var hotkeys = {
- A: $("#automationButton"),
- B: $("#board"),
- C: $("#card"),
- D: $("#cardDeck"),
- G: $("#gamePiece"),
- I: $("#spinner"),
- L: $("#label"),
- N: $("#hand"),
- P: $("#cardPile"),
- T: $("#counter")
- };
- function buildToolbar(buttons, toolbar) {
- for(var b in buttons) {
- if(b.match(/^sep[0-9]+$/)) {
- $("<span class='separator'> | </span>").appendTo(toolbar);
- continue;
- }
- if(b.match(/^html[0-9]+$/)) {
- $(buttons[b]).appendTo(toolbar);
- continue;
- }
- var toolInfo = "<br><br><i>Hotkey: " + buttons[b][1] + "</i>";
- if(buttons[b][2] === "TOGGLE_TOOL")
- toolInfo += "<br><br><i>this button opens a tool</i>";
- if(hotkeys[buttons[b][1]] && buttons[b][1] != "none")
- console.log("HOTKEY CONFLICT for " + buttons[b][1]);
- if(typeof buttons[b][2] === "object") {
- var subBar = $("<div class='toolbar " + b + "'></div>").appendTo(".secondary.toolbarContainer").hide();
- buildToolbar(buttons[b][2], subBar);
- toolInfo += "<br><br><i>this button opens a secondary toolbar</i>";
- }
- hotkeys[buttons[b][1]] = $("<button class='material-icons " + b + "'>" + b + "<span class='tooltip'>" + buttons[b][0] + toolInfo + "</span></button>").on("click", function() {
- var button = this;
- if(buttons[button.firstChild.nodeValue][2] === "ONE_SHOT") {
- buttons[button.firstChild.nodeValue][3](window.triggeredByHotkey || false);
- return;
- }
- if(buttons[button.firstChild.nodeValue][2] === "TOGGLE_TOOL") {
- $(this).toggleClass("activated");
- $(buttons[button.firstChild.nodeValue][3]).toggle($(this).is(".activated"));
- return;
- }
- if(typeof buttons[button.firstChild.nodeValue][2] === "object") {
- if($(this).is(".toolbarButton:not(.activated)"))
- $(this).siblings(".activated.toolbarButton").click();
- else
- $(".toolbar." + button.firstChild.nodeValue + " .activated.toolbarButton").click();
- $(this).toggleClass("activated").blur();
- $(".toolbar." + button.firstChild.nodeValue).toggle($(this).is(".activated"));
- return;
- }
- var allSelected = [];
- $(".selected").each(function() {
- allSelected.push(addDimensions(widgets[$(this).data("index")]));
- });
- if(allSelected.length > 0)
- pushUndoStack();
- var i = 0;
- $(".selected").each(function() {
- buttons[button.firstChild.nodeValue][2](allSelected, i++, widgets[$(this).data("index")], addDimensions(widgets[$(this).data("index")]));
- });
- drawWidgets(widgets);
- }).appendTo(toolbar);
- if(typeof buttons[b][2] === "object")
- hotkeys[buttons[b][1]].addClass("toolbarButton");
- }
- }
- buildToolbar(buttons, $(".primary.toolbar"));
- $("#file").on("change", function(evt) {
- clearUndoStack();
- zipFile = evt.target.files[0];
- lastFilename = zipFile.name
- imageCache = {};
- boardImageCache = {};
- compressedImageCache = {};
- removeFromZip = {};
- $("#editPreview").empty();
- JSZip.loadAsync(zipFile).then(function(zip) {
- return zip.files["widgets.json"].async("text");
- }).then(function(content) {
- widgets = JSON.parse(content);
- $(".selected").removeClass("selected");
- drawWidgets(widgets);
- }, function (e) {
- console.log("error loading zip file", e);
- });
- });
- $("#fileImportWidgets").on("change", function(evt) {
- pushUndoStack();
- loadAllImages(evt.target.files[0], function() {
- JSZip.loadAsync(evt.target.files[0]).then(function(zip) {
- return zip.files["widgets.json"].async("text");
- }).then(function(content) {
- widgets = widgets.concat(JSON.parse(content));
- drawWidgets(widgets);
- }, function (e) {
- console.log("error loading zip file", e);
- });
- });
- });
- $("#fileImportTabletop").on("change", function(evt) {
- tabletop.showImport(evt.target.files[0]);
- $("#tabletopImporter").show();
- });
- $("#checkHAR").on("click", function() {
- var count = 0;
- var errors = 0;
- loadAllImages(zipFile, function() {
- var fileNames = {};
- var duplicates = 0;
- for(var i in imageCache) {
- if(fileNames[imageCache[i]] === undefined) {
- fileNames[imageCache[i]] = i;
- } else {
- console.log("replacing duplicate " + i + " by " + fileNames[imageCache[i]]);
- widgets = JSON.parse(replaceall(JSON.stringify("package://" + i), JSON.stringify("package://" + fileNames[imageCache[i]]), JSON.stringify(widgets)));
- delete imageCache[i];
- removeFromZip[i] = true;
- ++duplicates;
- }
- }
- var multiParts = {};
- JSON.parse($("#firefoxHAR").val()).log.entries.forEach(function(entry) {
- if(entry.request.method == "POST") {
- var request = entry.request.postData;
- var image = request.text.replace(/^(.|\r|\n)*?Content-Type: image\/.*\r\n\r\n/, "").replace(/\r\n.*$/, "");
- var response = null;
- try {
- response = JSON.parse(entry.response.content.text);
- } catch(e) {
- var name = "";
- entry.request.queryString.forEach(function(x) { if(x.name == "name") name = x.value; });
- for(var i in entry.request.headers) {
- var h = entry.request.headers[i];
- if(h.name == "X-Goog-Upload-Command" && h.value == "start")
- multiParts[name] = "";
- if(h.name == "X-Goog-Upload-Command" && h.value == "upload")
- multiParts[name] += image;
- }
- return;
- }
- var url = "https://firebasestorage.googleapis.com/v0/b/playingcardsio.appspot.com/o/" + response.name + "?alt=media&token=" + response.downloadTokens;
- //$("body").append("<img src='" + url + "'>");
- //$("body").append("<img src='data:" + imageType(btoa(image)) + ";base64," + btoa(image) + "'>");
- var packageURL = fileNames[btoa((multiParts[response.name] || "") + image)];
- if(packageURL) {
- if(!removeFromZip[packageURL]) {
- widgets = JSON.parse(replaceall(JSON.stringify("package://" + packageURL), JSON.stringify(url), JSON.stringify(widgets)));
- removeFromZip[packageURL] = true;
- delete imageCache[packageURL];
- ++count;
- }
- } else {
- console.log("PACKAGED ASSET NOT FOUND", entry, url, btoa((multiParts[response.name] || "") + image));
- ++errors;
- }
- $("#statusTool").text("replaced " + count + " assets (" + errors + " assets not found - " + duplicates + " duplicates merged)");
- }
- });
- });
- });
- $("#typeFilter input").on("change", function() {
- drawWidgets(widgets);
- });
- $(function() {
- for(var header in generateWidgetPresets) {
- var optGroup = $("<optgroup label='" + header +"'>");
- for(var name in generateWidgetPresets[header])
- $("<option data-group='" + header + "'>" + name + "</option>").appendTo(optGroup);
- optGroup.appendTo($("#macros select"));
- }
- $("#macros select").change();
- });
- $("#macros select").on("change", function(evt) {
- $("#macros textarea").val(generateWidgetPresets[$("option:selected", this).data("group")][$(this).val()].replace(/^ /gm, '').trim());
- });
- $("#macros [type=button]").on("click", function(evt) {
- pushUndoStack();
- eval("obj = { " + $("#macros textarea").val() + "}");
- for(var i=0; i<(obj.number || 1); ++i) {
- var newWidget = obj.widget(i);
- if(typeof newWidget === "object" && newWidget.id !== undefined)
- addWidget(newWidget);
- else
- $("#statusTool").text("The generated widget was not added. It did not have an ID.");
- }
- drawWidgets(widgets);
- });
- $("#fileStripped").on("change", function(evt) {
- pushUndoStack();
- var tempZipFile = evt.target.files[0];
- JSZip.loadAsync(tempZipFile).then(function(zip) {
- return zip.files["widgets.json"].async("text");
- }).then(function(content) {
- newWidgets = JSON.parse(content);
- for(var i in newWidgets) {
- if(newWidgets[i].type == "board") {
- var oldWidget = widgets.filter((v) => v.id == newWidgets[i].id)[0];
- var newName = newWidgets[i].boardImage.replace("package://", "");
- var oldName = oldWidget.boardImage.replace("package://", "");
- if(newName != oldName) {
- if(oldWidget.boardImage.match(/^package/)) {
- delete imageCache[oldName];
- removeFromZip[oldName] = true;
- }
- if(newWidgets[i].boardImage.match(/^package/))
- JSZip.loadAsync(tempZipFile).then((zip) => zip.files[newName].async("base64").then(function(b) { imageCache[newName] = b; drawWidgets(widgets); }));
- }
- }
- }
- widgets = newWidgets;
- drawWidgets(widgets);
- }, function (e) {
- console.log("error loading zip file", e);
- });
- });
- $("#room").on("click", ".widget", function() {
- $(this).toggleClass("selected").data("selectionOrder", selectionOrder++);
- $("#statusTool").text($(".selected").length + " widgets selected");
- fillDetails(this);
- })
- $("#jsonEdit .set").on("click", function() {
- pushUndoStack();
- widgets[$(".selected").data("index")] = json_postProcess(JSON.parse($("#json").val()));
- drawWidgets(widgets);
- });
- $("#editCardDecks input.set").on("click", function() {
- pushUndoStack();
- $(".selected").each(function() {
- var widget = widgets[$(this).data("index")];
- if($("#cardOverlapH").get(0).checked)
- widget.cardOverlapH = null;
- else
- widget.cardOverlapH = 0;
- if($("#onRemoveFromHand").get(0).checked)
- delete widget.onRemoveFromHand;
- else
- widget.onRemoveFromHand = null;
- if($("#confirmRecall").get(0).checked)
- widget.confirmRecall = true;
- else
- delete widget.confirmRecall;
- if($("#confirmRecallAll").get(0).checked)
- delete widget.confirmRecallAll;
- else
- widget.confirmRecallAll = false;
- if($("#enlarge").get(0).checked)
- widget.enlarge = true;
- else
- delete widget.enlarge;
- });
- drawWidgets(widgets);
- });
- var ctrlIsPressed = false;
- var shiftIsPressed = false;
- var altIsPressed = false;
- document.onkeydown = ({key}) => {
- if(key == "Control")
- ctrlIsPressed = true;
- if(key == "Alt")
- altIsPressed = true;
- if(key == "Shift")
- shiftIsPressed = true;
- if($("input:focus, textarea:focus").filter(":not([type])").length > 0)
- return true;
- if(key == "Escape") {
- $(".activated").click();
- return false;
- }
- //console.log(key, ctrlIsPressed, altIsPressed, shiftIsPressed);
- if(shiftIsPressed && key.match(/^Arrow/)) {
- $(".selected").each(function() {
- var left = key == "ArrowLeft" ? -1 : (key == "ArrowRight" ? 1 : 0);
- var top = key == "ArrowUp" ? -1 : (key == "ArrowDown" ? 1 : 0);
- if(ctrlIsPressed) {
- left *= 5;
- top *= 5;
- }
- $(this).offset({ left: $(this).offset().left+left, top: $(this).offset().top+top });
- widgets[$(this).data("index")].x += left;
- widgets[$(this).data("index")].y += top;
- var w = addDimensions(widgets[$(this).data("index")]);
- $("#statusWidget").text(w.type + " " + Math.floor(w.width*10)/10 + "x" + Math.floor(w.height*10)/10 + "+" + Math.floor(w.x*10)/10 + "+" + Math.floor(w.y*10)/10);
- });
- return false;
- }
- if(key == "B") {
- $("#board").click();
- return false;
- }
- if(key == "C") {
- $("#card").click();
- return false;
- }
- if(key == "P") {
- $("#cardPile").click();
- return false;
- }
- if(hotkeys[key]) {
- triggeredByHotkey = true;
- hotkeys[key].click();
- triggeredByHotkey = false;
- return false;
- }
- };
- document.onkeyup = ({key}) => {
- if(key == "Control")
- ctrlIsPressed = false;
- if(key == "Alt")
- altIsPressed = false;
- if(key == "Shift")
- shiftIsPressed = false;
- };
- function loadAllImages(zipfileToLoad, callback) {
- var toLoad = 0;
- var loaded = 0;
- JSZip.loadAsync(zipfileToLoad).then(function(zip) {
- zip.forEach(() => ++toLoad);
- zip.forEach(function(filename, file) {
- if(imageCache[filename] === undefined) {
- file.async("base64").then(function(content) {
- imageCache[filename] = content;
- ++loaded;
- if(loaded == toLoad)
- callback();
- });
- } else {
- ++loaded;
- if(loaded == toLoad)
- callback();
- }
- });
- });
- }
- function addResultToEditPreview(dataURL, zipPath, target) {
- compressedImageCache[zipPath] = dataURL.replace(/data.*?,/, '');
- target.children("td:eq(2)").append("<img src=\"" + dataURL + "\">");
- target.children("td:eq(3)").append(Math.round(atob(compressedImageCache[zipPath]).length/1024) + " KiB");
- }
- function addImageToEditPreview(width, height, path, $table, button) {
- if(!path.match(/^package/))
- return;
- path = path.replace("package://", "");
- var $tr = $("<tr><td>Loading...</td><td></td><td></td><td></td></tr>").appendTo($table);
- loadImage(path, [ $tr, path ], function(image, type, vars) {
- var target = vars[0];
- var zipPath = vars[1];
- target.children("td:eq(0)").empty().append("<img src=\"data:" + type + ";base64," + image + "\">");
- target.children("td:eq(1)").append(Math.round(atob(image).length/1024) + " KiB");
- if($(button).is(".compress"))
- compress("data:" + type + ";base64," + image, width, height, $("#compressType").val(), +$("#compressQuality").val(), (dataURL) => addResultToEditPreview(dataURL, zipPath, target));
- if($(button).is(".rotate"))
- rotate("data:" + type + ";base64," + image, +$("#rotateDegrees").val()).then((dataURL) => addResultToEditPreview(dataURL, zipPath, target));
- if($(button).is(".crop"))
- crop("data:" + type + ";base64," + image, +$("#cropX").val(), +$("#cropY").val(), +$("#cropWidth").val(), +$("#cropHeight").val(), (dataURL) => addResultToEditPreview(dataURL, zipPath, target));
- });
- }
- $("#editImages [type=button]").on("click", function() {
- var button = this;
- $("#editPreview").empty().text("Loading...");
- compressedImageCache = {};
- loadAllImages(zipFile, function() {
- $("#editPreview").empty();
- $(".selected").each(function() {
- var widget = widgets[$(this).data("index")];
- if(widget.type == "board" || widget.type == "cardDeck") {
- widget = addDimensions(widget);
- $("#editPreview").append("<h2>" + widget.type + ": " + widget.id + "</h2>");
- var $table = $("<table><tr><th>Original</th><th>Size</th><th>Resized</th><th>Size</th></tr></table>").appendTo("#editPreview");
- $table.data("widgetIndex", $(this).data("index"));
- }
- if(widget.type == "board") {
- var width = +$("#compressWidth").val();
- var height = width*widget.height/widget.width;
- addImageToEditPreview(width, height, widget.boardImage, $table, button);
- }
- if(widget.type == "cardDeck") {
- var width = +$("#compressWidth").val();
- var height = width*widget.cardHeight/widget.cardWidth;
- for(var field in { faceTemplate: 1, backTemplate: 1 })
- for(var obj in widget[field].objects)
- if(widget[field].objects[obj].type == "image" && widget[field].objects[obj].valueType == "static")
- addImageToEditPreview(width, height, widget[field].objects[obj].value, $table, button);
- for(var t in widget.cardTypes)
- for(var key in widget.cardTypes[t])
- addImageToEditPreview(width, height, widget.cardTypes[t][key], $table, button);
- }
- });
- $("<input type=\"button\" value=\"Set\">").appendTo("#editPreview").on("click", function() {
- Object.assign(imageCache, compressedImageCache);
- if($(button).is(".rotate") && $("#rotateResizeDeck").prop("checked") || $(button).is(".crop") && $("#cropResizeDeck").prop("checked")) {
- $("#editPreview table").each(function() {
- var w = widgets[$(this).data("widgetIndex")];
- var widthFactor = $("img", this).get(1).width /$("img", this).get(0).width;
- var heightFactor = $("img", this).get(1).height/$("img", this).get(0).height;
- if(w.type == "board") {
- w.width = addDimensions(w).width *widthFactor;
- w.height = addDimensions(w).height*heightFactor;
- }
- if(w.type == "cardDeck")
- resizeCardDeck(w, addDimensions(w).cardWidth*widthFactor, addDimensions(w).cardHeight*heightFactor);
- });
- }
- drawWidgets(widgets);
- });
- $("#editPreview").get(0).scrollIntoView();
- });
- });
- $("#editCardDecks input.preview").on("click", function() {
- if($(".selected.type-cardDeck").length == 0) {
- $("#editCardDecksPreview").empty().text("Please select at least one card deck!");
- return;
- }
- $("#editCardDecksPreview").empty().text("Loading...");
- loadAllImages(zipFile, function() {
- $("#editCardDecksPreview").empty();
- $(".selected").each(function() {
- var widget = addDimensions(widgets[$(this).data("index")]);
- if(widget.type == "cardDeck") {
- $("#editCardDecksPreview").append("<h2>" + widget.type + ": " + widget.id + "</h2>");
- $("<input type='button' value='Toggle all'>").appendTo("#editCardDecksPreview").on("click", function() {
- $(this).next().children().click();
- });
- var $div = $("<div data-widgetid=\"" + widget.id + "\"></div>").appendTo("#editCardDecksPreview");
- for(var t in widget.cardTypes) {
- for(var key in widget.cardTypes[t]) {
- var value = widget.cardTypes[t][key];
- if(value.match(/^package/)) {
- loadImage(value.replace("package://", ""), [ $div, t, key, widget ], function(image, type, vars) {
- var count = widgets.filter((w) => w.type == "card" && w.deck == vars[3].id && w.cardType == vars[1]).length;
- vars[0].append("<div class='cardPreview'><img src=\"data:" + type + ";base64," + image + "\" data-type=\"" + vars[1] + "\" data-key=\"" + vars[2] + "\"><div>" + count + "x</div></div>");
- });
- break;
- } else if(value.match(/^(http|\/img)/)) {
- var url = value.match(/^\/img/) ? "https://playingcards.io" + value : value;
- var count = widgets.filter((w) => w.type == "card" && w.deck == widget.id && w.cardType == t).length;
- $div.append("<div class='cardPreview'><img src=\"" + url + "\" data-type=\"" + t + "\" data-key=\"" + key + "\"><div>" + count + "x</div></div>");
- break;
- }
- }
- }
- }
- });
- $("<input type='button' class='mergeTypes first' value='Merge to first' >").appendTo("#editCardDecksPreview");
- $("<input type='button' class='mergeTypes last' value='Merge to last' >").appendTo("#editCardDecksPreview");
- $("<input type='button' class='splitDeck move' value='Move to new deck'>").appendTo("#editCardDecksPreview");
- $("<input type='button' class='splitDeck copy' value='Copy to new deck'>").appendTo("#editCardDecksPreview");
- $(`<fieldset>
- <legend>Card rotation</legend>
- <input type='checkbox' id='addRotationRandomizedPile'><label for='addRotationRandomizedPile'> Add a draw pile with randomized rotations</label><br>
- <input type='checkbox' id='addRotationTray'><label for='addRotationTray'> Add card piles with all possible cards and a rotate button</label>
- - cards per type
- min <select id="rotTypeMin"><option selected>1</option><option>2</option><option>5</option><option>10</option></select>
- , target <select id="rotTypeTarget"><option>0.5n</option><option selected>1n</option><option>2n</option><option>5n</option><option>10n</option></select>
- , max <select id="rotTypeMax"><option>1</option><option>2</option><option>5</option><option selected>10</option><option>20</option><option>50</option></select>
- (n is the number on the card above - don't overdo it!)<br>
- <input type='button' class='addRotation' value='Add rotation'>
- </fieldset>
- `).appendTo("#editCardDecksPreview");
- $("#editCardDecksPreview").get(0).scrollIntoView();
- });
- });
- function addCardsToRotatedPile(i, cardCount, clone, pileID, type, deg) {
- for(var cc=0; cc<cardCount; ++cc) {
- addWidget({
- id: pileID + "-" + deg + "-" + cc,
- type: "card",
- deck: clone.id,
- parent: pileID,
- cardType: type,
- x: 4 + i*(clone.cardWidth+10),
- y: 1104
- });
- }
- }
- function addRotateTrayPile(clone, i, type, deg, cardCount, typesCount) {
- var trayPileID = clone.id + "-" + i + "-" + deg;
- var dy = 1000/typesCount;
- addWidget({
- id: trayPileID,
- type: "cardPile",
- x: deg == 0 ? 1600-clone.cardWidth-4 : 1700,
- y: -4 + i*dy,
- width: clone.cardWidth + 8,
- height: clone.cardHeight + 8
- });
- for(var cc=0; cc<cardCount; ++cc) {
- addWidget({
- id: trayPileID + "-" + cc,
- type: "card",
- deck: clone.id,
- parent: trayPileID,
- cardType: type,
- faceup: true,
- x: deg == 0 ? 1600-clone.cardWidth : 1704,
- y: 0 + i*dy
- });
- }
- }
- async function addRotations(button) {
- pushUndoStack();
- var totalTodo = $("#editCardDecksPreview .cardSelected").length * 3;
- var done = 0;
- var decks = $("#editCardDecksPreview > div");
- for(var d=0; d<decks.length; ++d) {
- var deck = decks.eq(d);
- var widget = widgets.filter((w) => w.id == deck.data("widgetid"))[0];
- var widget = widgets.filter((w) => w.id == deck.data("widgetid"))[0];
- var clone = cloneCardDeck(widget, uniqueID("rotDeck"));
- clone.cardTypes = {};
- widgets = widgets.filter((w) => !(w.type == "card" && w.deck == clone.id));
- var images = $(".cardSelected > img", deck);
- var key = images.data("key");
- if($("#addRotationRandomizedPile").prop("checked")) {
- var rotatedPileIDs = [];
- var rotatedDrawPileID = clone.id + "-permdraw";
- var automationButton = {
- id: clone.id + "-permbutton",
- type: "automationButton",
- label: "Start",
- x: 120,
- y: 120,
- clickRoutine: []
- };
- addWidget({
- id: rotatedDrawPileID,
- type: "cardPile",
- width: clone.cardWidth + 8,
- height: clone.cardHeight + 8
- });
- }
- if($("#addRotationTray").prop("checked")) {
- var rotateButton = {
- id: clone.id + "-traybutton",
- type: "automationButton",
- label: "↻",
- x: 1600-50-clone.cardWidth,
- y: 50,
- clickRoutine: [],
- height: 40,
- width: 40
- };
- addWidget(rotateButton);
- }
- for(var i=0; i<images.length; ++i) {
- var img = images.eq(i);
- var type = img.data("type");
- var cardCount = +img.next().text().replace(/x/, "");
- clone.cardTypes[type] = { label: type };
- clone.cardTypes[type][key] = "package://userassets/" + type;
- if($("#addRotationRandomizedPile").prop("checked")) {
- var pileID = clone.id + "-permdraw-" + i;
- rotatedPileIDs.push(pileID);
- addWidget({
- id: pileID,
- type: "cardPile",
- x: 0 + i*(clone.cardWidth+10),
- y: 1100,
- width: clone.cardWidth + 8,
- height: clone.cardHeight + 8
- });
- addCardsToRotatedPile(i, cardCount, clone, pileID, type, 0);
- automationButton.clickRoutine.push({
- "func": "MOVE_CARDS_BETWEEN_HOLDERS",
- "args": {
- "from": {
- "type": "literal",
- "value": [ pileID ]
- },
- "to": {
- "type": "literal",
- "value": [ rotatedDrawPileID ]
- },
- "quantity": {
- "type": "literal",
- "value": cardCount
- }
- }
- });
- }
- if($("#addRotationTray").prop("checked")) {
- var trayCardCount = Math.max($("#rotTypeMin").val(), Math.min($("#rotTypeMax").val(), $("#rotTypeTarget").val().replace(/n/, "") * cardCount));
- addRotateTrayPile(clone, i, type, "empty", 0, images.length);
- addRotateTrayPile(clone, i, type, 0, trayCardCount, images.length);
- [ [ "0", "empty" ], [ "90", "0" ], [ "180", "90" ], [ "270", "180" ], [ "empty", "270" ] ].forEach(function(v) {
- rotateButton.clickRoutine.push({
- "func": "MOVE_CARDS_BETWEEN_HOLDERS",
- "args": {
- "from": {
- "type": "literal",
- "value": [ clone.id + "-" + i + "-" + v[0] ]
- },
- "to": {
- "type": "literal",
- "value": [ clone.id + "-" + i + "-" + v[1] ]
- },
- "quantity": {
- "type": "literal",
- "value": 1000
- }
- }
- });
- });
- }
- for(var deg=90; deg<=270; deg+=90) {
- var dataURL = await rotate(img.get(0).src, deg);
- var rotType = type + "-rot" + deg;
- clone.cardTypes[rotType] = { label: rotType };
- clone.cardTypes[rotType][key] = "package://userassets/" + rotType;
- imageCache["userassets/" + rotType] = dataURL.replace(/data.*?,/, '');
- if($("#addRotationRandomizedPile").prop("checked")) {
- addCardsToRotatedPile(i, cardCount, clone, pileID, rotType, deg);
- }
- if($("#addRotationTray").prop("checked")) {
- addRotateTrayPile(clone, i, rotType, deg, trayCardCount, images.length);
- }
- ++done;
- $("#statusTool").text("Rotated " + done + "/" + totalTodo + " images.");
- }
- }
- if($("#addRotationRandomizedPile").prop("checked")) {
- automationButton.clickRoutine.unshift({
- "func": "SHUFFLE_CARDS",
- "args": {
- "holders": {
- "type": "literal",
- "value": rotatedPileIDs
- }
- }
- });
- automationButton.clickRoutine.push({
- "func": "SHUFFLE_CARDS",
- "args": {
- "holders": {
- "type": "literal",
- "value": [ rotatedDrawPileID ]
- }
- }
- });
- addWidget(automationButton);
- }
- }
- $("#statusTool").text("Rotated deck added.");
- drawWidgets(widgets);
- }
- $("#editCardDecksPreview").on("click", ".addRotation", function() {
- addRotations(this);
- });
- function uniqueID(prefix="widget", cutSuffixes=false) {
- var currentIDs = widgets.map((w) => cutSuffixes ? w.id.replace(/-.*/, "") : w.id);
- var id;
- for(var i=1; i<9999; ++i) {
- id = prefix+String(i).padStart(3, "0");
- if(currentIDs.indexOf(id) == -1)
- break;
- }
- return id;
- }
- function renameIDinClickRoutine(cr, funcName, argName, oldID, newID) {
- if(cr.func == funcName)
- cr.args[argName].value = cr.args[argName].value.map((x) => x == oldID ? newID : oldID);
- }
- function renameID(widgets, oldID, newID) {
- widgets.forEach(function(w) {
- [ "id", "deck", "parent" ].forEach(function(f) {
- if(w[f] == oldID)
- w[f] = newID;
- });
- if(w.clickRoutine !== undefined) {
- w.clickRoutine.forEach(function(cr) {
- renameIDinClickRoutine(cr, "CHANGE_COUNTER", "counters", oldID, newID);
- renameIDinClickRoutine(cr, "FLIP_CARDS", "holders", oldID, newID);
- renameIDinClickRoutine(cr, "MOVE_CARDS_BETWEEN_HOLDERS", "from", oldID, newID);
- renameIDinClickRoutine(cr, "MOVE_CARDS_BETWEEN_HOLDERS", "to", oldID, newID);
- renameIDinClickRoutine(cr, "SHUFFLE_CARDS", "holders", oldID, newID);
- });
- }
- });
- }
- $("#tools").on("click", ".cardPreview", function() {
- $(this).toggleClass("cardSelected");
- });
- $("#editCardDecksPreview").on("click", ".mergeTypes", function() {
- var button = this;
- $("#editCardDecksPreview > div").each(function() {
- var widget = widgets.filter((w) => w.id == $(this).data("widgetid"))[0];
- var target = $(".cardSelected > img", this).eq($(button).is(".first") ? 0 : -1).data();
- $(".cardSelected > img", this).each(function() {
- var d = $(this).data();
- if(d.type != target.type) {
- var cards = widgets.filter((w) => w.type == "card" && w.deck == widget.id && w.cardType == d.type);
- delete widget.cardTypes[d.type];
- $.each(cards, (_, c) => c.cardType = target.type);
- }
- });
- });
- drawWidgets(widgets);
- $(".select_all").click();
- $("#editCardDecks input.preview").click();
- });
- $("#editCardDecksPreview").on("click", ".splitDeck", function() {
- pushUndoStack();
- var button = this;
- $("#editCardDecksPreview > div").each(function() {
- var widget = widgets.filter((w) => w.id == $(this).data("widgetid"))[0];
- var clone = cloneCardDeck(widget);
- $(":not(.cardSelected) > img", this).each(function() {
- var d = $(this).data();
- widgets = widgets.filter((w) => !(w.type == "card" && w.deck == clone.id && w.cardType == d.type));
- delete clone.cardTypes[d.type];
- });
- if($(button).is(".move")) {
- $(".cardSelected > img", this).each(function() {
- var d = $(this).data();
- widgets = widgets.filter((w) => !(w.type == "card" && w.deck == widget.id && w.cardType == d.type));
- delete widget.cardTypes[d.type];
- });
- }
- });
- drawWidgets(widgets);
- });
- var undoStack = [];
- function pushUndoStack() {
- undoStack.push(JSON.parse(JSON.stringify(widgets)));
- }
- function popUndoStack() {
- if(undoStack.length)
- widgets = undoStack.pop();
- drawWidgets(widgets);
- }
- function clearUndoStack() {
- undoStack = [];
- }
- function selectByRectangle(x1, y1, x2, y2) {
- $(".widget").each(function() {
- var b = $(this).get(0).getBoundingClientRect();
- $(this).toggleClass("selected", b.left >= x1 && b.left+b.width <= x2 && b.top >= y1 && b.top+b.height <= y2).data("selectionOrder", selectionOrder++);
- fillDetails(this);
- });
- $("#statusTool").text($(".selected").length + " widgets selected");
- }
- function addDimensions(widget) {
- widget = JSON.parse(JSON.stringify(widget));
- var fields = [ "width", "height" ];
- if(widget.type == "cardDeck") {
- var d = widgets.filter((v) => v.id == widget.deck)[0];
- widget.cardWidth = widget.cardWidth || defaultSizes["card"][0];
- widget.cardHeight = widget.cardHeight || defaultSizes["card"][1];
- }
- if(widget.type == "card") {
- var d = widgets.filter((v) => v.id == widget.deck)[0];
- if(widget.width == undefined)
- widget.width = d && d.cardWidth || defaultSizes["card"][0];
- if(widget.height == undefined)
- widget.height = d && d.cardHeight || defaultSizes["card"][1];
- }
- for(var i in fields) {
- if(widget[fields[i]] === undefined && widget.pieceType !== undefined)
- widget[fields[i]] = defaultSizes[widget.type][widget.pieceType][i];
- else if(widget[fields[i]] === undefined)
- widget[fields[i]] = defaultSizes[widget.type] && defaultSizes[widget.type][i] || 40;
- }
- return widget;
- }
- function imageType(content) {
- // https://stackoverflow.com/a/58158656
- var signatures = {
- R0lGOD: "image/gif",
- iVBORw0KGgo: "image/png",
- PHN: "image/svg+xml"
- };
- var type = "image/jpeg";
- for(var s in signatures)
- if(content.indexOf(s) === 0)
- type = signatures[s];
- return type;
- }
- function loadImage(image, target, callback) {
- if(imageCache[image] !== undefined) {
- callback(imageCache[image], imageType(imageCache[image]), target);
- return;
- }
- JSZip.loadAsync(zipFile).then(function(zip) {
- return zip.files[image].async("base64");
- }).then(function(content) {
- imageCache[image] = content;
- callback(content, imageType(content), target);
- }, function (e) {
- console.log("error loading image from zip file", e);
- });
- }
- function drawWidgets(ws) {
- previouslySelected = $.map($(".selected"), (s) => (widgets[$(s).data("index")] || {id:null}).id);
- var typesToDisplay = {};
- $('#typeFilter input').get().map((o) => typesToDisplay[o.id] = o.checked);
- $(".hideWithMulti").hide();
- $("#room").empty();
- for(var i in ws) {
- if(typesToDisplay[ws[i].type]) {
- var w = addDimensions(ws[i]);
- var $w = $("<div data-index='" + i + "' class='widget type-" + w.type + "' style='top: " + w.y + "px; left: " + w.x + "px; width: " + w.width + "px; height: " + w.height + "px; z-index: " + w.z + "'>"+(w.type == "automationButton" ? w.label : "")+"</div>").appendTo("#room");
- if(w.type == "cardPile" && w.hasShuffleButton === true && typesToDisplay["label"]) {
- var labelY = w.y + 1.02*(w.height || 168 - 2) - 1;
- $("<div class='widgetLabel' style='top: " + labelY + "px; left: " + (w.x+1) + "px; width: " + (w.width-2) + "px; height: 32px; z-index: " + w.z + "'>Recall & Shuffle</div>").appendTo("#room");
- }
- if((w.type == "cardPile" || w.type == "counter") && w.label && typesToDisplay["label"])
- $("<div class='widgetLabel' style='top: " + (w.y - 18) + "px; left: " + w.x + "px; width: " + w.width + "px; height: 18px; z-index: " + w.z + "'>" + w.label + "</div>").appendTo("#room");
- if(w.type == "hand" && w.enabled === false)
- $w.addClass("disabled");
- if(w.type == "gamePiece" && !w.enabled)
- $w.addClass("subtype-" + w.pieceType).addClass("color-" + w.color);
- if(w.type == "board" && w.boardImage != undefined) {
- if(w.boardImage.match(/^package/)) {
- var imageName = w.boardImage.replace("package://", "");
- loadImage(imageName, [ $w, imageName ], function(image, type, target) {
- target[0].css("background-image", "url(data:" + type + ";base64," + image + ")");
- boardImageCache[target[1]] = image;
- });
- } else if(w.boardImage.match(/^\//)) {
- $w.css("background-image", "url(http://playingcards.io" + w.boardImage + ")");
- } else {
- $w.css("background-image", "url(" + w.boardImage + ")");
- }
- }
- }
- }
- }
- $(function() { drawWidgets(widgets); });
- function json_preProcess(w) {
- if($("#compactAB").prop("checked") && w.type == "automationButton") {
- for(var i in w.clickRoutine) {
- if(w.clickRoutine[i].func == "MOVE_CARDS_BETWEEN_HOLDERS") {
- var moveFlip = w.clickRoutine[i].args.moveFlip && w.clickRoutine[i].args.moveFlip.value;
- w.clickRoutine[i] = [ "MOVE", w.clickRoutine[i].args.from.value, (w.clickRoutine[i].args.quantity || { value: 1 }).value, w.clickRoutine[i].args.to.value ];
- if(w.clickRoutine[i][1].length == 1)
- w.clickRoutine[i][1] = w.clickRoutine[i][1][0];
- if(w.clickRoutine[i][3].length == 1)
- w.clickRoutine[i][3] = w.clickRoutine[i][3][0];
- }
- if(w.clickRoutine[i].func == "SHUFFLE_CARDS") {
- w.clickRoutine[i] = [ "SHUFFLE", w.clickRoutine[i].args.holders.value ];
- if(w.clickRoutine[i][1].length == 1)
- w.clickRoutine[i][1] = w.clickRoutine[i][1][0];
- }
- if(w.clickRoutine[i].func == "FLIP_CARDS") {
- w.clickRoutine[i] = [ "FLIP", w.clickRoutine[i].args.flipMode.value, w.clickRoutine[i].args.holders.value ];
- if(w.clickRoutine[i][2].length == 1)
- w.clickRoutine[i][2] = w.clickRoutine[i][2][0];
- }
- if(w.clickRoutine[i].func == "CHANGE_COUNTER") {
- w.clickRoutine[i] = [ "COUNTER", w.clickRoutine[i].args.counters.value, w.clickRoutine[i].args.changeMode.value, w.clickRoutine[i].args.changeNumber.value ];
- if(w.clickRoutine[i][1].length == 1)
- w.clickRoutine[i][1] = w.clickRoutine[i][1][0];
- }
- if(moveFlip && moveFlip != "none")
- w.clickRoutine[i][4] = moveFlip;
- }
- }
- return w;
- }
- function json_postProcess(w) {
- if($("#compactAB").prop("checked") && w.type == "automationButton") {
- for(var i in w.clickRoutine) {
- if(Array.isArray(w.clickRoutine[i]) && w.clickRoutine[i][0] == "MOVE") {
- if(typeof w.clickRoutine[i][1] === "string")
- w.clickRoutine[i][1] = [ w.clickRoutine[i][1] ];
- if(typeof w.clickRoutine[i][3] === "string")
- w.clickRoutine[i][3] = [ w.clickRoutine[i][3] ];
- w.clickRoutine[i] = {
- "func": "MOVE_CARDS_BETWEEN_HOLDERS",
- "args": {
- "from": {
- "type": "literal",
- "value": w.clickRoutine[i][1]
- },
- "to": {
- "type": "literal",
- "value": w.clickRoutine[i][3]
- },
- "quantity": {
- "type": "literal",
- "value": w.clickRoutine[i][2]
- },
- "moveFlip": {
- "type": "literal",
- "value": w.clickRoutine[i][4] || "none"
- }
- }
- };
- }
- if(Array.isArray(w.clickRoutine[i]) && w.clickRoutine[i][0] == "SHUFFLE") {
- if(typeof w.clickRoutine[i][1] === "string")
- w.clickRoutine[i][1] = [ w.clickRoutine[i][1] ];
- w.clickRoutine[i] = {
- "func": "SHUFFLE_CARDS",
- "args": {
- "holders": {
- "type": "literal",
- "value": w.clickRoutine[i][1]
- }
- }
- };
- }
- if(Array.isArray(w.clickRoutine[i]) && w.clickRoutine[i][0] == "FLIP") {
- if(typeof w.clickRoutine[i][2] === "string")
- w.clickRoutine[i][2] = [ w.clickRoutine[i][2] ];
- w.clickRoutine[i] = {
- "func": "FLIP_CARDS",
- "args": {
- "flipMode": {
- "type": "literal",
- "value": w.clickRoutine[i][1]
- },
- "holders": {
- "type": "literal",
- "value": w.clickRoutine[i][2]
- }
- }
- };
- }
- if(Array.isArray(w.clickRoutine[i]) && w.clickRoutine[i][0] == "COUNTER") {
- if(typeof w.clickRoutine[i][1] === "string")
- w.clickRoutine[i][1] = [ w.clickRoutine[i][1] ];
- w.clickRoutine[i] = {
- "func": "CHANGE_COUNTER",
- "args": {
- "changeMode": {
- "type": "literal",
- "value": w.clickRoutine[i][2]
- },
- "changeNumber": {
- "type": "literal",
- "value": w.clickRoutine[i][3]
- },
- "counters": {
- "type": "literal",
- "value": w.clickRoutine[i][1]
- }
- }
- };
- }
- }
- }
- return w;
- }
- function json_postProcessString(str) {
- return str.replace(/\[\n +"(MOVE|SHUFFLE|FLIP|COUNTER)",\n[^\[\]]*(\[[^\[\]]*\][^\[\]]*)*\]/g, function(match) {
- return match.replace(/\n */g, " ");
- }).replace(/"options": \[\n[^\[\]]*\]/g, function(match) {
- return match.replace(/\n */g, " ");
- });
- }
- $("#jsonEdit [type=checkbox]").on("click", () => fillDetails($(".selected").get(0)));
- function fillDetails(element) {
- var w = addDimensions(widgets[$(element).data("index")]);
- $(".hideWithMulti").toggle($(".selected").length == 1);
- if($(".selected").length == 0) {
- $("#x, #y, #height, #width, #quickSetX.inactive, #quickSetY.inactive, #quickSetW.inactive, #quickSetH.inactive, #quickSetID").val("");
- return;
- } else if($(".selected").length == 1) {
- $("#x").val(w.x);
- $("#y").val(w.y);
- $("#width" ).val(w.width);
- $("#height").val(w.height);
- $("#quickSetX.inactive").val($(".highlight_alt").is(".activated") ? 1600 - w.width - w.x : w.x);
- $("#quickSetY.inactive").val($(".highlight_alt").is(".activated") ? 1000 - w.height - w.y : w.y);
- $("#quickSetW.inactive").val(w.width);
- $("#quickSetH.inactive").val(w.height);
- $("#quickSetID").val(w.id);
- } else {
- return;
- }
- w = widgets[$(".selected:eq(0)").data("index")];
- $("#json").val(json_postProcessString(JSON.stringify(json_preProcess(JSON.parse(JSON.stringify(w))), null, " ")));
- if(w.pieceType !== undefined)
- $("#defaultSize").text(" for " + w.type + ": " + defaultSizes[w.type][w.pieceType][0] + "*" + defaultSizes[w.type][w.pieceType][1]);
- else if(defaultSizes[w.type] !== undefined)
- $("#defaultSize").text(" for " + w.type + ": " + defaultSizes[w.type][0] + "*" + defaultSizes[w.type][1]);
- else
- $("#defaultSize").text(" for " + w.type + " is unavailable");
- }
- $(".toolbar").on("focus", "input.inactive", function() {
- $(this).removeClass("inactive");
- });
- $(".toolbar").on("blur", "input:not(inactive)", function() {
- if($(this).val() == "") {
- $(this).addClass("inactive");
- fillDetails($(".selected").get(0));
- }
- });
- function resizeCardDeck(w, width, height) {
- var dim = addDimensions(w);
- var widthFactor = width/dim.cardWidth;
- var heightFactor = height/dim.cardHeight;
- w.cardWidth *= widthFactor;
- w.cardHeight *= heightFactor;
- var templateScale = function(o) {
- o.x *= widthFactor;
- o.y *= heightFactor;
- o.w *= widthFactor;
- o.h *= heightFactor;
- };
- w.backTemplate.objects.forEach(templateScale);
- w.faceTemplate.objects.forEach(templateScale);
- var pileIDs = widgets.map((wi) => wi.type == "card" && wi.deck == w.id && wi.parent || null);
- widgets.forEach(function (wi) {
- if(wi.type == "cardPile" && pileIDs.indexOf(wi.id) > -1) {
- wi.width = (wi.width - 8)*widthFactor + 8;
- wi.height = (wi.height - 8)*heightFactor + 8;
- }
- });
- }
- function cloneCardDeck(w, id) {
- var clone = JSON.parse(JSON.stringify(w));
- var newID = id || uniqueID(clone.id + "-copy");
- clone.id = newID;
- clone.parent = null;
- clone.x = 200;
- clone.y = 200;
- addWidget(clone);
- widgets.forEach(function (wi) {
- if(wi.type == "card" && wi.deck == w.id) {
- var cardClone = JSON.parse(JSON.stringify(wi));
- delete cardClone.id;
- cardClone.x = 200;
- cardClone.y = 200;
- cardClone.deck = newID;
- cardClone.parent = null;
- addWidget(cardClone);
- }
- });
- return clone;
- }
- function downloadURI(uri, name) {
- // https://stackoverflow.com/a/15832662
- var link = document.createElement("a");
- link.download = name;
- link.href = uri;
- document.body.appendChild(link);
- link.click();
- document.body.removeChild(link);
- delete link;
- }
- function compress(url, width, height, type, compression, callback) {
- Jimp.read(url).then(image => {
- return image.background(0xFFFFFFFF).contain(width, height, Jimp.RESIZE_BICUBIC).quality(compression*100).getBase64Async(type == "image/jpeg" ? Jimp.MIME_JPEG : Jimp.MIME_PNG);
- }).then(base64 => callback(base64)).catch(console.log);
- }
- function rotate(url, degrees) {
- return Jimp.read(url).then(image => {
- return image.rotate(-degrees, Jimp.RESIZE_BICUBIC).getBase64Async(Jimp.MIME_PNG);
- }).catch(console.log);
- }
- function crop(url, x, y, width, height, callback) {
- Jimp.read(url).then(image => {
- return image.crop(x, y, width, height).getBase64Async(Jimp.MIME_PNG);
- }).then(base64 => callback(base64)).catch(console.log);
- }
- function toDataURL(url, callback) {
- // https://stackoverflow.com/a/20285053
- var xhr = new XMLHttpRequest();
- xhr.onload = function() {
- var reader = new FileReader();
- reader.onloadend = function() {
- callback(reader.result);
- }
- reader.readAsDataURL(xhr.response);
- };
- if(window.location.protocol == "https:")
- url = url.replace(/^http:/, "https:");
- xhr.open('GET', url);
- xhr.responseType = 'blob';
- xhr.send();
- }
- function hashFnv32a(str, asString, seed) {
- // https://stackoverflow.com/a/22429679
- var i, l,
- hval = (seed === undefined) ? 0x811c9dc5 : seed;
- for (i = 0, l = str.length; i < l; i++) {
- hval ^= str.charCodeAt(i);
- hval += (hval << 1) + (hval << 4) + (hval << 7) + (hval << 8) + (hval << 24);
- }
- if( asString ){
- // Convert to 8 digit hex string
- return ("0000000" + (hval >>> 0).toString(16)).substr(-8);
- }
- return hval >>> 0;
- }
- function dataURLtoFile(dataurl, filename) {
- // https://stackoverflow.com/a/38935990
- var arr = dataurl.split(','),
- mime = arr[0].match(/:(.*?);/)[1],
- bstr = atob(arr[1]),
- n = bstr.length,
- u8arr = new Uint8Array(n);
- while(n--)
- u8arr[n] = bstr.charCodeAt(n);
- return new File([u8arr], filename, {type:mime});
- }
- function copyTextToClipboard(text) {
- // https://stackoverflow.com/a/30810322
- var textArea = document.createElement("textarea");
- textArea.value = text;
- // Avoid scrolling to bottom
- textArea.style.top = "0";
- textArea.style.left = "0";
- textArea.style.position = "fixed";
- document.body.appendChild(textArea);
- textArea.focus();
- textArea.select();
- try {
- var successful = document.execCommand('copy');
- var msg = successful ? 'successful' : 'unsuccessful';
- console.log('Fallback: Copying text command was ' + msg);
- } catch (err) {
- console.error('Fallback: Oops, unable to copy', err);
- }
- document.body.removeChild(textArea);
- }
- window.onmouseover = function(e) {
- var i = $(e.target).data("index");
- if(i !== undefined) {
- var w = addDimensions(widgets[i]);
- $("#statusWidget").text(w.type + " " + Math.floor(w.width*10)/10 + "x" + Math.floor(w.height*10)/10 + "+" + Math.floor(w.x*10)/10 + "+" + Math.floor(w.y*10)/10);
- } else {
- $("#statusWidget").text("-");
- }
- };
- $('div').on('dragstart', function(event) { event.preventDefault(); });
- // https://stackoverflow.com/a/23284608
- var div = document.getElementById('selectionDiv'), x1 = 0, y1 = 0, x2 = 0, y2 = 0;
- function reCalc() { //This will restyle the div
- var x3 = Math.min(x1,x2); //Smaller X
- var x4 = Math.max(x1,x2); //Larger X
- var y3 = Math.min(y1,y2); //Smaller Y
- var y4 = Math.max(y1,y2); //Larger Y
- div.style.left = x3 + 'px';
- div.style.top = y3 + 'px';
- div.style.width = x4 - x3 + 'px';
- div.style.height = y4 - y3 + 'px';
- }
- var $movingWidgets = $([]);
- var movingWidgetAspect = 1;
- $(document).on("mousedown", function(e) {
- if($("input:hover, textarea:hover, button:hover, select:hover").length > 0)
- return true;
- if(!$(e.target).is("#room") && (moveWidgets || resizeWidgets)) {
- pushUndoStack();
- $movingWidgets = $(".selected");
- if(!$movingWidgets.length)
- $movingWidgets = $(e.target);
- $movingWidgets.each(function() {
- var rect = $(this).get(0).getBoundingClientRect();
- $(this).data("moveStartClientRect", rect).data("resizeAspect", rect.width/rect.height);
- });
- } else {
- $(div).show(); //Unhide the div
- }
- x1 = x2 = e.clientX; //Set the initial X
- y1 = y2 = e.clientY; //Set the initial Y
- reCalc();
- });
- $(document).on("mousemove", function(e) {
- x2 = e.clientX; //Update the current position X
- y2 = e.clientY; //Update the current position Y
- var b = $("#room").get(0).getBoundingClientRect();
- var scale = 1600 / (b.width - 2);
- var x = Math.floor((x2 - b.x) * scale);
- var y = Math.floor((y2 - b.y) * scale);
- $("#statusCoordinates").text(x + ", " + y);
- reCalc();
- if(moveWidgets && $movingWidgets.length) {
- $movingWidgets.each(function() {
- var newX = Math.floor(x + ($(this).data("moveStartClientRect").left - x1) * scale);
- var newY = Math.floor(y + ($(this).data("moveStartClientRect").top - y1) * scale);
- $(this).css({ left: newX+"px", top: newY+"px" });
- widgets[$(this).data("index")].x = newX;
- widgets[$(this).data("index")].y = newY;
- });
- $("#statusTool").text("Δx " + ((x2-x1) * scale) + ", Δy " + ((y2-y1) * scale));
- }
- if(resizeWidgets && $movingWidgets.length) {
- $movingWidgets.each(function() {
- var w = Math.floor(x-$(this).position().left * scale);
- var h = Math.floor(y-$(this).position().top * scale);
- if(ctrlIsPressed)
- h = Math.floor(w / $(this).data("resizeAspect"));
- $movingWidgets.css({ width: w+"px", height: h+"px" });
- widgets[$(this).data("index")].width = w;
- widgets[$(this).data("index")].height = h;
- });
- $("#statusTool").text(w + "x" + h);
- }
- }).on("mouseup", function(e) {
- if(moveWidgets || resizeWidgets) {
- $movingWidgets = $([]);
- drawWidgets(widgets);
- $(".select_all").click();
- }
- if(!$(div).is(":hidden")) {
- $(div).hide(); //Hide the div
- if(Math.abs(x1-x2) > 2 && Math.abs(y1-y2) > 2)
- selectByRectangle(Math.min(x1, x2), Math.min(y1, y2), Math.max(x1, x2), Math.max(y1, y2));
- }
- });
- tabletop = {
- images: {},
- crops: {},
- addCardDecks: function(o, fieldName, target) {
- var container = tabletop.addFieldHeaderAndContainer(fieldName, target);
- (o.DeckIDs || [ o.CardID ]).forEach(function(id) {
- var deckID = Math.floor(id/100);
- tabletop.addCardToDeck(o.CustomDeck[deckID], id%100, container);
- });
- var importContainer = $("<div class='import'></div>").appendTo(container);
- $("<div class='loading'>Loading...</div>").appendTo(container);
- $("<label>Target height: </label>" ).appendTo(importContainer);
- var targetHeight = $("<input class='targetHeight' class='short' value='160'>" ).appendTo(importContainer);
- $("<button>Import as new deck</button>").appendTo(importContainer).on("click", function() {
- var cards = $(".cardSelected.cardPreview", container);
- if(!cards.length)
- cards = $(".cardPreview", container);
- tabletop.importAsNewDeck(cards, +targetHeight.val());
- });
- $("<input type='button' value='Toggle all'>").appendTo(importContainer).on("click", function() {
- $(".cardPreview", container).click();
- });
- },
- addCardImage: function(frontURL, backURL, target) {
- return $("<div class='cardPreview'><img class='front' src='" + frontURL + "'><img class='back' src='" + backURL + "'></div>").appendTo(target);
- },
- addCardToDeck: function(deck, id, target) {
- var w = deck.NumWidth || 10;
- var h = deck.NumHeight || 7;
- if(deck.BackURL.match(/\{Unique\}$/)) {
- deck.UniqueBack = true;
- deck.BackURL = deck.BackURL.replace(/\{Unique\}$/, "");
- }
- tabletop.cropCard(deck.FaceURL, h, w, id, 0, function(url) {
- if(deck.UniqueBack) {
- tabletop.cropCard(deck.BackURL, h, w, id, 0, function(backURL) {
- $(".loading", target).remove();
- tabletop.addCardImage(url, backURL, target);
- });
- } else {
- $(".loading", target).remove();
- tabletop.addCardImage(url, deck.BackURL, target);
- }
- }, function() {
- $("<div class='error'><p>Loading and cropping failed! Did you disable CORS (see step 1 above)?</p><p>If you didn't, do it and reload with <CTRL>+<F5> to clear the cache.</p><p>If you did, the image is probably offline.</p></div>").appendTo(target);
- });
- },
- addFieldHeaderAndContainer: function(fieldName, target) {
- $("<h5>" + fieldName + "</h5>").appendTo(target);
- return $("<div></div>").appendTo(target);
- },
- addImage: function(url, fieldName, target, useParent) {
- var container = tabletop.addFieldHeaderAndContainer(fieldName, target);
- var importContainer = $("<div class='import'></div>").appendTo(container);
- $("<label>Target height: </label>" ).appendTo(importContainer);
- var targetHeight = $("<input class='targetHeight' class='short' value='160'>" ).appendTo(importContainer);
- $("<button>Import as board</button>").appendTo(importContainer).on("click", ()=>tabletop.importAsBoard(container, url));
- $("<button>Import as card</button>" ).appendTo(importContainer).on("click", ()=>tabletop.importAsNewDeck($(".cardPreview", useParent ? container.parent() : container), +targetHeight.val()) );
- $("<div class='cardPreview'><img src='" + url + "'></div>").appendTo(container);
- },
- cropCard: function(url, rows, perRow, i, rotation, callback, errorCallback) {
- if(!(url in tabletop.images)) {
- tabletop.crops[url] = {};
- tabletop.crops[url].meta = { rows: rows, perRow: perRow, rotation: rotation };
- tabletop.images[url] = new Image();
- tabletop.images[url].crossOrigin = "anonymous";
- tabletop.images[url].onload = tabletop.imageLoaded;
- tabletop.images[url].onerror = errorCallback;
- }
- if(!(i in tabletop.crops[url]))
- tabletop.crops[url][i] = [];
- if(typeof tabletop.crops[url][i] == "string")
- callback(tabletop.crops[url][i]);
- else
- tabletop.crops[url][i].push(callback);
- //img.src = "https://cors-anywhere.herokuapp.com/" + url;
- tabletop.images[url].src = url;
- },
- imageLoaded: function() {
- var m = tabletop.crops[this.src].meta;
- const canvas = document.createElement('canvas');
- canvas.width = this.width/m.perRow;
- canvas.height = this.height/m.rows;
- const ctx = canvas.getContext('2d');
- //console.log(i, canvas.width, canvas.width*(i%perRow), canvas.height, canvas.height*Math.floor(i/perRow));
- for(var i in tabletop.crops[this.src]) {
- if(i != "meta") {
- ctx.drawImage(this, canvas.width*(i%m.perRow), canvas.height*Math.floor(i/m.perRow), this.width/m.perRow, this.height/m.rows, 0, 0, this.width/m.perRow, this.height/m.rows);
- var dataURL = ctx.canvas.toDataURL("image/jpeg", 0.9);
- tabletop.crops[this.src][i].forEach(function(c) { c(dataURL); });
- tabletop.crops[this.src][i] = dataURL;
- }
- }
- },
- importAsBoard: function(container, url) {
- pushUndoStack();
- var height = +$(".targetHeight", container).val();
- addWidget({
- id: uniqueID("ttboard"),
- type: "board",
- height: height,
- width: height*$("img", container).width()/$("img", container).height(),
- boardImage: url,
- dragging: null
- });
- drawWidgets(widgets);
- },
- importAsNewDeck: function(cards, targetHeight) {
- pushUndoStack();
- var deck = uniqueID("ttdeck");
- var pile = uniqueID("ttpile");
- var x = 24;
- var y = 24;
- var width = 160;
- var height = 160;
- var types = {};
- var typeI = 0;
- cards.each(function() {
- var front = $(this).children("img:eq(0)");
- var back = $(this).children("img.back");
- if(back.length == 0)
- back = front;
- width = front.width();
- height = front.height();
- var frontURL = front.get(0).src;
- var backURL = back.get(0).src;
- var type = deck + "-" + (++typeI);
- if(frontURL.match(/^data/)) {
- imageCache["userassets/" + type] = frontURL.replace(/data.*?,/, '');
- types[type] = {
- label: type,
- front: "package://userassets/" + type
- };
- } else {
- types[type] = {
- label: type,
- front: frontURL
- };
- }
- if(backURL.match(/^data/)) {
- imageCache["userassets/" + type + "-back"] = backURL.replace(/data.*?,/, '');
- types[type].back = "package://userassets/" + type + "-back";
- } else {
- types[type].back = backURL;
- }
- });
- addWidget({
- id: pile,
- x: x,
- y: y,
- type: "cardPile",
- dragging: null,
- hasShuffleButton: true,
- width: targetHeight*width/height + 8,
- height: targetHeight + 8
- });
- addWidget({
- id: deck,
- type: "cardDeck",
- faceTemplate: {
- includeBorder: false,
- includeRadius: true,
- objects: [
- {
- type: "image",
- x: 0,
- y: 0,
- w: targetHeight*width/height,
- h: targetHeight,
- color: "white",
- valueType: "dynamic",
- value: "front"
- }
- ]
- },
- backTemplate: {
- includeBorder: false,
- includeRadius: true,
- objects: [
- {
- type: "image",
- x: 0,
- y: 0,
- w: targetHeight*width/height,
- h: targetHeight,
- color: "white",
- valueType: "dynamic",
- value: "back"
- }
- ]
- },
- cardTypes: types,
- dragging: null,
- x: x+13,
- y: y+44,
- parent: pile,
- cardWidth: targetHeight*width/height,
- cardHeight: targetHeight,
- cardOverlapH: 0,
- enlarge: true,
- });
- for(var t in types) {
- addWidget({
- id: uniqueID("ttcard"),
- type: "card",
- cardType: t,
- deck: deck,
- parent: pile,
- x: x+4,
- y: y+4,
- faceup: false,
- dragging: null,
- owner: null
- });
- }
- drawWidgets(widgets);
- },
- listObjects: function(objects, target) {
- objects.forEach(function(o) {
- if([ "FogOfWarTrigger" ].indexOf(o.Name) > -1)
- return;
- var title = o.Nickname ? o.Name + " (" + o.Nickname + ")" : o.Name;
- var container = $("<div></div>").appendTo(target);
- $("<h4>" + title + "</h4>").appendTo(container);
- tabletop.showJSONbutton(o, container);
- $("<button>Remove</button>").appendTo(container).on("click", function() { $(this).parent().remove(); });
- if("CustomImage" in o && o.CustomImage && "ImageURL" in o.CustomImage && o.CustomImage.ImageURL)
- tabletop.addImage(o.CustomImage.ImageURL, "CustomImage.ImageURL", container, "USE_PARENT");
- if("CustomImage" in o && o.CustomImage && "ImageSecondaryURL" in o.CustomImage && o.CustomImage.ImageSecondaryURL) {
- var backContainer = $("<div class='deckBack'>").appendTo(container);
- tabletop.addImage(o.CustomImage.ImageSecondaryURL, "CustomImage.ImageSecondaryURL", backContainer);
- }
- if("CustomMesh" in o && o.CustomMesh && "DiffuseURL" in o.CustomMesh && o.CustomMesh.DiffuseURL)
- tabletop.addImage(o.CustomMesh.DiffuseURL, "CustomMesh.DiffuseURL", container);
- if("CustomDeck" in o && o.CustomDeck)
- tabletop.addCardDecks(o, "CustomDeck", container);
- if(o.Name == "Custom_PDF")
- $("<p>Not importable: <a href='" + o.CustomPDF.PDFUrl + "'>PDF</a></p>").appendTo(container);
- if(o.ContainedObjects && o.Name != "DeckCustom" && o.Name != "Deck")
- tabletop.listObjects(o["ContainedObjects"], $("<div></div>").appendTo(container));
- if($("img,p", container).length == 0)
- container.addClass("empty");
- });
- },
- readJSON: function(json) {
- tabletop.images = {};
- tabletop.crops = {};
- $("#tabletopPreview").empty();
- var importContainer = $("<div class='import'></div>").appendTo("#tabletopPreview");
- $("<label>Target height: </label>" ).appendTo(importContainer);
- var targetHeight = $("<input class='targetHeight' class='short' value='160'>" ).appendTo(importContainer);
- $("<button>Import all selected cards as a single new deck</button>").appendTo(importContainer).on("click", function() {
- var cards = $("#tabletopPreview .cardSelected.cardPreview");
- if(cards.length)
- tabletop.importAsNewDeck(cards, +targetHeight.val());
- });
- $("<input type='button' value='Toggle all'>").appendTo(importContainer).on("click", function() {
- $("#tabletopPreview .cardPreview").click();
- });
- $("#tabletopPreview").append("<h3>" + json.SaveName + "</h3>");
- var target = $("<div id='tabletopMain'></div>").appendTo("#tabletopPreview");
- tabletop.showJSONbutton(json, target);
- if(json.Table == "Table_Custom")
- tabletop.addImage(json.TableURL, "Table_Custom", target);
- tabletop.listObjects(json["ObjectStates"], target);
- $("#tabletopPreview").get(0).scrollIntoView();
- },
- showImport: function(file) {
- if(file.type == "application/json" || file.type == "") {
- const reader = new FileReader();
- reader.addEventListener('load', (event) => tabletop.readJSON(JSON.parse(event.target.result.replace(/\+Infinity/g, 0))));
- reader.readAsText(file);
- } else if(file.type == "application/zip") {
- JSZip.loadAsync(file).then(function(zip) {
- for(var file in zip.files)
- if(file.match(/\.json$/))
- return zip.files[file].async("blob");
- }).then(function(content) {
- tabletop.showImport(new File([content], "tabletop.json"));
- }, function (e) {
- console.log("error loading zip file", e);
- });
- } else {
- console.log(file);
- }
- },
- showJSONbutton: function(json, target) {
- $("<button>Show JSON</button>").appendTo(target).on("click", function() {
- $(this).parent().removeClass("empty");
- $(this).replaceWith("<textarea>" + JSON.stringify(json, null, " ") + "</textarea>");
- });
- }
- };
- function addWidget(w, offsetX, offsetY) {
- w.id = w.id || uniqueID(w.type);
- if(typeof w.x === "undefined")
- w.x = 200 + (offsetX || 0);
- if(typeof w.y === "undefined")
- w.y = 200 + (offsetY || 0);
- if(typeof w.z === "undefined")
- w.z = Math.max(...widgets.map((wi)=>wi.z || 0).concat(0))+1;
- widgets.push(w);
- return w.id;
- }
- function addCardDeck(o) {
- o = Object.assign({
- pathPrefix: "/img/cards",
- backImage: "/img/cardback-red.svg",
- jokerColors: [ "Black", "Red", "Blue" ],
- addJokers: false,
- suits: [ "Diamonds", "Hearts", "Spades", "Clubs" ],
- numbers: [ "A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K" ],
- cardsToAdd: [ "A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K" ],
- addPile: true
- }, o || {});
- var pileID = o.addPile ? addWidget({"type":"cardPile","hasShuffleButton":true}) : null;
- var types = {};
- var cards = [];
- o.jokerColors.forEach(function(c) {
- types["joker-" + c.toLowerCase()] = {"label":c+" Joker","image":o.pathPrefix+"/joker-"+c.toLowerCase()+".svg"};
- if(o.addJokers)
- cards.push({"parent":pileID,"type":"card","cardType":"joker-" + c.toLowerCase(),"faceup":false});
- });
- o.suits.forEach(function(s) {
- o.numbers.forEach(function(n) {
- types[s.toLowerCase() + "-" + n.toLowerCase()] = {"label":n+" of "+s,"image":o.pathPrefix+"/"+s.toLowerCase()+"-"+n.toLowerCase()+".svg"};
- if(o.cardsToAdd.indexOf(n) > -1)
- cards.push({"parent":pileID,"type":"card","cardType":s.toLowerCase() + "-" + n.toLowerCase(),"faceup":false});
- });
- });
- var deckID = addWidget({
- "type":"cardDeck",
- "parent":pileID,
- "cardTypes":types,
- "faceTemplate":{
- "includeBorder":true,
- "includeRadius":true,
- "objects":[{"type":"image","x":0,"y":0,"w":103,"h":160,"color":"white","valueType":"dynamic","value":"image"}]
- },
- "backTemplate":{
- "includeBorder":false,
- "includeRadius":true,
- "objects":[{"type":"image","x":0,"y":0,"w":103,"h":160,"color":"#a23b2a","valueType":"static","value":o.backImage}]
- }
- }, 13, 44);
- cards.forEach(function(c) {
- c.deck = deckID;
- addWidget(c, 4, 4);
- });
- drawWidgets(widgets);
- }
- $("#WCardHolder").on("click", function() {
- addWidget({
- type: "cardPile"
- });
- drawWidgets(widgets);
- });
- $(".WPiece").on("click", function() {
- addWidget({
- type: "gamePiece",
- pieceType: $(this).data("type"),
- color: "red"
- });
- drawWidgets(widgets);
- });
- $("#WAutomationButton").on("click", function() {
- addWidget({
- type: "automationButton",
- label: "DEAL",
- clickRoutine: [
- {
- "func": "MOVE_CARDS_BETWEEN_HOLDERS",
- "args": {
- "from": {
- "type": "literal",
- "value": []
- },
- "to": {
- "type": "literal",
- "value": []
- },
- "quantity": {
- "type": "literal",
- "value": 1
- }
- }
- }
- ]
- });
- drawWidgets(widgets);
- });
- $("#WCounter").on("click", function() {
- addWidget({
- type: "counter",
- counterValue: 0
- });
- drawWidgets(widgets);
- });
- $("#WSpinner").on("click", function() {
- addWidget({
- type: "spinner",
- options: [ 1, 2, 3, 4, 5, 6 ]
- });
- drawWidgets(widgets);
- });
- $("#WBoardCustom").on("click", function() {
- addWidget({
- x: 300,
- y: 0,
- type: "board",
- width: 1000,
- height: 1000
- });
- drawWidgets(widgets);
- });
- $("#WBoardBackgammon").on("click", function() {
- addWidget({
- x: 205,
- y: 0,
- type: "board",
- boardImage: "/img/boards/backgammon.svg",
- width: 1190,
- height: 1000
- });
- drawWidgets(widgets);
- });
- $("#WBoardChess").on("click", function() {
- addWidget({
- x: 300,
- y: 0,
- type: "board",
- boardImage: "/img/boards/chess.svg",
- height: 1000,
- width: 1000
- });
- drawWidgets(widgets);
- });
- $("#WBoardCribbage").on("click", function() {
- addWidget({
- x: 34,
- type: "board",
- boardImage: "/img/boards/cribbage.svg",
- width: 1533,
- height: 414
- });
- drawWidgets(widgets);
- });
- $("#WdeckStandard").on("click", function() {
- addCardDeck({ pathPrefix: "/img/cards-french", cardsToAdd: [ "A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K" ] });
- });
- $("#WdeckExtended").on("click", function() {
- addCardDeck({
- pathPrefix: "/img/cards-french",
- numbers: [ "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15" ],
- cardsToAdd: [ "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15" ]
- });
- });
- $("#WdeckPiquet").on("click", function() {
- addCardDeck({ pathPrefix: "/img/cards-french", cardsToAdd: [ "A", "7", "8", "9", "10", "J", "Q", "K" ] });
- });
- $("#WdeckEuchre").on("click", function() {
- addCardDeck({ pathPrefix: "/img/cards-french", cardsToAdd: [ "A", "9", "10", "J", "Q", "K" ] });
- });
- $("#WdeckSpanish").on("click", function() {
- addCardDeck({
- pathPrefix: "/img/cards-spanish",
- backImage: "/img/cardback-crosshatch-noborder.svg",
- jokerColors: [],
- suits: [ 'Clubs', 'Coins', 'Cups', 'Swords' ],
- numbers: [ "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12" ],
- cardsToAdd: [ "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12" ]
- });
- });
- $("#WdeckCustom").on("click", function() {
- addCardDeck({ pathPrefix: "/img/cards-french", addPile: false, cardsToAdd: [] });
- });
- $("#WMainHand").on("click", function() {
- addWidget({
- id: "hand",
- type: "hand",
- x: 50,
- y: 820,
- z: 1,
- dragging: null
- });
- drawWidgets(widgets);
- });
- $("#WExtraHand").on("click", function() {
- addWidget({
- type: "hand",
- x: 50,
- y: 820,
- dragging: null
- });
- drawWidgets(widgets);
- });
- $("#WGhettoSpinner").on("click", function() {
- var options=[1,2,3,4,5,6];
- var input = prompt("Enter a number or options separated by commas:", 6);
- var a="";
- try {a= JSON.parse(input)}catch(err) {};
- if (typeof(a)=="number"){
- var options=Array.from({length: input}, (_, i) => i + 1);
- }else if (input.split(",").length!=1){options=JSON.parse("["+input+"]")}
- else if(input == null || input == "") {
- var options=[1,2,3,4,5,6];
- };
- addWidget({
- type: "spinner",
- options: options
- });
- drawWidgets(widgets);
- });
- $("#WdeckStandardOld").on("click", function() {
- addCardDeck({ jokerColors: [ "Black", "Red" ] });
- });
- $("#WdeckPiquetOld").on("click", function() {
- addCardDeck({ jokerColors: [ "Black", "Red" ], cardsToAdd: [ "A", "7", "8", "9", "10", "J", "Q", "K" ] });
- });
- $("#WdeckEuchreOld").on("click", function() {
- addCardDeck({ jokerColors: [ "Black", "Red" ], cardsToAdd: [ "A", "9", "10", "J", "Q", "K" ] });
- });
- $("#WdeckCustomOld").on("click", function() {
- addCardDeck({ jokerColors: [ "Black", "Red" ], addPile: false, cardsToAdd: [] });
- });
- $(".WColorPiece").on("click", function() {
- addWidget({
- type: "gamePiece",
- pieceType: $(this).val().toLowerCase(),
- color: $("#GPColor").val().toLowerCase()
- });
- drawWidgets(widgets);
- });
- $(".WChess").on("click", function() {
- var type = $(this).val().toLowerCase();
- addWidget({
- type: "gamePiece",
- pieceType: type,
- color: $("#ChessColor").val().toLowerCase(),
- width: defaultSizes.gamePiece[type][0],
- height: defaultSizes.gamePiece[type][1],
- dragging: null
- });
- drawWidgets(widgets);
- });
- $("#WBoardFull").on("click", function() {
- $("#fileCustomBoard").click();
- });
- $("#fileCustomBoard").on("change", function(evt) {
- const reader = new FileReader();
- reader.addEventListener("load", function () {
- var name = uniqueID("gpe-board");
- imageCache["userassets/" + name] = reader.result.replace(/data.*?,/, '');
- addWidget({
- x: 0,
- y: 0,
- type: "board",
- boardImage: "package://userassets/" + name,
- width: 1600,
- height: 1000
- });
- drawWidgets(widgets);
- }, false);
- if(evt.target.files[0])
- reader.readAsDataURL(evt.target.files[0]);
- });
- </script>
Add Comment
Please, Sign In to add comment