Skip to content

Commit

Permalink
Added cube overview tab
Browse files Browse the repository at this point in the history
  • Loading branch information
dsoskey committed May 28, 2024
1 parent 58d60bd commit e6836f7
Show file tree
Hide file tree
Showing 36 changed files with 2,478 additions and 459 deletions.
1,548 changes: 1,326 additions & 222 deletions package-lock.json

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,12 +66,17 @@
"webpack-dev-server": "^4.3.1"
},
"dependencies": {
"@floating-ui/react": "^0.26.16",
"axios": "^1.6.0",
"date-fns": "^2.30.0",
"dexie": "^3.2.2",
"dexie-react-hooks": "^1.1.6",
"lodash": "^4.17.21",
"mana-font": "^1.15.4",
"micromark-factory-space": "^2.0.0",
"micromark-util-character": "^2.1.0",
"micromark-util-chunked": "^2.0.0",
"micromark-util-symbol": "^2.0.0",
"moo": "^0.5.2",
"mtgql": "^1.3.4",
"nearley": "^2.20.1",
Expand All @@ -87,8 +92,10 @@
"react-markdown": "^8.0.7",
"react-router-dom": "^6.16.0",
"remark": "^15.0.1",
"remark-breaks": "^4.0.0",
"remark-gfm": "^3.0.1",
"scryfall-sdk": "^4.2.0",
"unist-util-visit": "^5.0.0",
"uuid": "^9.0.0"
}
}
20 changes: 17 additions & 3 deletions src/api/cubeartisan/cubeListImport.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,25 @@
import { CoverImage } from "../local/db";
import { parseISO } from 'date-fns'

