Skip to content

Commit

Permalink
Implement new scanner behavior
Browse files Browse the repository at this point in the history
  • Loading branch information
Daniils Petrovs committed Jun 22, 2024
1 parent 6d184ab commit b1d670f
Show file tree
Hide file tree
Showing 7 changed files with 69 additions and 24 deletions.
2 changes: 2 additions & 0 deletions src/const.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ export const apiServerURL = dev
? 'http://localhost:4000/api/json'
: 'https://api.hololivefanbooth.com/api/json';

export const minStampCountRequired = 10;

export const holomemGachaPool = [
{
id: 'UC060r4zABV18vcahAWR1n7w',
Expand Down
4 changes: 4 additions & 0 deletions src/crypto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,7 @@ export function calculateTokenChecksum(tokens: string[]): string {
shaObj.update(tokens.join(''));
return shaObj.getHash('HEX');
}

export function tokenHash(token: string): string {
return sha256(token);
}
4 changes: 2 additions & 2 deletions src/lib/components/Stamp.svelte
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
<script lang="ts">
import { fade } from 'svelte/transition';
import placeholderImg from '$lib/assets/watamesmug.jpg';
export let id: number;
export let name: string;
export let collected = false;
export let navURL = `/partner/${id}`;
const PLACEHOLDER_STAMP_IMG =
'https://4.bp.blogspot.com/-ZMzPgyP64Zg/WFuJyby0eDI/AAAAAAABAmk/I_UtFowN0ecaQiqgYGpTsKGTqFzMLESdwCLcB/s800/nenga_hanko_kingashinnen_maru.png';
const PLACEHOLDER_STAMP_IMG = placeholderImg;
export let img = PLACEHOLDER_STAMP_IMG;
Expand Down
16 changes: 10 additions & 6 deletions src/lib/components/StampSheet.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@
import { fade, fly } from 'svelte/transition';
import { cubicOut } from 'svelte/easing';
import StampComponent from '$lib/components/Stamp.svelte';
import type {Tables} from '$lib/database.types';
import type { Tables } from '$lib/database.types';
import { onMount } from 'svelte';
import HolomemGacha from './HolomemGacha.svelte';
import RoundScanButton from './RoundScanButton.svelte';
import { collectedStamps } from '$lib/stores/stamps';
import { get } from 'svelte/store';
import { tokenHash } from '../../crypto';
export let stamps: Tables<'stamps'>[] = [];
Expand All @@ -22,16 +23,18 @@
let isQuestCompleted = false; // Can only be true if all stamps were collected
function isAllStampsCollected() {
// TODO: Support minimum collected stamps required
return stamps.every(isStampCollected);
}
const delay = 500; // synchronized fade in delay
const minTouchTime = 1000; // minimum touch time in milliseconds, how long the stub of the stamp sheet should be touched
onMount(() => {
isStampCollected = function (stamp: Tables<'stamps'>) {
// return localStorage.getItem(stamp.hash) !== null;
return get(collectedStamps)[stamp.hash];
isStampCollected = function (stamp: Tables<'stamps'>): boolean {
const collectedStampsObj = get(collectedStamps) as { [key: string]: boolean };
const stampHash = tokenHash(stamp.id);
return collectedStampsObj?.[stampHash];
};
tearStampSheet = function () {
Expand Down Expand Up @@ -83,6 +86,7 @@
}
</script>

<!-- MARK: Component body -->
<div
class="mx-8 my-4 mb-8 divide-y-2 divide-dashed divide-slate-900 rounded-xl bg-slate-100"
out:fade|global
Expand Down Expand Up @@ -119,11 +123,11 @@
? { y: 20, duration: 1200, easing: cubicOut }
: {}}
>
{#each stamps as stamp}
{#each stamps as stamp, i}
<StampComponent
name={stamp.name}
collected={isStampCollected(stamp)}
id={stamp.id}
id={i}
img={stamp.image_url || undefined}
/>
{/each}
Expand Down
13 changes: 13 additions & 0 deletions src/lib/stores/stamps.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,17 @@
import { persisted } from 'svelte-persisted-store';
import { get } from 'svelte/store';
import { tokenHash } from '../../crypto';

export const collectedStamps = persisted('collected-stamps', {});
export const expectedStamps = persisted('expected-stamps', {});

export function getExpectedStampHashes() {
return Object.keys(get(expectedStamps));
}

export function saveStamp(token: string) {
const newCollectedStamps = { ...get(collectedStamps) };
const hash = tokenHash(token);
newCollectedStamps[hash] = token;
collectedStamps.set(newCollectedStamps);
}
9 changes: 5 additions & 4 deletions src/routes/+layout.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,20 @@
import Navigation from '$lib/components/Navigation.svelte';
import Toast from '$lib/components/Toast.svelte';
import { onMount } from 'svelte';
import { generateNickname } from 'hololive-nick-gen';
import { dev } from '$app/environment';
import { saveStamp, collectedStamps } from '$lib/stores/stamps';
onMount(async () => {
// Initialize the stores with the nickname and user token found locally
// If first time using the app, generate a new nickname
$userToken = localStorage.getItem('userToken');
$userToken = localStorage.getItem('userToken'); // FIXME: Replace with persisted store
// Bind stuff to window for debugging
if (dev || localStorage.getItem('debug') === 'true') {
Object.assign(window, {
setToast: setToast
setToast: setToast,
saveStamp: saveStamp,
collectedStamps: collectedStamps,
});
}
});
Expand Down
45 changes: 33 additions & 12 deletions src/routes/scanner/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,21 @@
import QrScanner from 'qr-scanner';
import { sha256 } from '../../crypto';
import { tokenHash } from '../../crypto';
import { TOAST_TYPE, SCANNER_STATE, type ScannerState } from '../../custom';
import { Icon } from '@steeze-ui/svelte-icon';
import { Ticket, QrCode, StopCircle } from '@steeze-ui/heroicons';
import { fade } from 'svelte/transition';
import { get } from 'svelte/store';
import { expectedStamps } from '$lib/stores/stamps';
import { getExpectedStampHashes, saveStamp } from '$lib/stores/stamps';
import { setToast } from '$lib/stores/toasts';
import { minStampCountRequired } from '../../const';
let state: ScannerState = 'STOPPED';
let videoElem: HTMLVideoElement;
let qrScanner: QrScanner;
let token = '';
const expectedHashes = Object.values(get(expectedStamps)).map((stamp) => stamp.hash);
let collectedStampCount = function () {
return 0;
Expand All @@ -40,14 +39,25 @@
}
}
// Handle scan result
function onResult(result: QrScanner.ScanResult) {
token = result.data;
try {
token = parseTokenFromScan(result.data);
console.log('Scanned token:', token)
} catch (e) {
setToast({
type: TOAST_TYPE.ERROR,
message: 'Invalid QR code!'
});
return;
}
transitionState();
let hash = sha256(token);
let hash = tokenHash(token);
if (expectedHashes.includes(hash)) {
if (getExpectedStampHashes().includes(hash)) {
// Scan success
localStorage.setItem(hash, token);
saveStamp(token);
let collectedStamps = collectedStampCount();
Expand All @@ -56,7 +66,7 @@
message: 'Stamp Saved!'
});
if (collectedStamps == 10) {
if (collectedStamps == minStampCountRequired) {
setTimeout(() => {
setToast({
type: TOAST_TYPE.SUCCESS,
Expand All @@ -72,6 +82,16 @@
}
}
function parseTokenFromScan(data: string): string {
const url = new URL(data);
if (url.pathname.startsWith('/sc/')) {
const token = url.pathname.substring(4);
return token;
} else {
throw new Error('Invalid QR code');
}
}
onMount(() => {
videoElem = document.querySelector('video') as HTMLVideoElement;
qrScanner = new QrScanner(videoElem, onResult, {
Expand All @@ -83,9 +103,10 @@
videoElem.setAttribute('autoplay', 'true');
videoElem.setAttribute('muted', 'true');
collectedStampCount = () => {
return Object.keys(localStorage).filter((key) => expectedHashes.includes(key)).length;
};
// TODO: Implement collectedStampCount
// collectedStampCount = () => {
// return Object.keys(localStorage).filter((key) => expectedHashes.includes(key)).length;
// };
});
onDestroy(() => {
Expand Down

0 comments on commit b1d670f

Please sign in to comment.