import { inject , injectable } from "inversify" ; import type { Deck } from "./Deck" ; import { TYPES } from "../types" ; import { CardDatabase , FindCardBy } from "../card/CardDatabase" ; import { DeckService } from "./DeckService" ; import { DECK_PART_ARR } from "./DeckPart" ; import { HttpService } from "../http/HttpService" ; import { UrlService } from "../http/UrlService" ; import { DefaultDeckPartConfig } from "./DeckPartConfig" ; export interface ImportResult { readonly deck : Deck ; readonly missing : string [ ] ; } interface DeckFile { readonly fileName : string ; readonly fileContent : string ; } @ injectable ( ) export class DeckFileService { readonly #httpService : HttpService ; readonly #cardDatabase : CardDatabase ; readonly #deckService : DeckService ; readonly #urlService : UrlService ; public static readonly DECK_FILE_MIME_TYPE = "text/ydk" ; constructor ( @ inject ( TYPES . HttpService ) httpService , @ inject ( TYPES . CardDatabase ) cardDatabase , @ inject ( TYPES . DeckService ) deckService , @ inject ( TYPES . UrlService ) urlService ) { this . #httpService = httpService ; this . #deckService = deckService ; this . #cardDatabase = cardDatabase ; this . #urlService = urlService ; } param currentUrl param remoteUrl throws return async fromRemoteFile ( currentUrl , remoteUrl ) { if ( ! this . #urlService . hasSameOrigin ( currentUrl , remoteUrl ) ) { throw new Error ( "Decks can only be loaded from the same origin." ) ; } const fileName = this . #urlService . getFileName ( remoteUrl ) ; const response = await this . #httpService . get < string > ( remoteUrl , { responseType : "text" , timeout : 5000 , } ) ; return this . fromFile ( { fileName , fileContent : response . data , } ) ; } param deckFile return fromFile ( deckFile ) { const missing = [ ] ; const deck = this . #deckService . createEmptyDeck ( ) ; const lines = deckFile . fileContent . split ( "\n" ) . map ( ( line ) => line . trim ( ) ) . filter ( ( line ) => line . length > 0 ) ; let currentDeckPart = null ; for ( const line of lines ) { const foundDeckPart = DECK_PART_ARR . find ( ( part ) => DefaultDeckPartConfig [ part ] . indicator === line ) ; if ( foundDeckPart != null ) { currentDeckPart = foundDeckPart ; continue ; } if ( currentDeckPart != null ) { const passcode = line . replace ( /^0+/ , "" ) ; if ( ! this . #cardDatabase . hasCard ( passcode , FindCardBy . PASSCODE ) ) { missing . push ( passcode ) ; } else { const card = this . #cardDatabase . getCard ( passcode , FindCardBy . PASSCODE ) ! ; deck . parts [ currentDeckPart ] . push ( card ) ; } } } deck . name = deckFile . fileName . replace ( ".ydk" , "" ) ; return { deck , missing , } ; } param deck return toFile ( deck ) { const fileLines = [ ] ; for ( const deckPart of DECK_PART_ARR ) { const deckPartCards = deck . parts [ deckPart ] ; fileLines . push ( DefaultDeckPartConfig [ deckPart ] . indicator ) ; fileLines . push ( ... deckPartCards . map ( ( card ) => card . passcode ) ) ; fileLines . push ( "" ) ; } return { fileName : `${ deck . name ?? "Unnamed" }.ydk` , fileContent : fileLines . join ( "\n" ) , } ; } }