export interface CubeArtisanCardIds {
export interface CubeArtisanImportData {
oracleIds: string[]
printIds: string[]
name: string,
description: string,
cover_image: CoverImage
created_by: string
last_source_update: Date
}
export async function importCubeArtisan(cubeId: string): Promise<CubeArtisanCardIds> {
export async function importCubeArtisan(cubeId: string): Promise<CubeArtisanImportData> {
const response = await fetch(`https://cubeartisan.net/cube/${cubeId}/export/json`)
const cube = await response.json();
const printIds = cube.cards.map(it => it.cardID)
return { printIds, oracleIds: cube.cardOracles }
return { printIds, oracleIds: cube.cardOracles, name: cube.name, description: cube.description,
cover_image: {
uri: cube.image_uri,
artist: cube.image_artist,
},
created_by: cube.owner_name,
last_source_update: parseISO(cube.last_updated),
}
}
32 changes: 18 additions & 14 deletions src/api/cubecobra/cubeImportWorker.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { importCubeCobra } from './cubeListImport'
import {
CubeDefinition, ExternalCubeSource,
ExternalCubeSource,
CubeCard
} from 'mtgql'
import { cogDB as cogDBClient, cogDB } from '../local/db'
import { importCubeArtisan } from '../cubeartisan/cubeListImport'
import { CogCubeDefinition, cogDB as cogDBClient, cogDB } from '../local/db'
import { CubeArtisanImportData, importCubeArtisan } from '../cubeartisan/cubeListImport'
import * as Scryfall from 'scryfall-sdk'

self.onmessage = (_event) => {
Expand Down Expand Up @@ -33,22 +33,25 @@ async function searchCubes({ cubeIds, source }: SearchInput) {
async function searchCubeCobra(cubeIds: string[]) {
const last_updated = new Date()
const missingCubes: string[] = []
const cubeDefinitions: CubeDefinition[] = [];
const cubeDefinitions: CogCubeDefinition[] = [];
const results = await Promise.allSettled(cubeIds.map(importCubeCobra))

for (let i = 0; i < results.length; i++) {
const cubeId = cubeIds[i]
const result = results[i]

if (result.status === "fulfilled") {
const cards = result.value;
const { cards, name, description, last_source_update, cover_image, created_by } = result.value;
cubeDefinitions.push({
key: cubeId,
cards,
cards, created_by,
name, description,
cover_image,
oracle_ids: cards.map(it => it.oracle_id),
print_ids: cards.map(it => it.print_id),
source: "cubecobra",
last_updated,
last_source_update,
})
} else {
missingCubes.push(cubeId)
Expand All @@ -70,7 +73,7 @@ async function searchCubeCobra(cubeIds: string[]) {
}

async function searchCubeArtisan(cubeIds: string[]) {
const foundCubes: { [cubeId: string]: string[] } = {}
const foundCubes: { [cubeId: string]: CubeArtisanImportData } = {}
const foundOracleIds = new Set<string>()
const results = await Promise.allSettled(cubeIds.map(importCubeArtisan))
const missingCubes: string[] = []
Expand All @@ -80,7 +83,7 @@ async function searchCubeArtisan(cubeIds: string[]) {
const result = results[i]

if (result.status === "fulfilled") {
foundCubes[cubeId] = result.value.printIds
foundCubes[cubeId] = result.value
for (const line of result.value.oracleIds) {
foundOracleIds.add(line)
}
Expand All @@ -97,14 +100,15 @@ async function searchCubeArtisan(cubeIds: string[]) {
const printToOracleId = await generatePrintToOracleId(foundCubes, foundOracleIds)

const last_updated = new Date()
const cubeDefinitions: CubeDefinition[] = Object.entries(foundCubes)
.map(([key, printIds]) => {
const cards: CubeCard[] = printIds.map(it => ({ oracle_id: printToOracleId[it], print_id: it }))
const cubeDefinitions: CogCubeDefinition[] = Object.entries(foundCubes)
.map(([key, cubeInfo]) => {
const cards: CubeCard[] = cubeInfo.printIds.map(it => ({ oracle_id: printToOracleId[it], print_id: it }))
return {
key,
...cubeInfo,
cards,
oracle_ids: cards.map(it => it.oracle_id),
print_ids: printIds,
print_ids: cubeInfo.printIds,
source: "cubeartisan",
last_updated,
}
Expand All @@ -120,7 +124,7 @@ async function searchCubeArtisan(cubeIds: string[]) {
}

async function generatePrintToOracleId(
foundCubes: { [cubeId: string]: string[] },
foundCubes: { [cubeId: string]: CubeArtisanImportData },
foundOracleIds: Set<string>
): Promise<{ [key: string]: string }> {
const result: { [key: string]: string } = {}
Expand All @@ -140,7 +144,7 @@ async function generatePrintToOracleId(
}

const toCheckScryfall = Array.from(
new Set(Object.values(foundCubes).flat())
new Set(Object.values(foundCubes).flatMap(it => it.printIds))
).map((id => Scryfall.CardIdentifier.byId(id)));
const scryfallCards = await Scryfall.Cards.collection(...toCheckScryfall).waitForAll();

Expand Down
16 changes: 13 additions & 3 deletions src/api/cubecobra/cubeListImport.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { CubeCard } from 'mtgql'
import { CogCubeDefinition } from '../local/db'


const URL_BASE = "https://cubecobra.com/cube/api/cubeJSON/"
export async function importCubeCobra(cubeId: string): Promise<CubeCard[]> {
export async function importCubeCobra(cubeId: string): Promise<Partial<CogCubeDefinition>> {
const response = await fetch(`${URL_BASE}${cubeId}`)

const json = await response.json();
Expand All @@ -12,5 +12,15 @@ export async function importCubeCobra(cubeId: string): Promise<CubeCard[]> {

}))
console.debug(json);
return cards;
return {
name: json.name,
cover_image: {
uri: json.image.uri,
artist: json.image.artist,
},
created_by: json.owner.username,
last_source_update: new Date(json.date),
description: json.description,
cards,
};
}
55 changes: 52 additions & 3 deletions src/api/local/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,18 @@ import { Project } from './types/project'
import { RunStrategy } from '../queryRunnerCommon'
import { ThemeDefinition } from './types/theme'

export interface CoverImage {
uri: string
artist: string
}
export interface CogCubeDefinition extends CubeDefinition {
name: string
created_by: string
description: string
cover_image?: CoverImage
last_source_update: Date
}

export interface Collection {
id: string // used for NormedCard.collectionId
name: string
Expand Down Expand Up @@ -55,9 +67,8 @@ export class TypedDexie extends Dexie implements DataProvider {
collection!: Table<Collection>

card!: Table<NormedCard>

cube!: Table<CubeDefinition>

customCard!: Table<NormedCard>
cube!: Table<CogCubeDefinition>
oracleTag!: Table<OracleTag>
illustrationTag!: Table<IllustrationTag>
block!: Table<Block>
Expand All @@ -78,6 +89,23 @@ export class TypedDexie extends Dexie implements DataProvider {
return it
})

getCardByNameId = async (name: string, id: string) => {
let res = await cogDB.card.where("name").equals(name).toArray();
if (res.length === 0)
res = await cogDB.card.where("name").startsWith(`${name} /`).toArray();
if (res.length === 0)
res = await cogDB.card.where("name").equalsIgnoreCase(name).toArray();
if (res.length === 0)
res = await cogDB.card.where("name").startsWithIgnoreCase(`${name} /`).toArray();
if (res.length === 0) return undefined;
const card = res.length > 1
? res.find(card => card.printings.find(it => it.id === id)) ?? res[0]
: res[0];

const printing = card.printings.find(it => it.id === id) ?? card.printings[0]
return { ...card, ...printing }
}

constructor() {
super('cogwork-librarian')

Expand Down Expand Up @@ -182,6 +210,27 @@ export class TypedDexie extends Dexie implements DataProvider {
this.version(13).stores({
theme: 'name, createdAt, updatedAt'
})

this.version(14).stores({
customCard: 'oracle_id, name, collectionId',
})

this.version(15).stores({
cube: 'key, name',
}).upgrade(trans => {
return trans.table("cube").toCollection()
.modify(c => {
if (c.name === undefined) {
c.name = c.key
}
if (c.description === undefined) {
c.description = ""
}
if (c.created_by === undefined) {
c.created_by = ""
}
})
})
}
}
export const cogDB = new TypedDexie()
Expand Down
20 changes: 10 additions & 10 deletions src/api/local/dbWorker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@ self.onmessage = (_event) => {
switch (event.type) {
case 'load': {
const { filter } = event.data;
loadDb(filter).catch(e => postMessage({ type: 'error', data: e.toString() }))
sendCardDBToMemory(filter).catch(e => postMessage({ type: 'error', data: e.toString() }))
break;
}
case 'init': { // idea: this event data has manifest OR type name to fetch
const { bulkType, targets, filter } = event.data
initDb(bulkType ?? 'default_cards', targets, filter)
initOfficialDB(bulkType ?? 'default_cards', targets, filter)
.catch(e => postMessage({ type: 'error', data: e.toString() }))
break;
}
Expand All @@ -41,23 +41,21 @@ self.onmessage = (_event) => {
}
}

async function loadDb(filter?: string) {
async function sendCardDBToMemory(filter?: string) {
let index = 0
let found = 0;
console.debug("pulling collection")
const manifest = await cogDB.collection.get("the_one")
postMessage({ type: 'manifest', data: manifest })

const count = await cogDB.card.count()
const count = (await cogDB.card.count()) + (await cogDB.customCard.count())
postMessage({ type: 'count', data: count })
const node: FilterNode = filter ?
await QueryRunner.parseFilterNode(MQLParser, new CachingFilterProvider(cogDB), filter)
.unwrapOr(identityNode()) :
identityNode();

console.log(node)

await cogDB.card.each(card => {
const processCard = (card: NormedCard) => {
index++
if (node.filterFunc(card)) {
const printings = [];
Expand All @@ -71,12 +69,15 @@ async function loadDb(filter?: string) {
postMessage({ type: 'card', data: { card: {...card, printings}, index } })
}
}
})
}
await cogDB.card.each(processCard)
await cogDB.customCard.each(processCard)

postMessage({ type: 'count', data: found })
postMessage({ type: 'end' })
}

async function initDb(type: BulkDataType, targets: ImportTarget[], filter?: string) {
async function initOfficialDB(type: BulkDataType, targets: ImportTarget[], filter?: string) {
if (targets.length === 0) {
throw Error("No targets specified!")
}
Expand Down Expand Up @@ -171,5 +172,4 @@ async function loadBlocks() {
postMessage({ type: "blocks-downloaded", data: sets.length })
await cogDB.block.bulkPut(sets)
postMessage({ type: "blocks-end" })

}
2 changes: 1 addition & 1 deletion src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export const App = () => {
{pathname !== "/" && <DefaultLayout>
<Routes>
<Route path="/data/cube/*" element={<CubeRedirect />} />
<Route path="/cube/:key" element={<CubeView />} />
<Route path="/cube/:key/*" element={<CubeView />} />
<Route path='/data/card' element={<CardDataView />}/>
<Route path='/cube' element={<CubeDataView />}/>
<Route
Expand Down
Loading

0 comments on commit e6836f7

Please sign in to comment.