Adam Goldsmith
6faf7988f5
actually uses deck number and FaceURL preperation for arbitrary decks (from TTS mods) also changes to send only the deck part of the JSON
291 lines
9.4 KiB
JavaScript
291 lines
9.4 KiB
JavaScript
let deckName, deckNum, deckJSON, cardCount, deckWidth, deckHeight,
|
|
piles = {'deck': [], discard: []};
|
|
|
|
interact.dynamicDrop(true);
|
|
|
|
window.addEventListener('load', () => {
|
|
let xhr = new XMLHttpRequest();
|
|
xhr.addEventListener("load", () => {
|
|
deckJSON = JSON.parse(xhr.responseText);
|
|
deckNum = Object.keys(deckJSON.CustomDeck)[0];
|
|
piles.deck = deckJSON.DeckIDs.map(c => c - deckNum * 100);
|
|
cardCount = piles.deck.length;
|
|
shuffle(piles.deck);
|
|
deckWidth = deckJSON.CustomDeck[deckNum].NumWidth;
|
|
deckHeight = deckJSON.CustomDeck[deckNum].NumHeight;
|
|
console.log(deckName);
|
|
});
|
|
deckName = document.querySelector('#card-container').getAttribute("data-deckName");
|
|
xhr.open("GET", "deck.json");
|
|
xhr.send();
|
|
|
|
window.addEventListener("contextmenu", event => event.preventDefault());
|
|
});
|
|
|
|
let cardInteract = interact('.card', {ignoreFrom: '.in-list'})
|
|
.draggable({
|
|
restrict: {
|
|
restriction: "parent",
|
|
endOnly: true,
|
|
elementRect: { top: 0, left: 0, bottom: 1, right: 1 }
|
|
},
|
|
|
|
snap: {
|
|
targets: [() => {
|
|
// TODO: maybe change to dropzone?
|
|
let pos = document.body.getBoundingClientRect(),
|
|
hand = document.getElementById('hand'),
|
|
style = window.getComputedStyle(hand),
|
|
handHeight = parseInt(style.getPropertyValue("height"));
|
|
return {y: pos.bottom,
|
|
range: handHeight/2};
|
|
}],
|
|
relativePoints: [{x: 0.5 , y: 1}]
|
|
},
|
|
|
|
onmove: event => {
|
|
dragMoveListener(event);
|
|
// raise to top
|
|
event.target.parentElement.removeChild(event.target);
|
|
document.querySelector("#card-container").appendChild(event.target);
|
|
}
|
|
})
|
|
.on('doubletap', event => {
|
|
let scale = parseFloat(event.target.getAttribute('data-scale')) === 2 ? 1 : 2;
|
|
event.target.setAttribute('data-scale', scale);
|
|
dragMoveListener({target: event.target, dx: 0, dy: 0});
|
|
});
|
|
|
|
interact('.card.in-list')
|
|
.draggable({
|
|
restrict: {
|
|
restriction: "parent",
|
|
endOnly: false,
|
|
elementRect: { top: 0, left: 0, bottom: 1, right: 1 }
|
|
},
|
|
autoScroll: false,
|
|
|
|
onmove: dragMoveListener,
|
|
onend: event => {
|
|
// reset transform and data attributes
|
|
event.target.style.webkitTransform = event.target.style.transform = '';
|
|
|
|
event.target.removeAttribute('data-x');
|
|
event.target.removeAttribute('data-y');
|
|
}
|
|
})
|
|
.dropzone({
|
|
accept: '.card',
|
|
ondropmove: event => {
|
|
let target = event.target;
|
|
// TODO: there must be a better way to do this
|
|
let relCursorPos = (event.dragEvent.pageX -
|
|
target.getBoundingClientRect().left) /
|
|
parseInt(window.getComputedStyle(target).width);
|
|
|
|
let oldOffsetLeft = event.relatedTarget.offsetLeft;
|
|
let oldOffsetTop = event.relatedTarget.offsetTop;
|
|
|
|
// move the object in the DOM
|
|
if (relCursorPos < 0.5 || relCursorPos === Infinity) {
|
|
target.parentElement.insertBefore(event.relatedTarget, target);
|
|
}
|
|
else {
|
|
target.parentElement.insertBefore(event.relatedTarget, target.nextSibling);
|
|
}
|
|
|
|
// update position
|
|
dragMoveListener({target: event.relatedTarget,
|
|
dx: oldOffsetLeft - event.relatedTarget.offsetLeft,
|
|
dy: oldOffsetTop - event.relatedTarget.offsetTop});
|
|
|
|
// rebuild source pile
|
|
piles[target.getAttribute('data-pile')] =
|
|
Array.from(target.parentElement.children).map(
|
|
c => c.getAttribute('data-num'));
|
|
}
|
|
})
|
|
.on('tap', event => {
|
|
let target = event.target;
|
|
console.log(`Drawing ${target.getAttribute('data-num')} from ${target.getAttribute('data-pile')}`);
|
|
|
|
let listDiv = target.parentElement;
|
|
// re-parent
|
|
document.querySelector("#card-container").appendChild(target);
|
|
|
|
// rebuild source pile
|
|
piles[target.getAttribute('data-pile')] =
|
|
Array.from(listDiv.children).map(c => c.getAttribute('data-num'));
|
|
|
|
// remove list class
|
|
target.classList.remove('in-list');
|
|
|
|
// fix position
|
|
target.style.position = "fixed";
|
|
|
|
});
|
|
|
|
interact('.card-pile')
|
|
.dropzone({
|
|
accept: '.card:not(.in-pile)',
|
|
ondrop: event => {
|
|
// TODO: fix possible duped zeros
|
|
let pileName = event.target.getAttribute('data-pile');
|
|
let cardNum = event.relatedTarget.getAttribute('data-num');
|
|
if (event.dragEvent.shiftKey) {
|
|
console.log(`Adding ${cardNum} bottom of to ${pileName}`);
|
|
piles[pileName].unshift(cardNum);
|
|
}
|
|
else {
|
|
console.log(`Adding ${cardNum} to ${pileName}`);
|
|
piles[pileName].push(cardNum);
|
|
}
|
|
|
|
// remove from DOM
|
|
event.relatedTarget.parentElement.removeChild(event.relatedTarget);
|
|
|
|
// update deck text
|
|
event.target.innerHTML = `${pileName.toUpperCase()}<br>${piles[pileName].length}/${cardCount}`;
|
|
}
|
|
})
|
|
.draggable({manualStart: true})
|
|
.on('move', event => {
|
|
let interaction = event.interaction;
|
|
let pileName = event.target.getAttribute('data-pile');
|
|
let pile = piles[pileName];
|
|
|
|
// if the pointer was moved while being held down
|
|
// and an interaction hasn't started yet
|
|
// and there are cards in the pile
|
|
if (interaction.pointerIsDown &&
|
|
!interaction.interacting() &&
|
|
pile.length > 0) {
|
|
// draw a new card
|
|
let newCard = makeCard(pile.pop());
|
|
newCard.style.position = "fixed";
|
|
newCard.style.top = event.pageY;
|
|
newCard.style.left = event.pageX;
|
|
// insert the card to the page
|
|
document.querySelector("#card-container").appendChild(newCard);
|
|
|
|
// update deck text
|
|
event.target.innerHTML = `${pileName.toUpperCase()}<br>${pile.length}/${cardCount}`;
|
|
|
|
// start a drag interaction targeting the clone
|
|
interaction.start({name: 'drag'}, cardInteract, newCard);
|
|
}
|
|
})
|
|
.on('hold', event => {
|
|
let pile = piles[event.target.getAttribute('data-pile')];
|
|
let searchBox = document.createElement('input');
|
|
let container = document.createElement('div');
|
|
let cardList = document.createElement('div');
|
|
searchBox.setAttribute('type', 'search');
|
|
searchBox.setAttribute('placeholder', 'Filter');
|
|
searchBox.addEventListener('input', event => {
|
|
let input = event.target.value;
|
|
Array.from(cardList.children).forEach(card => {
|
|
let cardNum = parseInt(card.getAttribute('data-num'));
|
|
let cardData = deckJSON.ContainedObjects.find(c => c.CardID === (cardNum + deckNum * 100));
|
|
card.style.display =
|
|
(cardData.Nickname.toLowerCase().includes(input.toLowerCase()) ||
|
|
cardData.Description.toLowerCase().includes(input.toLowerCase())) ?
|
|
"": "none";
|
|
});
|
|
});
|
|
container.appendChild(searchBox);
|
|
|
|
pile.forEach(cardNum => {
|
|
let newCard = makeCard(cardNum);
|
|
newCard.classList.add('in-list');
|
|
newCard.setAttribute('data-pile', event.target.getAttribute('data-pile'));
|
|
cardList.appendChild(newCard);
|
|
});
|
|
container.appendChild(cardList);
|
|
showModal(container);
|
|
})
|
|
.on('tap', event => {
|
|
shuffle(piles[event.target.getAttribute('data-pile')]);
|
|
event.target.classList.add("shake");
|
|
// reset animation so it can be played again
|
|
event.target.onanimationend = e => {
|
|
event.target.classList.remove("shake");
|
|
};
|
|
});
|
|
|
|
function makeCard(cardNum) {
|
|
// draw a new card
|
|
let card = document.createElement('div');
|
|
card.className = "card";
|
|
|
|
// temporary add so getComputedStyle works on Chrome
|
|
document.body.appendChild(card);
|
|
let style = window.getComputedStyle(card);
|
|
card.setAttribute('data-num', cardNum);
|
|
card.style.backgroundPositionX =
|
|
-(cardNum % deckWidth) * parseInt(style.getPropertyValue("width")) + "px";
|
|
card.style.backgroundPositionY =
|
|
-Math.floor(cardNum/deckWidth) * parseInt(style.getPropertyValue("height")) + "px";
|
|
card.style.backgroundImage = `url(${deckJSON.CustomDeck[deckNum].FaceURL})`;
|
|
card.style.backgroundSize = `${deckWidth * 100}% ${deckHeight * 100}%`;
|
|
document.body.removeChild(card);
|
|
|
|
return card;
|
|
}
|
|
|
|
function showModal(content) {
|
|
let shade = document.createElement('div');
|
|
shade.id = "shade";
|
|
shade.className = "modal";
|
|
shade.addEventListener('click', hideModal);
|
|
document.body.appendChild(shade);
|
|
|
|
let modal = document.createElement('div');
|
|
modal.id = "modal-content";
|
|
modal.className = "modal";
|
|
modal.appendChild(content);
|
|
document.body.appendChild(modal);
|
|
}
|
|
|
|
function hideModal() {
|
|
document.querySelectorAll('.modal').forEach(
|
|
e => e.parentElement.removeChild(e));
|
|
}
|
|
|
|
// Fisher-Yates shuffle from https://bost.ocks.org/mike/shuffle/
|
|
function shuffle(array) {
|
|
let m = array.length, t, i;
|
|
|
|
// While there remain elements to shuffle…
|
|
while (m) {
|
|
// Pick a remaining element…
|
|
i = Math.floor(Math.random() * m--);
|
|
|
|
// And swap it with the current element.
|
|
t = array[m];
|
|
array[m] = array[i];
|
|
array[i] = t;
|
|
}
|
|
|
|
return array;
|
|
}
|
|
|
|
function dragMoveListener (event) {
|
|
let target = event.target;
|
|
|
|
// keep the dragged position in the data-x/data-y attribute
|
|
let x = (parseFloat(target.getAttribute('data-x')) || 0) + event.dx;
|
|
let y = (parseFloat(target.getAttribute('data-y')) || 0) + event.dy;
|
|
let scale = (parseFloat(target.getAttribute('data-scale')) || 1);
|
|
|
|
// translate and scale the element
|
|
target.style.webkitTransform =
|
|
target.style.transform =
|
|
'translate(' + x + 'px, ' + y + 'px) scale(' + scale + ')';
|
|
|
|
// update the posiion attributes
|
|
target.setAttribute('data-x', x);
|
|
target.setAttribute('data-y', y);
|
|
}
|
|
|