mirror of
https://github.com/AndrioCelos/TableturfBattleApp.git
synced 2026-04-25 16:20:12 -05:00
Rework deck editor page and allow drag/drop of decks and cards
This commit is contained in:
parent
252b326262
commit
5dfd7b6a44
3
.gitmodules
vendored
3
.gitmodules
vendored
|
|
@ -1,3 +1,6 @@
|
||||||
[submodule "TableturfBattleClient/qrcodejs"]
|
[submodule "TableturfBattleClient/qrcodejs"]
|
||||||
path = TableturfBattleClient/qrcodejs
|
path = TableturfBattleClient/qrcodejs
|
||||||
url = https://github.com/davidshimjs/qrcodejs.git
|
url = https://github.com/davidshimjs/qrcodejs.git
|
||||||
|
[submodule "TableturfBattleClient/mobile-drag-drop"]
|
||||||
|
path = TableturfBattleClient/mobile-drag-drop
|
||||||
|
url = https://github.com/timruffles/mobile-drag-drop.git
|
||||||
|
|
|
||||||
3
TableturfBattleClient/assets/grip-horizontal.svg
Normal file
3
TableturfBattleClient/assets/grip-horizontal.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="white" class="bi bi-grip-horizontal" viewBox="0 0 16 16">
|
||||||
|
<path d="M2 8a1 1 0 1 1 0 2 1 1 0 0 1 0-2zm0-3a1 1 0 1 1 0 2 1 1 0 0 1 0-2zm3 3a1 1 0 1 1 0 2 1 1 0 0 1 0-2zm0-3a1 1 0 1 1 0 2 1 1 0 0 1 0-2zm3 3a1 1 0 1 1 0 2 1 1 0 0 1 0-2zm0-3a1 1 0 1 1 0 2 1 1 0 0 1 0-2zm3 3a1 1 0 1 1 0 2 1 1 0 0 1 0-2zm0-3a1 1 0 1 1 0 2 1 1 0 0 1 0-2zm3 3a1 1 0 1 1 0 2 1 1 0 0 1 0-2zm0-3a1 1 0 1 1 0 2 1 1 0 0 1 0-2z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 479 B |
4
TableturfBattleClient/assets/plus-circle.svg
Normal file
4
TableturfBattleClient/assets/plus-circle.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="grey" class="bi bi-plus-circle" viewBox="0 0 16 16">
|
||||||
|
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"/>
|
||||||
|
<path d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 329 B |
|
|
@ -15,6 +15,11 @@
|
||||||
<link rel="stylesheet" href="tableturf.css"/>
|
<link rel="stylesheet" href="tableturf.css"/>
|
||||||
<script src="config/config.js"></script>
|
<script src="config/config.js"></script>
|
||||||
<script src="qrcodejs/qrcode.js"></script>
|
<script src="qrcodejs/qrcode.js"></script>
|
||||||
|
<script src="mobile-drag-drop/release/index.min.js"></script>
|
||||||
|
<script>
|
||||||
|
// Enable a polyfill for drag/drop support on mobile Firefox.
|
||||||
|
MobileDragDrop.polyfill({ dragImageTranslateOverride: MobileDragDrop.scrollBehaviourDragImageTranslateOverride });
|
||||||
|
</script>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<meta property="og:type" content="website"/>
|
<meta property="og:type" content="website"/>
|
||||||
<meta name="title" property="og:title" content="Tableturf Battle"/>
|
<meta name="title" property="og:title" content="Tableturf Battle"/>
|
||||||
|
|
@ -402,27 +407,27 @@
|
||||||
<div id="testPlacementList"></div>
|
<div id="testPlacementList"></div>
|
||||||
</div>
|
</div>
|
||||||
<div id="deckListPage" hidden>
|
<div id="deckListPage" hidden>
|
||||||
<section id="deckEditordeckListPage">
|
<section id="deckEditorDeckListSection">
|
||||||
<a id="deckListBackButton" href=".">Back</a>
|
<a id="deckListBackButton" href=".">Back</a>
|
||||||
<h3>Deck list</h3>
|
<h3>Deck list</h3>
|
||||||
<button id="deckExportAllButton">Export all</button>
|
<button id="deckExportAllButton">Export all</button>
|
||||||
<div id="deckList" class="deckList">
|
<div id="deckList" class="deckList">
|
||||||
<div id="addDeckControls">
|
</div>
|
||||||
<button id="newDeckButton">New deck</button>
|
<div id="addDeckControls">
|
||||||
<button id="importDeckButton">Import deck</button>
|
<button id="newDeckButton">New deck</button>
|
||||||
</div>
|
<button id="importDeckButton">Import deck</button>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<section id="deckEditorDeckViewSection" hidden>
|
<section id="deckEditorDeckViewSection">
|
||||||
<a id="deckViewBackButton" href="#">Back</a>
|
<a id="deckViewBackButton" href="#">Back</a>
|
||||||
<h3 id="deckName">Deck</h3>
|
<h3 id="deckName"> </h3>
|
||||||
<div>
|
<div id="deckListToolbar">
|
||||||
<button type="button" id="deckEditButton">Edit</button>
|
<button type="button" id="deckListTestButton" disabled>Test</button>
|
||||||
<button type="button" id="deckListTestButton">Test</button>
|
<button type="button" id="deckExportButton" disabled>Export</button>
|
||||||
<button type="button" id="deckExportButton">Export</button>
|
<button type="button" id="deckCopyButton" disabled>Copy</button>
|
||||||
<button type="button" id="deckRenameButton">Rename</button>
|
<button type="button" id="deckEditButton" disabled>Edit</button>
|
||||||
<button type="button" id="deckCopyButton">Copy</button>
|
<button type="button" id="deckRenameButton" disabled>Rename</button>
|
||||||
<button type="button" id="deckDeleteButton" class="danger">Delete</button>
|
<button type="button" id="deckDeleteButton" class="danger" disabled>Delete</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="deckSizeContainer">Total: <div id="deckViewSize">0</div></div>
|
<div class="deckSizeContainer">Total: <div id="deckViewSize">0</div></div>
|
||||||
<div id="deckCardListView">
|
<div id="deckCardListView">
|
||||||
|
|
@ -490,15 +495,15 @@
|
||||||
</dialog>
|
</dialog>
|
||||||
</div>
|
</div>
|
||||||
<div id="deckEditPage" hidden>
|
<div id="deckEditPage" hidden>
|
||||||
<section id="deckEditordeckEditPage">
|
<section id="deckEditorDeckEditPage">
|
||||||
<h3 id="deckName2">Deck</h3>
|
<h3 id="deckName2">Deck</h3>
|
||||||
<div>
|
<div id="deckEditToolbar">
|
||||||
<button type="button" id="deckSortButton">Sort</button>
|
<button type="button" id="deckSortButton">Sort</button>
|
||||||
<button type="button" id="deckTestButton">Test</button>
|
<button type="button" id="deckTestButton">Test</button>
|
||||||
<button type="button" id="deckSaveButton">Save</button>
|
<button type="button" id="deckSaveButton">Save</button>
|
||||||
<button type="button" id="deckCancelButton">Cancel</button>
|
<button type="button" id="deckCancelButton">Cancel</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="deckSizeContainer">Total: <div id="deckEditSize">0</div></div>
|
<div class="deckSizeContainer">Total <div id="deckEditSize">0</div></div>
|
||||||
<div id="deckCardListEdit">
|
<div id="deckCardListEdit">
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
|
||||||
1
TableturfBattleClient/mobile-drag-drop
Submodule
1
TableturfBattleClient/mobile-drag-drop
Submodule
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit e30fa878adfb669745aa52e4bb6de8f31fbd1828
|
||||||
|
|
@ -31,9 +31,33 @@ class CheckButtonGroup<TValue> {
|
||||||
}
|
}
|
||||||
|
|
||||||
replace(index: number, button: CheckButton, value: TValue) {
|
replace(index: number, button: CheckButton, value: TValue) {
|
||||||
|
const existingChild = this.entries[index].button.buttonElement;
|
||||||
this.entries[index] = { button, value };
|
this.entries[index] = { button, value };
|
||||||
this.setupButton(button, value);
|
this.setupButton(button, value);
|
||||||
// The caller is responsible for adding the button to the DOM.
|
this.parentElement?.insertBefore(button.buttonElement, existingChild);
|
||||||
|
this.parentElement?.removeChild(existingChild);
|
||||||
|
}
|
||||||
|
|
||||||
|
insert(index: number, button: CheckButton, value: TValue) {
|
||||||
|
const existingChild = this.entries[index].button.buttonElement;
|
||||||
|
this.entries.splice(index, 0, { button, value });
|
||||||
|
this.setupButton(button, value);
|
||||||
|
this.parentElement?.insertBefore(button.buttonElement, existingChild);
|
||||||
|
}
|
||||||
|
|
||||||
|
removeAt(index: number) {
|
||||||
|
const existingChild = this.entries[index].button.buttonElement;
|
||||||
|
this.entries.splice(index, 1)
|
||||||
|
this.parentElement?.removeChild(existingChild);
|
||||||
|
}
|
||||||
|
|
||||||
|
move(index: number, newIndex: number | null) {
|
||||||
|
const entry = this.entries[index];
|
||||||
|
this.entries.splice(index, 1)
|
||||||
|
const existingChild = newIndex == null || newIndex >= this.entries.length ? null : this.entries[newIndex].button.buttonElement;
|
||||||
|
if (newIndex == null) this.entries.push(entry);
|
||||||
|
else this.entries.splice(newIndex, 0, entry);
|
||||||
|
this.parentElement?.insertBefore(entry.button.buttonElement, existingChild);
|
||||||
}
|
}
|
||||||
|
|
||||||
clear() {
|
clear() {
|
||||||
|
|
|
||||||
|
|
@ -7,3 +7,4 @@ interface Config {
|
||||||
}
|
}
|
||||||
|
|
||||||
declare var config: Config;
|
declare var config: Config;
|
||||||
|
declare var polyfillActive: boolean;
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ const testStageSelectionDialog = document.getElementById('testStageSelectionDial
|
||||||
|
|
||||||
const deckEditCardButtons = new CheckButtonGroup<number>(deckCardListEdit);
|
const deckEditCardButtons = new CheckButtonGroup<number>(deckCardListEdit);
|
||||||
|
|
||||||
let selectedDeckCardIndex: number | null = null;
|
let draggingCardButton: Element | null = null;
|
||||||
|
|
||||||
function deckEditInitCardDatabase(cards: Card[]) {
|
function deckEditInitCardDatabase(cards: Card[]) {
|
||||||
for (const card of cards) {
|
for (const card of cards) {
|
||||||
|
|
@ -32,8 +32,9 @@ function deckEditInitCardDatabase(cards: Card[]) {
|
||||||
button2.checked = false;
|
button2.checked = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectedDeckCardIndex == null) return;
|
const index = deckEditCardButtons.entries.findIndex(el => el.button.checked);
|
||||||
const oldEntry = deckEditCardButtons.entries[selectedDeckCardIndex];
|
if (index < 0) return;
|
||||||
|
const oldEntry = deckEditCardButtons.entries[index];
|
||||||
const oldCardNumber = oldEntry.value;
|
const oldCardNumber = oldEntry.value;
|
||||||
|
|
||||||
if (oldCardNumber != 0)
|
if (oldCardNumber != 0)
|
||||||
|
|
@ -43,11 +44,7 @@ function deckEditInitCardDatabase(cards: Card[]) {
|
||||||
const button3 = createDeckEditCardButton(card.number);
|
const button3 = createDeckEditCardButton(card.number);
|
||||||
button3.checked = true;
|
button3.checked = true;
|
||||||
|
|
||||||
const oldElement = oldEntry.button.buttonElement;
|
deckEditCardButtons.replace(index, button3, card.number);
|
||||||
deckCardListEdit.insertBefore(button3.buttonElement, oldElement);
|
|
||||||
deckCardListEdit.removeChild(oldElement);
|
|
||||||
|
|
||||||
deckEditCardButtons.replace(selectedDeckCardIndex, button3, card.number);
|
|
||||||
deckEditUpdateSize();
|
deckEditUpdateSize();
|
||||||
|
|
||||||
cardList.listElement.parentElement!.classList.remove('selecting');
|
cardList.listElement.parentElement!.classList.remove('selecting');
|
||||||
|
|
@ -94,7 +91,6 @@ function editDeck() {
|
||||||
deckNameLabel2.innerText = selectedDeck.name;
|
deckNameLabel2.innerText = selectedDeck.name;
|
||||||
|
|
||||||
deckEditCardButtons.clear();
|
deckEditCardButtons.clear();
|
||||||
selectedDeckCardIndex = null;
|
|
||||||
|
|
||||||
for (let i = 0; i < 15; i++) {
|
for (let i = 0; i < 15; i++) {
|
||||||
if (selectedDeck.cards[i]) {
|
if (selectedDeck.cards[i]) {
|
||||||
|
|
@ -117,27 +113,88 @@ function editDeck() {
|
||||||
|
|
||||||
function createDeckEditCardButton(cardNumber: number) {
|
function createDeckEditCardButton(cardNumber: number) {
|
||||||
const button = new CardButton(cardDatabase.get(cardNumber));
|
const button = new CardButton(cardDatabase.get(cardNumber));
|
||||||
|
button.buttonElement.draggable = true;
|
||||||
button.buttonElement.addEventListener('click', () => {
|
button.buttonElement.addEventListener('click', () => {
|
||||||
selectedDeckCardIndex = deckEditCardButtons.entries.findIndex(e => e.button == button);
|
|
||||||
for (const button2 of cardList.cardButtons) {
|
for (const button2 of cardList.cardButtons) {
|
||||||
button2.checked = button2.card.number == cardNumber;
|
button2.checked = button2.card.number == cardNumber;
|
||||||
}
|
}
|
||||||
cardList.listElement.parentElement!.classList.add('selecting');
|
cardList.listElement.parentElement!.classList.add('selecting');
|
||||||
});
|
});
|
||||||
|
button.buttonElement.addEventListener('dragstart', e => {
|
||||||
|
if (e.dataTransfer == null) return;
|
||||||
|
const index = deckEditCardButtons.entries.findIndex(el => el.button.buttonElement == e.currentTarget);
|
||||||
|
draggingCardButton = button.buttonElement;
|
||||||
|
e.dataTransfer.effectAllowed = 'move';
|
||||||
|
e.dataTransfer.setData('application/tableturf-card-index', index.toString());
|
||||||
|
button.buttonElement.classList.add('dragging');
|
||||||
|
});
|
||||||
|
button.buttonElement.addEventListener('dragend', e => {
|
||||||
|
button.buttonElement.classList.remove('dragging');
|
||||||
|
if (draggingCardButton != null && e.currentTarget == draggingCardButton) {
|
||||||
|
const index = deckEditCardButtons.entries.findIndex(el => el.button.buttonElement == e.currentTarget) + 1;
|
||||||
|
deckCardListEdit.insertBefore(draggingCardButton, index >= deckEditCardButtons.entries.length ? null : deckEditCardButtons.entries[index].button.buttonElement);
|
||||||
|
draggingCardButton = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
button.buttonElement.addEventListener('dragenter', e => e.preventDefault());
|
||||||
|
button.buttonElement.addEventListener('dragover', deckEditCardButton_dragover);
|
||||||
|
button.buttonElement.addEventListener('drop', deckEditCardButton_drop);
|
||||||
|
|
||||||
return button;
|
return button;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function deckEditCardButton_dragover(e: DragEvent) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (e.dataTransfer == null) return;
|
||||||
|
const indexString = e.dataTransfer.getData('application/tableturf-card-index');
|
||||||
|
if (indexString != '' && draggingCardButton != null) {
|
||||||
|
e.dataTransfer.dropEffect = 'move';
|
||||||
|
if (e.currentTarget != draggingCardButton && e.currentTarget != deckCardListEdit) {
|
||||||
|
// Move the card being dragged into the new position as a preview.
|
||||||
|
for (let el = draggingCardButton.nextElementSibling; el != null; el = el.nextElementSibling) {
|
||||||
|
if (el == e.currentTarget) {
|
||||||
|
deckCardListEdit.insertBefore(draggingCardButton, el.nextElementSibling);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
deckCardListEdit.insertBefore(draggingCardButton, e.currentTarget as Node);
|
||||||
|
}
|
||||||
|
} else if (e.dataTransfer.getData('text/plain'))
|
||||||
|
e.dataTransfer.dropEffect = 'copy';
|
||||||
|
}
|
||||||
|
|
||||||
|
function deckEditCardButton_drop(e: DragEvent) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (e.dataTransfer == null) return;
|
||||||
|
const indexString = e.dataTransfer.getData('application/tableturf-card-index');
|
||||||
|
if (indexString) {
|
||||||
|
const index = parseInt(indexString);
|
||||||
|
let newIndex = 0;
|
||||||
|
for (let el = deckCardListEdit.firstElementChild; el != null; el = el.nextElementSibling) {
|
||||||
|
if (el == draggingCardButton) break;
|
||||||
|
newIndex++;
|
||||||
|
}
|
||||||
|
if (newIndex == index) return;
|
||||||
|
console.log(`Moving card ${index} to ${newIndex}.`);
|
||||||
|
|
||||||
|
deckEditCardButtons.move(index, newIndex);
|
||||||
|
draggingCardButton = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function createDeckEditEmptySlotButton() {
|
function createDeckEditEmptySlotButton() {
|
||||||
const buttonElement = document.createElement('button');
|
const buttonElement = document.createElement('button');
|
||||||
const button = new CheckButton(buttonElement);
|
const button = new CheckButton(buttonElement);
|
||||||
buttonElement.type = 'button';
|
buttonElement.type = 'button';
|
||||||
buttonElement.className = 'card emptySlot';
|
buttonElement.className = 'card emptySlot';
|
||||||
buttonElement.addEventListener('click', () => {
|
buttonElement.addEventListener('click', () => {
|
||||||
selectedDeckCardIndex = deckEditCardButtons.entries.findIndex(e => e.button == button);
|
|
||||||
for (const button2 of cardList.cardButtons)
|
for (const button2 of cardList.cardButtons)
|
||||||
button2.checked = false;
|
button2.checked = false;
|
||||||
cardList.listElement.parentElement!.classList.add('selecting');
|
cardList.listElement.parentElement!.classList.add('selecting');
|
||||||
});
|
});
|
||||||
|
buttonElement.addEventListener('dragenter', e => e.preventDefault());
|
||||||
|
buttonElement.addEventListener('dragover', deckEditCardButton_dragover);
|
||||||
|
buttonElement.addEventListener('drop', deckEditCardButton_drop);
|
||||||
return button;
|
return button;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
const deckListPage = document.getElementById('deckListPage')!;
|
||||||
const deckListBackButton = document.getElementById('deckListBackButton') as HTMLLinkElement;
|
const deckListBackButton = document.getElementById('deckListBackButton') as HTMLLinkElement;
|
||||||
const deckViewBackButton = document.getElementById('deckViewBackButton') as HTMLLinkElement;
|
const deckViewBackButton = document.getElementById('deckViewBackButton') as HTMLLinkElement;
|
||||||
const deckEditorDeckViewSection = document.getElementById('deckEditorDeckViewSection')!;
|
const deckEditorDeckViewSection = document.getElementById('deckEditorDeckViewSection')!;
|
||||||
|
|
@ -38,7 +39,10 @@ const deckImportFileBox = document.getElementById('deckImportFileBox') as HTMLIn
|
||||||
const deckImportErrorBox = document.getElementById('deckImportErrorBox')!;
|
const deckImportErrorBox = document.getElementById('deckImportErrorBox')!;
|
||||||
const deckImportOkButton = document.getElementById('deckImportOkButton') as HTMLButtonElement;
|
const deckImportOkButton = document.getElementById('deckImportOkButton') as HTMLButtonElement;
|
||||||
|
|
||||||
const deckButtons = new CheckButtonGroup<Deck>();
|
const deckButtons = new CheckButtonGroup<Deck>(deckList);
|
||||||
|
|
||||||
|
let deckListTouchMode = false;
|
||||||
|
let draggingDeckButton: Element | null = null;
|
||||||
|
|
||||||
function showDeckList() {
|
function showDeckList() {
|
||||||
showPage('deckList');
|
showPage('deckList');
|
||||||
|
|
@ -46,6 +50,18 @@ function showDeckList() {
|
||||||
deckButtons.deselect();
|
deckButtons.deselect();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deckList.addEventListener('touchstart', deckListEnableTouchMode);
|
||||||
|
|
||||||
|
function deckListEnableTouchMode() {
|
||||||
|
if (deckListTouchMode) return;
|
||||||
|
deckListTouchMode = true;
|
||||||
|
deckListPage.classList.add('touchmode');
|
||||||
|
for (var b of deckButtons.buttons) {
|
||||||
|
b.buttonElement.draggable = false;
|
||||||
|
(b.buttonElement.getElementsByClassName('handle')[0] as HTMLElement).draggable = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
deckListBackButton.addEventListener('click', e => {
|
deckListBackButton.addEventListener('click', e => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
showPage('preGame');
|
showPage('preGame');
|
||||||
|
|
@ -65,7 +81,7 @@ deckViewBackButton.addEventListener('click', e => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
clearChildren(deckCardListView);
|
clearChildren(deckCardListView);
|
||||||
deselectDeck();
|
deselectDeck();
|
||||||
deckEditorDeckViewSection.hidden = true;
|
deckListPage.classList.remove('showingDeck');
|
||||||
});
|
});
|
||||||
|
|
||||||
function saveDecks() {
|
function saveDecks() {
|
||||||
|
|
@ -90,25 +106,99 @@ function saveDecks() {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let i = 0; i < decks.length; i++) {
|
for (let i = 0; i < decks.length; i++) {
|
||||||
createDeckButton(i, decks[i]);
|
createDeckButton(decks[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function createDeckButton(index: number, deck: Deck) {
|
function createDeckButton(deck: Deck) {
|
||||||
const buttonElement = document.createElement('button');
|
const buttonElement = document.createElement('button');
|
||||||
buttonElement.type = 'button';
|
buttonElement.type = 'button';
|
||||||
|
buttonElement.draggable = true;
|
||||||
const button = new CheckButton(buttonElement);
|
const button = new CheckButton(buttonElement);
|
||||||
deckButtons.add(button, deck);
|
deckButtons.add(button, deck);
|
||||||
buttonElement.addEventListener('click', () => {
|
buttonElement.addEventListener('click', () => {
|
||||||
selectedDeck = deckButtons.value;
|
selectedDeck = deckButtons.value;
|
||||||
selectDeck();
|
selectDeck();
|
||||||
});
|
});
|
||||||
buttonElement.innerText = deck.name;
|
buttonElement.addEventListener('dragstart', e => {
|
||||||
|
if (e.dataTransfer == null) return;
|
||||||
|
const index = decks.indexOf(deck);
|
||||||
|
draggingDeckButton = buttonElement;
|
||||||
|
e.dataTransfer.effectAllowed = 'copyMove';
|
||||||
|
e.dataTransfer.setData('text/plain', JSON.stringify(deck, [ 'name', 'cards' ]));
|
||||||
|
e.dataTransfer.setData('application/tableturf-deck-index', index.toString());
|
||||||
|
buttonElement.classList.add('dragging');
|
||||||
|
});
|
||||||
|
buttonElement.addEventListener('dragend', e => {
|
||||||
|
buttonElement.classList.remove('dragging');
|
||||||
|
if (draggingDeckButton != null && e.currentTarget == draggingDeckButton) {
|
||||||
|
const index = deckButtons.entries.findIndex(el => el.button.buttonElement == e.currentTarget) + 1;
|
||||||
|
deckList.insertBefore(draggingDeckButton, index >= deckButtons.entries.length ? null : deckButtons.entries[index].button.buttonElement);
|
||||||
|
draggingDeckButton = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
buttonElement.addEventListener('dragenter', e => e.preventDefault());
|
||||||
|
buttonElement.addEventListener('dragover', deckButton_dragover);
|
||||||
|
buttonElement.addEventListener('drop', deckButton_drop);
|
||||||
|
|
||||||
|
const handle = document.createElement('div');
|
||||||
|
handle.className = 'handle';
|
||||||
|
buttonElement.appendChild(handle);
|
||||||
|
buttonElement.appendChild(document.createTextNode(deck.name));
|
||||||
|
|
||||||
deckList.insertBefore(buttonElement, addDeckControls);
|
|
||||||
return button;
|
return button;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function deckButton_dragover(e: DragEvent) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (e.dataTransfer == null) return;
|
||||||
|
const indexString = e.dataTransfer.getData('application/tableturf-deck-index');
|
||||||
|
if (indexString != '' && draggingDeckButton != null) {
|
||||||
|
e.dataTransfer.dropEffect = 'move';
|
||||||
|
if (e.currentTarget != draggingDeckButton && e.currentTarget != deckList) {
|
||||||
|
// Move the deck being dragged into the new position as a preview.
|
||||||
|
for (let el = draggingDeckButton.nextElementSibling; el != null; el = el.nextElementSibling) {
|
||||||
|
if (el == e.currentTarget) {
|
||||||
|
deckList.insertBefore(draggingDeckButton, el.nextElementSibling);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
deckList.insertBefore(draggingDeckButton, e.currentTarget as Node);
|
||||||
|
}
|
||||||
|
} else if (e.dataTransfer.getData('text/plain'))
|
||||||
|
e.dataTransfer.dropEffect = 'copy';
|
||||||
|
}
|
||||||
|
|
||||||
|
function deckButton_drop(e: DragEvent) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (e.dataTransfer == null) return;
|
||||||
|
const indexString = e.dataTransfer.getData('application/tableturf-deck-index');
|
||||||
|
if (indexString) {
|
||||||
|
const index = parseInt(indexString);
|
||||||
|
let newIndex = 0;
|
||||||
|
for (let el = deckList.firstElementChild; el != null; el = el.nextElementSibling) {
|
||||||
|
if (el == draggingDeckButton) break;
|
||||||
|
newIndex++;
|
||||||
|
}
|
||||||
|
if (newIndex == index) return;
|
||||||
|
console.log(`Moving deck ${index} to ${newIndex}.`);
|
||||||
|
|
||||||
|
deckButtons.move(index, newIndex);
|
||||||
|
const deck = decks[index];
|
||||||
|
decks.splice(index, 1);
|
||||||
|
decks.splice(newIndex, 0, deck);
|
||||||
|
saveDecks();
|
||||||
|
draggingDeckButton = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const text = e.dataTransfer.getData('text/plain');
|
||||||
|
if (text) {
|
||||||
|
const data = JSON.parse(text);
|
||||||
|
const decks = (data instanceof Array ? data : [ data ]) as Deck[];
|
||||||
|
importDecks(decks);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function importDecks(decksToImport: (Deck | number[])[]) {
|
function importDecks(decksToImport: (Deck | number[])[]) {
|
||||||
let newSelectedDeck: Deck | null = null;
|
let newSelectedDeck: Deck | null = null;
|
||||||
for (const el of decksToImport) {
|
for (const el of decksToImport) {
|
||||||
|
|
@ -119,7 +209,7 @@ function importDecks(decksToImport: (Deck | number[])[]) {
|
||||||
deck = el;
|
deck = el;
|
||||||
if (!deck.name) deck.name = `Imported Deck ${decks.length + 1}`;
|
if (!deck.name) deck.name = `Imported Deck ${decks.length + 1}`;
|
||||||
}
|
}
|
||||||
createDeckButton(decks.length, deck);
|
createDeckButton(deck);
|
||||||
decks.push(deck);
|
decks.push(deck);
|
||||||
newSelectedDeck ??= deck;
|
newSelectedDeck ??= deck;
|
||||||
}
|
}
|
||||||
|
|
@ -134,7 +224,7 @@ function importDecks(decksToImport: (Deck | number[])[]) {
|
||||||
|
|
||||||
newDeckButton.addEventListener('click', () => {
|
newDeckButton.addEventListener('click', () => {
|
||||||
selectedDeck = new Deck(`Deck ${decks.length + 1}`, [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], false);
|
selectedDeck = new Deck(`Deck ${decks.length + 1}`, [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], false);
|
||||||
createDeckButton(decks.length, selectedDeck);
|
createDeckButton(selectedDeck);
|
||||||
decks.push(selectedDeck);
|
decks.push(selectedDeck);
|
||||||
editDeck();
|
editDeck();
|
||||||
});
|
});
|
||||||
|
|
@ -196,20 +286,29 @@ function selectDeck() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deckListTestButton.disabled = false;
|
||||||
|
deckExportButton.disabled = false;
|
||||||
|
deckCopyButton.disabled = false;
|
||||||
deckEditButton.disabled = selectedDeck.isReadOnly;
|
deckEditButton.disabled = selectedDeck.isReadOnly;
|
||||||
deckRenameButton.disabled = selectedDeck.isReadOnly;
|
deckRenameButton.disabled = selectedDeck.isReadOnly;
|
||||||
deckCopyButton.disabled = false;
|
|
||||||
deckDeleteButton.disabled = selectedDeck.isReadOnly;
|
deckDeleteButton.disabled = selectedDeck.isReadOnly;
|
||||||
deckViewSize.innerText = size.toString();
|
deckViewSize.innerText = size.toString();
|
||||||
deckEditorDeckViewSection.hidden = false;
|
deckListPage.classList.add('showingDeck');
|
||||||
}
|
}
|
||||||
|
|
||||||
function deselectDeck() {
|
function deselectDeck() {
|
||||||
selectedDeck = null;
|
selectedDeck = null;
|
||||||
|
deckButtons.deselect();
|
||||||
|
clearChildren(deckCardListView);
|
||||||
|
deckNameLabel.innerText = '\u00a0';
|
||||||
|
deckViewSize.innerText = '0';
|
||||||
|
deckListTestButton.disabled = true;
|
||||||
|
deckExportButton.disabled = true;
|
||||||
|
deckCopyButton.disabled = true;
|
||||||
deckEditButton.disabled = true;
|
deckEditButton.disabled = true;
|
||||||
deckRenameButton.disabled = true;
|
deckRenameButton.disabled = true;
|
||||||
deckCopyButton.disabled = true;
|
|
||||||
deckDeleteButton.disabled = true;
|
deckDeleteButton.disabled = true;
|
||||||
|
deckListPage.classList.remove('showingDeck');
|
||||||
}
|
}
|
||||||
|
|
||||||
deckExportButton.addEventListener('click', () => {
|
deckExportButton.addEventListener('click', () => {
|
||||||
|
|
@ -244,19 +343,10 @@ deckDeleteButton.addEventListener('click', () => {
|
||||||
if (selectedDeck == null) return;
|
if (selectedDeck == null) return;
|
||||||
if (!confirm(`Are you sure you want to delete ${selectedDeck.name}?`)) return;
|
if (!confirm(`Are you sure you want to delete ${selectedDeck.name}?`)) return;
|
||||||
|
|
||||||
for (const el of deckButtons.entries) {
|
|
||||||
if (el.value == selectedDeck) {
|
|
||||||
deckList.removeChild(el.button.buttonElement);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const index = decks.indexOf(selectedDeck);
|
const index = decks.indexOf(selectedDeck);
|
||||||
if (index >= 0) decks.splice(index, 1);
|
if (index >= 0) decks.splice(index, 1);
|
||||||
deckButtons.entries.splice(index, 1);
|
deckButtons.removeAt(index);
|
||||||
deckButtons.deselect();
|
deselectDeck();
|
||||||
selectedDeck = null;
|
|
||||||
deckEditorDeckViewSection.hidden = true;
|
|
||||||
saveDecks();
|
saveDecks();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -195,6 +195,8 @@ document.getElementById('preGameBackButton')!.addEventListener('click', e => {
|
||||||
backPreGameForm(true);
|
backPreGameForm(true);
|
||||||
})
|
})
|
||||||
|
|
||||||
|
preGameDeckEditorButton.addEventListener('touchstart', deckListEnableTouchMode);
|
||||||
|
|
||||||
preGameDeckEditorButton.addEventListener('click', e => {
|
preGameDeckEditorButton.addEventListener('click', e => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
showDeckList();
|
showDeckList();
|
||||||
|
|
|
||||||
|
|
@ -1328,11 +1328,16 @@ dialog::backdrop {
|
||||||
|
|
||||||
:is(#deckListPage, #deckEditPage):not([hidden]) {
|
:is(#deckListPage, #deckEditPage):not([hidden]) {
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
display: grid;
|
display: flex;
|
||||||
grid-template-columns: auto 1fr;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.deckList button {
|
.deckList {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.deckList button, #addDeckControls button {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
@ -1341,12 +1346,15 @@ dialog::backdrop {
|
||||||
border: inherit;
|
border: inherit;
|
||||||
text-shadow: 0 0 4px black;
|
text-shadow: 0 0 4px black;
|
||||||
}
|
}
|
||||||
.deckList > * {
|
button.dragging {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
.deckList > button, #addDeckControls {
|
||||||
width: 20rem;
|
width: 20rem;
|
||||||
height: 3rem;
|
height: 3rem;
|
||||||
margin: 0.5em;
|
margin: 0.5em;
|
||||||
}
|
}
|
||||||
.deckList > button { background: var(--primary-colour-2); }
|
.deckList > button { background: var(--primary-colour-2); position: relative;}
|
||||||
.deckList > button:hover { background: var(--special-colour-2); }
|
.deckList > button:hover { background: var(--special-colour-2); }
|
||||||
.deckList > button:focus-within { outline: 2px solid var(--special-accent-colour-2); }
|
.deckList > button:focus-within { outline: 2px solid var(--special-accent-colour-2); }
|
||||||
.deckList > button:is(:active, .checked) { background: var(--special-accent-colour-2); }
|
.deckList > button:is(:active, .checked) { background: var(--special-accent-colour-2); }
|
||||||
|
|
@ -1357,6 +1365,7 @@ dialog::backdrop {
|
||||||
gap: 1em;
|
gap: 1em;
|
||||||
}
|
}
|
||||||
#addDeckControls > button {
|
#addDeckControls > button {
|
||||||
|
flex-basis: 8em;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
background: black;
|
background: black;
|
||||||
border: 0.25em solid var(--primary-colour-2);
|
border: 0.25em solid var(--primary-colour-2);
|
||||||
|
|
@ -1365,13 +1374,22 @@ dialog::backdrop {
|
||||||
#addDeckControls > button:focus-within { outline: 2px solid var(--special-accent-colour-2); }
|
#addDeckControls > button:focus-within { outline: 2px solid var(--special-accent-colour-2); }
|
||||||
#addDeckControls > button:is(:active, .checked) { border-color: var(--special-accent-colour-2); }
|
#addDeckControls > button:is(:active, .checked) { border-color: var(--special-accent-colour-2); }
|
||||||
|
|
||||||
.deckList {
|
.deckList .handle {
|
||||||
display: flex;
|
position: absolute;
|
||||||
flex-flow: column;
|
background: url('assets/grip-horizontal.svg') 0.5em center/1em no-repeat;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.touchmode .handle {
|
||||||
|
width: 2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card.emptySlot {
|
.card.emptySlot {
|
||||||
--colour: dimgrey;
|
--colour: dimgrey;
|
||||||
|
background: url('assets/plus-circle.svg') center/2em no-repeat, black;
|
||||||
}
|
}
|
||||||
|
|
||||||
#deckCardListView, #deckCardListEdit {
|
#deckCardListView, #deckCardListEdit {
|
||||||
|
|
@ -1379,14 +1397,15 @@ dialog::backdrop {
|
||||||
grid-template-columns: auto auto auto;
|
grid-template-columns: auto auto auto;
|
||||||
justify-items: center;
|
justify-items: center;
|
||||||
grid-column: 2;
|
grid-column: 2;
|
||||||
grid-row: 1 / 5;
|
grid-row: 2 / -1;
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
|
width: 33em;
|
||||||
}
|
}
|
||||||
|
|
||||||
#deckEditorDeckViewSection:not([hidden]), #deckEditordeckEditPage {
|
#deckEditorDeckViewSection:not([hidden]), #deckEditorDeckEditPage {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: minmax(min-content, auto) min-content;
|
grid-template-columns: 1fr auto;
|
||||||
grid-template-rows: auto auto auto 1fr;
|
grid-template-rows: auto auto 1fr;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1395,21 +1414,35 @@ dialog::backdrop {
|
||||||
}
|
}
|
||||||
|
|
||||||
#deckName, #deckName2 {
|
#deckName, #deckName2 {
|
||||||
grid-column: 1;
|
grid-column: 1 / -1;
|
||||||
grid-row: 1;
|
grid-row: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
:is(#deckEditorDeckViewSection, #deckEditordeckEditPage) > h3 + div {
|
#deckEditToolbar, #deckListToolbar {
|
||||||
grid-row: 2;
|
|
||||||
grid-column: 1;
|
grid-column: 1;
|
||||||
|
grid-row: 3;
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column;
|
||||||
|
}
|
||||||
|
:is(#deckListToolbar, #deckEditToolbar) button {
|
||||||
|
margin: 0.25em;
|
||||||
|
width: 5em;
|
||||||
|
height: 4em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.deckSizeContainer {
|
.deckSizeContainer {
|
||||||
grid-column: 1;
|
grid-column: 1;
|
||||||
grid-row: 3;
|
grid-row: 2;
|
||||||
|
background: dimgrey;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 1.5em;
|
||||||
|
margin: 0.25em;
|
||||||
|
min-width: 4em;
|
||||||
}
|
}
|
||||||
|
|
||||||
#deckEditorCardListSection {
|
#deckEditorCardListSection {
|
||||||
|
flex-grow: 1;
|
||||||
|
background: #00000080;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-rows: auto 1fr;
|
grid-template-rows: auto 1fr;
|
||||||
|
|
@ -1684,16 +1717,21 @@ dialog::backdrop {
|
||||||
|
|
||||||
:is(#deckListPage, #deckEditPage) section {
|
:is(#deckListPage, #deckEditPage) section {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
background: black;
|
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#deckListPage:not(.showingDeck) #deckEditorDeckViewSection {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
#deckListPage.showingDeck #deckEditorDeckListSection {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
#deckEditorDeckViewSection:not([hidden]) {
|
#deckEditorDeckViewSection:not([hidden]) {
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
grid-template-columns: auto 1fr 1fr;
|
grid-template-columns: auto 1fr auto;
|
||||||
grid-template-rows: auto auto 1fr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#deckViewBackButton {
|
#deckViewBackButton {
|
||||||
|
|
@ -1709,16 +1747,23 @@ dialog::backdrop {
|
||||||
}
|
}
|
||||||
|
|
||||||
#deckName {
|
#deckName {
|
||||||
grid-column: 2;
|
grid-column: 2 / -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
:is(#deckEditorDeckViewSection, #deckEditordeckEditPage) > h3 + div {
|
#deckListToolbar, #deckEditToolbar {
|
||||||
grid-row: 1;
|
grid-row: 2;
|
||||||
grid-column: -2;
|
grid-column: 1 / -2;
|
||||||
|
flex-flow: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
:is(#deckListToolbar, #deckEditToolbar) button {
|
||||||
|
width: auto;
|
||||||
|
flex-grow: 1;
|
||||||
|
flex-basis: 4em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.deckSizeContainer {
|
.deckSizeContainer {
|
||||||
grid-column: 1 / -1;
|
grid-column: -2;
|
||||||
grid-row: 2;
|
grid-row: 2;
|
||||||
justify-self: end;
|
justify-self: end;
|
||||||
}
|
}
|
||||||
|
|
@ -1729,6 +1774,7 @@ dialog::backdrop {
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
font-size: 80%;
|
font-size: 80%;
|
||||||
grid-template-columns: 1fr 1fr 1fr;
|
grid-template-columns: 1fr 1fr 1fr;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
#deckEditorCardListSection:not(.selecting) {
|
#deckEditorCardListSection:not(.selecting) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user