UNPKG

skynovel

Version:
341 lines (299 loc) 11.5 kB
/* ***** BEGIN LICENSE BLOCK ***** Copyright (c) 2018-2020 Famibee (famibee.blog38.fc2.com) This software is released under the MIT License. http://opensource.org/licenses/mit-license.php ** ***** END LICENSE BLOCK ***** */ import { SysBase } from "./SysBase"; import {CmnLib, getDateStr, argChk_Boolean} from './CmnLib'; import {IConfig, IHTag, IVariable, IMain, HArg, ITag, IFn2Path, IData4Vari} from './CmnInterface'; import {Main} from './Main'; const strLocal = require('store'); import {Application} from 'pixi.js'; import 'devtools-detect'; export class SysWeb extends SysBase { constructor(hPlg = {}, arg = {cur: 'prj/', crypto: false, dip: ''}) { super(hPlg, arg); const idxCur = arg.cur.lastIndexOf('/', arg.cur.length -2); this.def_prj = arg.cur.slice(idxCur +1, -1); // (idxCur === -1) // ? arg.cur.slice(0, -1) // : arg.cur.slice(idxCur +1, -1); window.onload = ()=> { for (const v of document.querySelectorAll('[data-prj]')) { v.addEventListener('click', ()=> { const elm = v.attributes.getNamedItem('data-prj'); if (! elm) return; const prj = elm.value; if (this.now_prj !== prj) this.run(prj); }, {passive: true}); } for (const v of document.querySelectorAll('[data-reload]')) { v.addEventListener('click', ()=> this.run(this.now_prj), {passive: true}); } if (arg.dip) CmnLib.hDip = JSON.parse(arg.dip); const sp = new URLSearchParams(location.search); const dip = sp.get('dip'); // ディップスイッチ if (dip) CmnLib.hDip = {...CmnLib.hDip, ...JSON.parse(dip)}; if (! argChk_Boolean(CmnLib.hDip, 'oninit_run', true)) return; this.run(sp.get('cur') ?? ''); } if ('webkitFullscreenEnabled' in document) this.tgl_full_scr = o=> this.regEvt_FullScr( o, //Chrome15+, Safari5.1+, Opera15+ 'webkitRequestFullscreen', 'webkitCancelFullScreen', 'webkitFullscreenElement' ); else if ('mozFullScreenEnabled' in document) this.tgl_full_scr = o=> this.regEvt_FullScr( o, //FF10+ 'mozRequestFullScreen', 'mozCancelFullScreen', 'mozFullScreenElement' ); else if ('msFullscreenEnabled' in document) this.tgl_full_scr = o=> this.regEvt_FullScr( o, //IE11+ 'msRequestFullscreen', 'msExitFullscreen', 'msFullscreenElement' ); else if (document['fullscreenEnabled']) this.tgl_full_scr = o=> this.regEvt_FullScr( o, // HTML5 Fullscreen API仕様 'requestFullscreen', 'exitFullscreen', 'fullscreenElement' ); } private def_prj = 'prj'; private readonly run = async (prj: string)=> { if (this.main) { const ms_late = 10; // NOTE: ギャラリーでのえもふり/Live 2D用・魔法数字 this.main.destroy(ms_late); await new Promise(r=> setTimeout(r, ms_late)); } this.now_prj = prj || this.def_prj; const idxEnd = this.arg.cur.lastIndexOf('/', this.arg.cur.length -2) +1; const idxStart = this.arg.cur.lastIndexOf('/', idxEnd -2) +1; this.arg.cur = location.href.slice(0, location.href.lastIndexOf('/') +1) + (idxEnd === 0 ?'' :this.arg.cur.slice(idxStart, idxEnd)) + this.now_prj +'/'; this.main = new Main(this); } stop() { if (! this.main) return; this.main.destroy(); this.main = null; } private now_prj = ':'; private main: Main | null; loadPathAndVal(hPathFn2Exts: IFn2Path, fncLoaded: ()=> void, cfg: IConfig): void { super.loadPathAndVal(hPathFn2Exts, fncLoaded, cfg); (async ()=> { const fn = this.arg.cur +'path.json'; const res = await fetch(fn); if (! res.ok) throw Error(res.statusText); const mes = await res.text(); const json = JSON.parse(await this.pre(fn, mes)); for (const nm in json) { const h = hPathFn2Exts[nm] = json[nm]; for (const ext in h) if (ext !== ':cnt') h[ext] = this.arg.cur + h[ext]; } fncLoaded(); // ここでnew Variable、clearsysvar()、次にinitVal() })(); } initVal(data: IData4Vari, hTmp: any, comp: (data: IData4Vari)=> void) { // システム情報 const hn = document.location.hostname; hTmp['const.sn.isDebugger'] = (hn === 'localhost' || hn ==='127.0.0.1'); this.val.defTmp('const.sn.displayState', ()=> this.isFullScr()); const ns = this.cfg.getNs(); this.flush = this.crypto ? ()=> { strLocal.set(ns +'sys_', String(this.enc(JSON.stringify(this.data.sys)))); strLocal.set(ns +'mark_', String(this.enc(JSON.stringify(this.data.mark)))); strLocal.set(ns +'kidoku_', String(this.enc(JSON.stringify(this.data.kidoku)))); } : ()=> { strLocal.set(ns +'sys', this.data.sys); strLocal.set(ns +'mark', this.data.mark); strLocal.set(ns +'kidoku', this.data.kidoku); }; const nm = ns +(this.arg.crypto ?'sys_' :'sys'); if (hTmp['const.sn.isFirstBoot'] = (strLocal.get(nm) === undefined)) { // データがない(初回起動)場合の処理 this.data.sys = data.sys; this.data.mark = data.mark; this.data.kidoku = data.kidoku; this.flush(); // 初期化なのでここのみ必要 comp(this.data); return; } // データがある場合の処理 if (! this.crypto) { this.data.sys = strLocal.get(ns +'sys'); this.data.mark = strLocal.get(ns +'mark'); this.data.kidoku = strLocal.get(ns +'kidoku'); comp(this.data); return; } (async ()=> { let mes = ''; try { mes = 'sys'; // tst sys this.data.sys = JSON.parse( await this.pre('json', strLocal.get(ns +'sys_')) ); mes += Number(this.val.getVal('sys:TextLayer.Back.Alpha', 1)); mes = 'mark'; // tst mark this.data.mark = JSON.parse( await this.pre('json', strLocal.get(ns +'mark_')) ); mes = 'kidoku'; // tst kidoku this.data.kidoku = JSON.parse( await this.pre('json', strLocal.get(ns +'kidoku_')) ); } catch (e) { console.error(`セーブデータ(${mes})が壊れています。一度クリアする必要があります %o`, e); } comp(this.data); })(); } init(hTag: IHTag, appPixi: Application, val: IVariable, main: IMain): void { super.init(hTag, appPixi, val, main); if (! this.cfg.oCfg.debug.devtool) window.addEventListener('devtoolschange', e=> { if (! e.detail.isOpen) return; console.error(`DevToolは禁止されています。許可する場合は【プロジェクト設定】の【devtool】をONに。`); main.destroy(); }, {once: true, passive: true}); } // プレイデータをエクスポート protected readonly _export: ITag = ()=> { const s = JSON.stringify({ 'sys': this.data.sys, 'mark': this.data.mark, 'kidoku': this.data.kidoku, }); const s2 = this.crypto ?String(this.enc(s)) :s; const blob = new Blob([s2], {'type':'text/json'}); const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = (this.crypto ?'' :'no_crypto_') + this.cfg.getNs() + getDateStr('-', '_', '') +'.swpd'; a.click(); if (CmnLib.debugLog) console.log('プレイデータをエクスポートしました'); setTimeout(()=> this.fire('sn:exported', new Event('click')), 10); return false; } // プレイデータをインポート protected readonly _import: ITag = ()=> { new Promise((rs, rj)=> { const inp = document.createElement('input'); inp.type = 'file'; inp.accept = '.swpd, text/plain'; inp.onchange = (e: any)=> { const file = e?.target?.files?.[0]; if (file) rs(file); else rj(); }; inp.click(); }) .then((file: any)=> new Promise(rs=> { const rd = new FileReader(); rd.readAsText(file); rd.onload = ()=> rs(rd.result); })) .then(async (s: string)=> { const o = JSON.parse(this.crypto ?await this.pre('json', s) :s); if (! o.sys || ! o.mark || ! o.kidoku) throw new Error('異常なプレイデータです'); if (o.sys[SysBase.VALNM_CFG_NS] !== this.cfg.oCfg.save_ns) { console.error(`別のゲーム【プロジェクト名=${o.sys[SysBase.VALNM_CFG_NS]}】のプレイデータです`); return; } this.data.sys = o.sys; this.data.mark = o.mark; this.data.kidoku = o.kidoku; this.flush(); this.val.updateData(o); if (CmnLib.debugLog) console.log('プレイデータをインポートしました'); this.fire('sn:imported', new Event('click')); }) .catch(e=> console.error(`異常なプレイデータです ${e.message}`)); return false; } // URLを開く protected readonly navigate_to: ITag = hArg=> { const url = hArg.url; if (! url) throw '[navigate_to] urlは必須です'; // window.open(url); // 近年セキュリティ的に効かない window.open(url, '_blank'); // 効くがポップアップブロック // location.href = url; // これは効くがSKYNovelが終了してしまう return false; } // タイトル指定 protected titleSub(txt: string) { document.title = txt; for (const v of document.querySelectorAll('[data-title]')) v.textContent = txt; } // 全画面状態切替(タグではない手段で提供) private readonly isFullScr = ()=> ('mozFullScreen' in document) ? document['mozFullScreen'] : document.fullscreen; private regEvt_FullScr(hArg: HArg, go_fnc_name: string, exit_fnc_name: string, get_fnc_name: string): boolean { const elm: any = document.body; const doc: any = document; if (! hArg.key) { if (doc[get_fnc_name]) doc[exit_fnc_name](); else elm[go_fnc_name](); this.resizeFramesWork(); return false; } const key = hArg.key.toLowerCase(); doc.addEventListener('keydown', (e: KeyboardEvent)=> { const key2 = (e.altKey ?(e.key === 'Alt' ?'' :'alt+') :'') + (e.ctrlKey ?(e.key === 'Control' ?'' :'ctrl+') :'') + (e.shiftKey ?(e.key === 'Shift' ?'' :'shift+') :'') + e.key.toLowerCase(); if (key2 !== key) return; e.stopPropagation(); if (doc[get_fnc_name]) doc[exit_fnc_name](); else elm[go_fnc_name](); this.resizeFramesWork(); }, {passive: true}); return false; } private resizeFramesWork() { const is_fs = this.isFullScr(); //this.reso4frame = is_fs ?screen.width /CmnLib.stageW :1; // 全画面を使う const ratioWidth = screen.width / CmnLib.stageW; const ratioHeight = screen.height / CmnLib.stageH; const ratio = (ratioWidth < ratioHeight) ?ratioWidth :ratioHeight; this.reso4frame = is_fs ?1 :ratio; // document.body.clientWidth が時々正しい値を返さないのでscreen.widthで this.ofsLeft4frm = is_fs ?0 :(screen.width -CmnLib.stageW *this.reso4frame *CmnLib.cvsScale) /2; this.ofsTop4frm = is_fs ?0 :(screen.height -CmnLib.stageH *this.reso4frame *CmnLib.cvsScale) /2; this.resizeFrames(); } readonly readFile = (path: string, callback: (err: NodeJS.ErrnoException | null, data: Buffer) => void)=> { fetch(path) //fetch(path, {mode: 'same-origin'}) .then(res=> { if (! res.ok) throw Error(res.statusText); callback(null, Buffer.from(res.text())); }) .catch(e=> console.error('Error:', e)); }; readonly savePic = (fn: string, data_url: string)=> { const a = document.createElement('a'); a.href = data_url; a.download = fn; a.click(); if (CmnLib.debugLog) console.log('画像ファイルをダウンロードします'); }; private readonly hAppendFile: {[path: string]: string} = {}; readonly appendFile = (path: string, data: any, _callback: (err: NodeJS.ErrnoException)=> void)=> { const txt = (this.hAppendFile[path] ?? '') + data; this.hAppendFile[path] = txt; const blob = new Blob([txt], {'type':'text/json'}); const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = path; a.click(); }; }