2016-12-16 01:21:51 +00:00
2018-07-04 02:14:07 +00:00
// 8bitworkshop IDE user interface
2017-01-14 02:31:04 +00:00
2021-07-20 15:40:21 +00:00
import * as localforage from "localforage" ;
2021-08-04 17:00:10 +00:00
import { CodeProject , createNewPersistentStore , LocalForageFilesystem , OverlayFilesystem , ProjectFilesystem , WebPresetsFileSystem } from "./project" ;
2021-08-08 18:40:19 +00:00
import { WorkerResult , WorkerOutputResult , WorkerError , FileData , WorkerErrorResult } from "../common/workertypes" ;
2018-07-08 03:10:51 +00:00
import { ProjectWindows } from "./windows" ;
2021-08-06 13:50:00 +00:00
import { Platform , Preset , DebugSymbols , DebugEvalCondition , isDebuggable , EmuState } from "../common/baseplatform" ;
2021-08-15 15:10:01 +00:00
import { PLATFORMS , EmuHalt } from "../common/emu" ;
import { Toolbar } from "../common/toolbar" ;
2021-08-06 13:50:00 +00:00
import { getFilenameForPath , getFilenamePrefix , highlightDifferences , byteArrayToString , compressLZG , stringToByteArray ,
2021-08-04 17:00:10 +00:00
byteArrayToUTF8 , isProbablyBinary , getWithBinary , getBasePlatform , getRootBasePlatform , hex , loadScript , decodeQueryString , parseBool } from "../common/util" ;
2019-10-26 01:55:50 +00:00
import { StateRecorderImpl } from "../common/recorder" ;
2021-08-06 13:50:00 +00:00
import { GHSession , GithubService , getRepos , parseGithubURL } from "./services" ;
2021-08-01 18:03:50 +00:00
import Split = require ( 'split.js' ) ;
import { importPlatform } from "../platform/_index" ;
2021-08-04 22:14:26 +00:00
import { DisassemblerView , ListingView , SourceEditor } from "./views/editors" ;
import { AddressHeatMapView , BinaryFileView , MemoryMapView , MemoryView , ProbeLogView , ProbeSymbolView , RasterPCHeatMapView , ScanlineIOView , VRAMMemoryView } from "./views/debugviews" ;
import { AssetEditorView } from "./views/asseteditor" ;
import { isMobileDevice } from "./views/baseviews" ;
import { CallStackView , DebugBrowserView } from "./views/treeviews" ;
2021-08-06 16:51:40 +00:00
import { saveAs } from "file-saver" ;
2018-07-08 03:10:51 +00:00
// external libs (TODO)
2021-09-16 02:53:38 +00:00
declare var Tour , GIF , Octokat ;
2019-05-17 19:45:19 +00:00
declare var ga ;
2021-08-01 18:03:50 +00:00
declare var $ : JQueryStatic ; // use browser jquery
2017-01-14 05:47:26 +00:00
2021-08-04 17:00:10 +00:00
// query string
interface UIQueryString {
platform? : string ;
repo? : string ;
file? : string ;
electron? : string ;
importURL? : string ;
githubURL? : string ;
localfs? : string ;
newfile? : string ;
embed? : string ;
ignore? : string ;
force? : string ;
2021-08-05 20:07:32 +00:00
highlight? : string ;
2021-08-04 17:00:10 +00:00
file0_name? : string ;
file0_data? : string ;
file0_type? : string ;
}
export var qs : UIQueryString = decodeQueryString ( window . location . search || '?' ) as UIQueryString ;
const isElectron = parseBool ( qs . electron ) ;
const isEmbed = parseBool ( qs . embed ) ;
/// GLOBALS (TODO: remove)
2019-05-08 23:15:26 +00:00
var PRESETS : Preset [ ] ; // presets array
2018-09-16 23:45:32 +00:00
2019-05-08 23:15:26 +00:00
export var platform_id : string ; // platform ID string (platform)
export var store_id : string ; // store ID string (repo || platform)
export var repo_id : string ; // repository ID (repo)
export var platform : Platform ; // emulator object
2017-01-13 02:21:35 +00:00
2017-04-12 16:23:24 +00:00
var toolbar = $ ( "#controls_top" ) ;
2019-04-04 02:23:58 +00:00
var uitoolbar : Toolbar ;
2018-09-16 23:45:32 +00:00
export var current_project : CodeProject ; // current CodeProject object
2018-06-29 02:42:47 +00:00
2019-02-26 15:56:51 +00:00
export var projectWindows : ProjectWindows ; // window manager
2018-07-06 00:13:07 +00:00
2018-08-22 18:50:10 +00:00
var stateRecorder : StateRecorderImpl ;
2018-07-06 00:13:07 +00:00
2019-04-05 12:58:26 +00:00
var userPaused : boolean ; // did user explicitly pause?
2021-08-08 18:40:19 +00:00
var current_output : any ; // current ROM (or other object)
2019-08-20 23:13:41 +00:00
var current_preset : Preset ; // current preset object (if selected)
2021-07-20 15:40:21 +00:00
var store : LocalForage ; // persistent store
2019-04-05 12:58:26 +00:00
export var compparams ; // received build params from worker
2019-09-29 13:36:20 +00:00
export var lastDebugState : EmuState ; // last debug state (object)
2019-04-05 12:58:26 +00:00
var lastDebugInfo ; // last debug info (CPU text)
var debugCategory ; // current debug category
var debugTickPaused = false ;
var recorderActive = false ;
2019-12-27 23:03:09 +00:00
var lastViewClicked = null ;
2021-06-06 05:50:45 +00:00
var errorWasRuntime = false ;
2019-04-05 12:58:26 +00:00
var lastBreakExpr = "c.PC == 0x6000" ;
2018-06-29 23:52:09 +00:00
// TODO: codemirror multiplex support?
2020-08-05 04:48:29 +00:00
// TODO: move to views.ts?
2021-06-18 14:08:25 +00:00
const TOOL_TO_SOURCE_STYLE = {
2017-01-16 04:47:12 +00:00
'dasm' : '6502' ,
'acme' : '6502' ,
'cc65' : 'text/x-csrc' ,
'ca65' : '6502' ,
2019-08-16 01:25:08 +00:00
'nesasm' : '6502' ,
2017-01-16 04:47:12 +00:00
'z80asm' : 'z80' ,
'sdasz80' : 'z80' ,
'sdcc' : 'text/x-csrc' ,
2017-11-11 19:45:32 +00:00
'verilator' : 'verilog' ,
2018-08-28 17:59:12 +00:00
'jsasm' : 'z80' ,
'zmac' : 'z80' ,
2018-11-20 19:39:33 +00:00
'bataribasic' : 'bataribasic' ,
2018-11-21 16:53:33 +00:00
'markdown' : 'markdown' ,
2019-09-29 18:41:29 +00:00
'js' : 'javascript' ,
2019-12-11 03:19:12 +00:00
'xasm6809' : 'z80' ,
'cmoc' : 'text/x-csrc' ,
2020-06-14 01:28:58 +00:00
'yasm' : 'gas' ,
'smlrc' : 'text/x-csrc' ,
2020-07-06 23:53:20 +00:00
'inform6' : 'inform6' ,
2020-07-29 00:14:42 +00:00
'fastbasic' : 'fastbasic' ,
2020-08-05 04:48:29 +00:00
'basic' : 'basic' ,
2020-12-16 16:48:33 +00:00
'silice' : 'verilog' ,
2021-03-04 16:22:12 +00:00
'wiz' : 'text/x-wiz' ,
2021-06-10 18:25:24 +00:00
'vasmarm' : 'vasm' ,
'armips' : 'vasm'
2017-01-16 04:47:12 +00:00
}
2021-06-18 14:08:25 +00:00
const TOOL_TO_HELPURL = {
'dasm' : 'https://github.com/dasm-assembler/dasm/blob/master/docs/dasm.pdf' ,
'cc65' : 'https://cc65.github.io/doc/cc65.html' ,
'ca65' : 'https://cc65.github.io/doc/ca65.html' ,
'sdcc' : 'http://sdcc.sourceforge.net/doc/sdccman.pdf' ,
'verilator' : 'https://www.veripool.org/ftp/verilator_doc.pdf' ,
'fastbasic' : 'https://github.com/dmsc/fastbasic/blob/master/manual.md'
}
2019-08-19 13:43:48 +00:00
function gaEvent ( category :string , action :string , label? :string , value? :string ) {
2020-07-30 19:43:55 +00:00
if ( window [ 'ga' ] ) ga ( 'send' , 'event' , category , action , label , value ) ;
2019-08-19 13:43:48 +00:00
}
2021-08-04 17:00:10 +00:00
2019-05-12 13:52:09 +00:00
function alertError ( s :string ) {
2019-05-20 19:21:38 +00:00
setWaitDialog ( false ) ;
2020-07-19 22:04:36 +00:00
bootbox . alert ( {
title : '<span class="glyphicon glyphicon-alert" aria-hidden="true"></span> Alert' ,
message : s
} ) ;
2019-05-12 13:52:09 +00:00
}
function alertInfo ( s :string ) {
2019-05-20 19:21:38 +00:00
setWaitDialog ( false ) ;
2019-05-12 13:52:09 +00:00
bootbox . alert ( s ) ;
}
2021-08-04 17:00:10 +00:00
function fatalError ( s :string ) {
alertError ( s ) ;
throw new Error ( s ) ;
}
2019-05-12 13:52:09 +00:00
2018-07-08 14:07:19 +00:00
function newWorker ( ) : Worker {
2021-08-23 18:12:02 +00:00
// TODO: return new Worker("https://8bitworkshop.com.s3-website-us-east-1.amazonaws.com/dev/gen/worker/bundle.js");
2021-07-27 02:18:07 +00:00
return new Worker ( "./gen/worker/bundle.js" ) ;
2018-06-29 02:42:47 +00:00
}
2018-06-28 04:57:06 +00:00
2020-08-27 01:42:18 +00:00
const hasLocalStorage : boolean = function ( ) {
2018-12-22 23:50:39 +00:00
try {
const key = "__some_random_key_you_are_not_going_to_use__" ;
localStorage . setItem ( key , key ) ;
2019-11-11 23:43:00 +00:00
var has = localStorage . getItem ( key ) == key ;
2018-12-22 23:50:39 +00:00
localStorage . removeItem ( key ) ;
2019-11-11 23:43:00 +00:00
return has ;
2018-12-22 23:50:39 +00:00
} catch ( e ) {
return false ;
}
} ( ) ;
2021-08-04 17:00:10 +00:00
// wrapper for localstorage
class UserPrefs {
setLastPreset ( id :string ) {
if ( hasLocalStorage && ! isEmbed ) {
if ( repo_id && platform_id && ! isElectron )
localStorage . setItem ( "__lastrepo_" + platform_id , repo_id ) ;
else
localStorage . removeItem ( "__lastrepo_" + platform_id ) ;
localStorage . setItem ( "__lastplatform" , platform_id ) ;
localStorage . setItem ( "__lastid_" + store_id , id ) ;
}
}
unsetLastPreset() {
if ( hasLocalStorage && ! isEmbed ) {
delete qs . file ;
localStorage . removeItem ( "__lastid_" + store_id ) ;
}
}
getLastPreset() {
return hasLocalStorage && ! isEmbed && localStorage . getItem ( "__lastid_" + store_id ) ;
}
getLastPlatformID() {
return hasLocalStorage && ! isEmbed && localStorage . getItem ( "__lastplatform" ) ;
}
2022-02-12 16:55:05 +00:00
getLastRepoID ( platform : string ) {
return hasLocalStorage && ! isEmbed && platform && localStorage . getItem ( "__lastrepo_" + platform ) ;
2021-08-04 17:00:10 +00:00
}
shouldCompleteTour() {
return hasLocalStorage && ! isEmbed && ! localStorage . getItem ( "8bitworkshop.hello" ) ;
}
completedTour() {
if ( hasLocalStorage && ! isEmbed ) localStorage . setItem ( "8bitworkshop.hello" , "true" ) ;
}
}
var userPrefs = new UserPrefs ( ) ;
2019-09-03 23:48:12 +00:00
// https://developers.google.com/web/updates/2016/06/persistent-storage
2020-07-13 04:54:09 +00:00
function requestPersistPermission ( interactive : boolean , failureonly : boolean ) {
2019-09-03 23:48:12 +00:00
if ( navigator . storage && navigator . storage . persist ) {
navigator . storage . persist ( ) . then ( persistent = > {
2020-07-12 19:41:45 +00:00
console . log ( "requestPersistPermission =" , persistent ) ;
2019-09-03 23:48:12 +00:00
if ( persistent ) {
2020-07-13 04:54:09 +00:00
interactive && ! failureonly && alertInfo ( "Your browser says it will persist your local file edits, but you may want to back up your work anyway." ) ;
2019-09-03 23:48:12 +00:00
} else {
2020-07-19 22:04:36 +00:00
interactive && alertError ( "Your browser refused to expand the peristent storage quota. Your edits may not be preserved after closing the page." ) ;
2019-09-03 23:48:12 +00:00
}
} ) ;
2019-09-07 23:44:26 +00:00
} else {
2020-09-10 23:16:26 +00:00
interactive && alertError ( "Your browser may not persist edits after closing the page. Try a different browser." ) ;
2019-09-03 23:48:12 +00:00
}
}
2018-07-08 14:07:19 +00:00
function getCurrentPresetTitle ( ) : string {
2019-08-20 23:13:41 +00:00
if ( ! current_preset )
2019-05-06 01:49:08 +00:00
return current_project . mainPath || "ROM" ;
2016-12-16 01:21:51 +00:00
else
2019-08-20 23:13:41 +00:00
return current_preset . title || current_preset . name || current_project . mainPath || "ROM" ;
2016-12-16 01:21:51 +00:00
}
2021-08-12 23:19:39 +00:00
async function newFilesystem() {
2021-04-07 15:22:49 +00:00
var basefs : ProjectFilesystem = new WebPresetsFileSystem ( platform_id ) ;
2021-07-10 19:28:02 +00:00
if ( isElectron ) {
console . log ( 'using electron with local filesystem' , alternateLocalFilesystem ) ;
2021-08-12 23:19:39 +00:00
return new OverlayFilesystem ( basefs , alternateLocalFilesystem ) ;
2021-08-04 17:00:10 +00:00
} else if ( qs . localfs != null ) {
2021-08-12 23:19:39 +00:00
return new OverlayFilesystem ( basefs , await getLocalFilesystem ( qs . localfs ) ) ;
2021-07-10 19:28:02 +00:00
} else {
2021-08-12 23:19:39 +00:00
return new OverlayFilesystem ( basefs , new LocalForageFilesystem ( store ) ) ;
2021-07-10 19:28:02 +00:00
}
2021-08-12 23:19:39 +00:00
}
async function initProject() {
var filesystem = await newFilesystem ( ) ;
2021-04-06 16:37:41 +00:00
current_project = new CodeProject ( newWorker ( ) , platform_id , platform , filesystem ) ;
2018-10-11 15:33:09 +00:00
projectWindows = new ProjectWindows ( $ ( "#workspace" ) [ 0 ] as HTMLElement , current_project ) ;
2018-07-26 13:43:49 +00:00
current_project . callbackBuildResult = ( result :WorkerResult ) = > {
setCompileOutput ( result ) ;
2018-06-29 02:42:47 +00:00
} ;
2018-07-25 17:29:09 +00:00
current_project . callbackBuildStatus = ( busy :boolean ) = > {
2020-10-16 11:14:40 +00:00
setBusyStatus ( busy ) ;
2018-06-29 02:42:47 +00:00
} ;
2018-06-29 23:44:04 +00:00
}
2018-06-29 02:42:47 +00:00
2020-10-16 11:14:40 +00:00
function setBusyStatus ( busy : boolean ) {
if ( busy ) {
toolbar . addClass ( "is-busy" ) ;
} else {
toolbar . removeClass ( "is-busy" ) ;
}
$ ( '#compile_spinner' ) . css ( 'visibility' , busy ? 'visible' : 'hidden' ) ;
}
2018-07-02 13:34:20 +00:00
function refreshWindowList() {
var ul = $ ( "#windowMenuList" ) . empty ( ) ;
var separate = false ;
2018-11-21 12:21:07 +00:00
2018-07-02 13:34:20 +00:00
function addWindowItem ( id , name , createfn ) {
if ( separate ) {
ul . append ( document . createElement ( "hr" ) ) ;
separate = false ;
}
var li = document . createElement ( "li" ) ;
var a = document . createElement ( "a" ) ;
a . setAttribute ( "class" , "dropdown-item" ) ;
a . setAttribute ( "href" , "#" ) ;
2019-08-31 19:36:50 +00:00
a . setAttribute ( "data-wndid" , id ) ;
2018-08-05 14:00:53 +00:00
if ( id == projectWindows . getActiveID ( ) )
$ ( a ) . addClass ( "dropdown-item-checked" ) ;
2018-07-02 13:34:20 +00:00
a . appendChild ( document . createTextNode ( name ) ) ;
li . appendChild ( a ) ;
ul . append ( li ) ;
if ( createfn ) {
2019-09-29 13:36:20 +00:00
var onopen = ( id , wnd ) = > {
ul . find ( 'a' ) . removeClass ( "dropdown-item-checked" ) ;
$ ( a ) . addClass ( "dropdown-item-checked" ) ;
} ;
2018-07-02 13:34:20 +00:00
projectWindows . setCreateFunc ( id , createfn ) ;
2019-09-29 13:36:20 +00:00
projectWindows . setShowFunc ( id , onopen ) ;
2019-05-06 01:49:08 +00:00
$ ( a ) . click ( ( e ) = > {
2018-07-02 13:34:20 +00:00
projectWindows . createOrShow ( id ) ;
2019-12-27 23:03:09 +00:00
lastViewClicked = id ;
2018-07-02 13:34:20 +00:00
} ) ;
}
}
2018-11-21 12:21:07 +00:00
2018-07-08 14:07:19 +00:00
function loadEditor ( path :string ) {
2018-07-02 13:34:20 +00:00
var tool = platform . getToolForFilename ( path ) ;
var mode = tool && TOOL_TO_SOURCE_STYLE [ tool ] ;
2021-08-04 22:14:26 +00:00
return new SourceEditor ( path , mode ) ;
2018-07-02 13:34:20 +00:00
}
2018-12-30 18:57:33 +00:00
2018-12-01 09:45:55 +00:00
function addEditorItem ( id :string ) {
2019-10-19 15:29:40 +00:00
addWindowItem ( id , getFilenameForPath ( id ) , ( ) = > {
var data = current_project . getFile ( id ) ;
if ( typeof data === 'string' )
return loadEditor ( id ) ;
else if ( data instanceof Uint8Array )
2021-08-04 22:14:26 +00:00
return new BinaryFileView ( id , data as Uint8Array ) ;
2019-10-19 15:29:40 +00:00
} ) ;
2018-12-01 09:45:55 +00:00
}
2018-11-21 12:21:07 +00:00
2018-07-02 13:34:20 +00:00
// add main file editor
2019-05-06 01:49:08 +00:00
addEditorItem ( current_project . mainPath ) ;
2018-11-21 12:21:07 +00:00
2018-07-04 01:09:58 +00:00
// add other source files
2019-05-06 01:49:08 +00:00
current_project . iterateFiles ( ( id , text ) = > {
2019-12-27 19:19:11 +00:00
if ( text && id != current_project . mainPath ) {
2018-12-01 09:45:55 +00:00
addEditorItem ( id ) ;
2019-12-27 19:19:11 +00:00
}
2018-07-02 13:34:20 +00:00
} ) ;
2018-11-21 12:21:07 +00:00
2018-07-04 01:09:58 +00:00
// add listings
2018-11-24 16:33:28 +00:00
separate = true ;
2018-07-04 01:09:58 +00:00
var listings = current_project . getListings ( ) ;
if ( listings ) {
for ( var lstfn in listings ) {
var lst = listings [ lstfn ] ;
2019-02-22 16:43:07 +00:00
// add listing if source/assembly file exists and has text
if ( ( lst . assemblyfile && lst . assemblyfile . text ) || ( lst . sourcefile && lst . sourcefile . text ) ) {
2018-11-21 12:21:07 +00:00
addWindowItem ( lstfn , getFilenameForPath ( lstfn ) , ( path ) = > {
2021-08-04 22:14:26 +00:00
return new ListingView ( path ) ;
2018-07-04 01:09:58 +00:00
} ) ;
}
}
}
2018-07-02 13:34:20 +00:00
// add other tools
separate = true ;
2019-09-30 13:26:05 +00:00
if ( platform . disassemble && platform . saveState ) {
2019-05-06 01:49:08 +00:00
addWindowItem ( "#disasm" , "Disassembly" , ( ) = > {
2021-08-04 22:14:26 +00:00
return new DisassemblerView ( ) ;
2018-07-02 13:34:20 +00:00
} ) ;
}
2018-08-24 00:53:37 +00:00
if ( platform . readAddress ) {
2019-05-06 01:49:08 +00:00
addWindowItem ( "#memory" , "Memory Browser" , ( ) = > {
2021-08-04 22:14:26 +00:00
return new MemoryView ( ) ;
2018-07-02 13:34:20 +00:00
} ) ;
}
2019-08-27 16:12:56 +00:00
if ( current_project . segments && current_project . segments . length ) {
2019-05-06 01:49:08 +00:00
addWindowItem ( "#memmap" , "Memory Map" , ( ) = > {
2021-08-04 22:14:26 +00:00
return new MemoryMapView ( ) ;
2019-02-21 21:47:25 +00:00
} ) ;
}
2019-08-26 17:44:06 +00:00
if ( platform . readVRAMAddress ) {
addWindowItem ( "#memvram" , "VRAM Browser" , ( ) = > {
2021-08-04 22:14:26 +00:00
return new VRAMMemoryView ( ) ;
2019-03-03 16:32:25 +00:00
} ) ;
}
2019-08-24 15:28:04 +00:00
if ( platform . startProbing ) {
2019-08-27 20:10:10 +00:00
addWindowItem ( "#memheatmap" , "Memory Probe" , ( ) = > {
2021-08-04 22:14:26 +00:00
return new AddressHeatMapView ( ) ;
2019-08-24 22:08:37 +00:00
} ) ;
// TODO: only if raster
2019-08-27 20:10:10 +00:00
addWindowItem ( "#crtheatmap" , "CRT Probe" , ( ) = > {
2021-08-04 22:14:26 +00:00
return new RasterPCHeatMapView ( ) ;
2019-08-24 15:28:04 +00:00
} ) ;
2020-01-04 20:14:36 +00:00
addWindowItem ( "#probelog" , "Probe Log" , ( ) = > {
2021-08-04 22:14:26 +00:00
return new ProbeLogView ( ) ;
2020-01-04 20:14:36 +00:00
} ) ;
2021-04-05 17:11:38 +00:00
addWindowItem ( "#scanlineio" , "Scanline I/O" , ( ) = > {
2021-08-04 22:14:26 +00:00
return new ScanlineIOView ( ) ;
2021-04-05 17:11:38 +00:00
} ) ;
2020-07-04 21:39:41 +00:00
addWindowItem ( "#symbolprobe" , "Symbol Profiler" , ( ) = > {
2021-08-04 22:14:26 +00:00
return new ProbeSymbolView ( ) ;
2020-07-04 21:39:41 +00:00
} ) ;
2020-07-09 18:27:56 +00:00
addWindowItem ( "#callstack" , "Call Stack" , ( ) = > {
2021-08-04 22:14:26 +00:00
return new CallStackView ( ) ;
2019-10-16 20:06:56 +00:00
} ) ;
2020-07-11 18:46:47 +00:00
/ *
addWindowItem ( "#framecalls" , "Frame Profiler" , ( ) = > {
2021-08-04 22:14:26 +00:00
return new FrameCallsView ( ) ;
2020-07-11 18:46:47 +00:00
} ) ;
2019-10-19 15:29:40 +00:00
* /
2019-08-24 15:28:04 +00:00
}
2020-07-13 23:05:29 +00:00
if ( platform . getDebugTree ) {
addWindowItem ( "#debugview" , "Debug Tree" , ( ) = > {
2021-08-04 22:14:26 +00:00
return new DebugBrowserView ( ) ;
2020-07-13 23:05:29 +00:00
} ) ;
}
2019-05-06 01:49:08 +00:00
addWindowItem ( '#asseteditor' , 'Asset Editor' , ( ) = > {
2021-08-04 22:14:26 +00:00
return new AssetEditorView ( ) ;
2019-03-18 18:39:02 +00:00
} ) ;
2018-07-02 13:34:20 +00:00
}
2021-08-05 20:07:32 +00:00
function highlightLines ( path :string , hispec :string ) {
if ( hispec ) {
var toks = qs . highlight . split ( ',' ) ;
var start = parseInt ( toks [ 0 ] ) - 1 ;
var end = parseInt ( toks [ 1 ] ) - 1 ;
var editor = projectWindows . createOrShow ( path ) as SourceEditor ;
editor . highlightLines ( start , end ) ;
}
}
2019-05-14 17:56:48 +00:00
function loadMainWindow ( preset_id :string ) {
// we need this to build create functions for the editor
refreshWindowList ( ) ;
// show main file
projectWindows . createOrShow ( preset_id ) ;
// build project
current_project . setMainFile ( preset_id ) ;
2021-08-05 20:07:32 +00:00
// highlighting?
highlightLines ( preset_id , qs . highlight ) ;
2019-05-14 17:56:48 +00:00
}
2019-12-18 17:20:15 +00:00
async function loadProject ( preset_id :string ) {
2018-06-29 23:52:09 +00:00
// set current file ID
2019-12-27 19:19:11 +00:00
// TODO: this is done twice (mainPath and mainpath!)
2018-07-06 00:13:07 +00:00
current_project . mainPath = preset_id ;
2021-08-04 17:00:10 +00:00
userPrefs . setLastPreset ( preset_id ) ;
2018-06-29 23:52:09 +00:00
// load files from storage or web URLs
2019-12-18 17:20:15 +00:00
var result = await current_project . loadFiles ( [ preset_id ] ) ;
measureTimeLoad = new Date ( ) ; // for timing calc.
if ( result && result . length ) {
// file found; continue
loadMainWindow ( preset_id ) ;
} else {
var skel = await getSkeletonFile ( preset_id ) ;
current_project . filedata [ preset_id ] = skel || "\n" ;
loadMainWindow ( preset_id ) ;
// don't alert if we selected "new file"
2021-08-04 17:00:10 +00:00
if ( ! qs . newfile ) {
2019-12-18 17:20:15 +00:00
alertInfo ( "Could not find file \"" + preset_id + "\". Loading default file." ) ;
2020-07-13 04:54:09 +00:00
} else {
requestPersistPermission ( true , true ) ;
2018-06-29 23:44:04 +00:00
}
2021-08-04 17:00:10 +00:00
delete qs . newfile ;
2019-12-18 17:20:15 +00:00
replaceURLState ( ) ;
}
2016-12-16 01:21:51 +00:00
}
2019-05-09 12:44:47 +00:00
function reloadProject ( id :string ) {
2019-05-12 13:52:09 +00:00
// leave repository == '/'
if ( id == '/' ) {
qs = { repo : '/' } ;
2019-05-09 17:22:24 +00:00
} else if ( id . indexOf ( '://' ) >= 0 ) {
var urlparse = parseGithubURL ( id ) ;
if ( urlparse ) {
qs = { repo :urlparse.repopath } ;
}
2019-05-09 12:44:47 +00:00
} else {
2021-08-04 17:00:10 +00:00
qs . platform = platform_id ;
qs . file = id ;
2019-05-09 12:44:47 +00:00
}
2017-01-16 19:13:03 +00:00
gotoNewLocation ( ) ;
2016-12-16 01:21:51 +00:00
}
2019-12-27 16:27:29 +00:00
async function getSkeletonFile ( fileid :string ) : Promise < string > {
2018-06-29 23:44:04 +00:00
var ext = platform . getToolForFilename ( fileid ) ;
2019-12-27 16:27:29 +00:00
try {
return await $ . get ( "presets/" + getBasePlatform ( platform_id ) + "/skeleton." + ext , 'text' ) ;
} catch ( e ) {
2019-05-12 13:52:09 +00:00
alertError ( "Could not load skeleton for " + platform_id + "/" + ext + "; using blank file" ) ;
2019-12-27 16:27:29 +00:00
}
2018-06-29 23:44:04 +00:00
}
2019-04-19 14:00:01 +00:00
function checkEnteredFilename ( fn : string ) : boolean {
if ( fn . indexOf ( " " ) >= 0 ) {
2019-05-12 13:52:09 +00:00
alertError ( "No spaces in filenames, please." ) ;
2019-04-19 14:00:01 +00:00
return false ;
}
return true ;
}
2016-12-16 01:21:51 +00:00
function _createNewFile ( e ) {
2018-09-13 00:54:25 +00:00
// TODO: support spaces
2019-05-12 13:52:09 +00:00
bootbox . prompt ( {
title : "Enter the name of your new main source file." ,
placeholder : "newfile" + platform . getDefaultExtension ( ) ,
callback : ( filename ) = > {
if ( filename && filename . trim ( ) . length > 0 ) {
if ( ! checkEnteredFilename ( filename ) ) return ;
if ( filename . indexOf ( "." ) < 0 ) {
filename += platform . getDefaultExtension ( ) ;
}
2019-05-12 19:39:09 +00:00
var path = filename ;
2019-08-19 13:43:48 +00:00
gaEvent ( 'workspace' , 'file' , 'new' ) ;
2021-08-04 17:00:10 +00:00
qs . newfile = '1' ;
2019-05-14 17:56:48 +00:00
reloadProject ( path ) ;
2019-05-12 13:52:09 +00:00
}
2016-12-16 01:21:51 +00:00
}
2019-05-12 13:52:09 +00:00
} as any ) ;
2016-12-30 23:51:15 +00:00
return true ;
2016-12-16 01:21:51 +00:00
}
2018-06-26 23:57:03 +00:00
function _uploadNewFile ( e ) {
2021-08-06 23:05:32 +00:00
const uploadFileElem = $ ( ` <input type="file" multiple accept="*" style="display:none"> ` ) ;
const file = uploadFileElem [ 0 ] as HTMLInputElement ;
uploadFileElem . change ( ( e ) = > { handleFileUpload ( file . files ) } ) ;
uploadFileElem . click ( ) ;
2018-06-26 23:57:03 +00:00
}
2020-06-25 19:13:20 +00:00
// called from index.html
2021-08-06 23:05:32 +00:00
function handleFileUpload ( files : FileList ) {
2018-06-26 23:57:03 +00:00
console . log ( files ) ;
var index = 0 ;
2018-11-21 12:21:07 +00:00
function uploadNextFile() {
2018-06-26 23:57:03 +00:00
var f = files [ index ++ ] ;
if ( ! f ) {
2019-09-08 21:21:58 +00:00
console . log ( "Done uploading" , index ) ;
if ( index > 2 ) {
alertInfo ( "Files uploaded." ) ;
setTimeout ( updateSelector , 1000 ) ; // TODO: wait for files to upload
} else {
2021-08-04 17:00:10 +00:00
qs . file = files [ 0 ] . name ;
2020-06-25 19:13:20 +00:00
bootbox . confirm ( {
2021-08-04 17:00:10 +00:00
message : "Open '" + qs . file + "' as main project file?" ,
2020-06-25 19:13:20 +00:00
buttons : {
confirm : { label : "Open As New Project" } ,
cancel : { label : "Include/Link With Project Later" } ,
} ,
callback : ( result ) = > {
if ( result )
gotoNewLocation ( ) ;
else
setTimeout ( updateSelector , 1000 ) ; // TODO: wait for files to upload
}
} ) ;
2019-09-08 21:21:58 +00:00
}
2019-09-03 00:24:34 +00:00
gaEvent ( 'workspace' , 'file' , 'upload' ) ;
2018-06-26 23:57:03 +00:00
} else {
2019-05-12 19:39:09 +00:00
var path = f . name ;
2018-06-26 23:57:03 +00:00
var reader = new FileReader ( ) ;
reader . onload = function ( e ) {
2018-11-30 15:43:23 +00:00
var arrbuf = ( < any > e . target ) . result as ArrayBuffer ;
var data : FileData = new Uint8Array ( arrbuf ) ;
2018-12-07 22:24:27 +00:00
// convert to UTF8, unless it's a binary file
2019-02-09 18:42:35 +00:00
if ( isProbablyBinary ( path , data ) ) {
2019-09-03 00:24:34 +00:00
//gotoMainFile = false;
2018-11-30 15:43:23 +00:00
} else {
2019-03-21 02:49:44 +00:00
data = byteArrayToUTF8 ( data ) . replace ( '\r\n' , '\n' ) ; // convert CRLF to LF
2018-11-30 15:43:23 +00:00
}
// store in local forage
2019-09-03 00:24:34 +00:00
projectWindows . updateFile ( path , data ) ;
console . log ( "Uploaded " + path + " " + data . length + " bytes" ) ;
uploadNextFile ( ) ;
2018-06-26 23:57:03 +00:00
}
2018-11-30 15:43:23 +00:00
reader . readAsArrayBuffer ( f ) ; // read as binary
2018-06-26 23:57:03 +00:00
}
}
if ( files ) uploadNextFile ( ) ;
}
2021-07-20 15:40:21 +00:00
async function _openLocalDirectory ( e ) {
var pickerfn = window [ 'showDirectoryPicker' ] ;
if ( ! pickerfn ) {
bootbox . alert ( ` This browser can't open local files on your computer, yet. Try Chrome. ` ) ;
}
var dirHandle = await pickerfn ( ) ;
var repoid = dirHandle . name ;
var storekey = '__localfs__' + repoid ;
var fsdata = {
handle : dirHandle ,
}
var lstore = localforage . createInstance ( {
name : storekey ,
version : 2.0
} ) ;
await lstore . setItem ( storekey , fsdata ) ;
qs = { localfs : repoid } ;
gotoNewLocation ( true ) ;
}
async function promptUser ( message : string ) : Promise < string > {
return new Promise ( ( resolve , reject ) = > {
bootbox . prompt ( message , ( result ) = > {
resolve ( result ) ;
} ) ;
} ) ;
}
async function getLocalFilesystem ( repoid : string ) : Promise < ProjectFilesystem > {
const options = { mode : 'readwrite' } ;
var storekey = '__localfs__' + repoid ;
var lstore = localforage . createInstance ( {
name : storekey ,
version : 2.0
} ) ;
var fsdata : any = await lstore . getItem ( storekey ) ;
var dirHandle = fsdata . handle as any ;
console . log ( fsdata , dirHandle ) ;
var granted = await dirHandle . queryPermission ( options ) ;
console . log ( granted ) ;
if ( granted !== 'granted' ) {
await promptUser ( ` Request permissions to access filesystem? ` ) ;
granted = await dirHandle . requestPermission ( options ) ;
}
if ( granted !== 'granted' ) {
bootbox . alert ( ` Could not get permission to access filesystem. ` ) ;
return ;
}
return {
getFileData : async ( path ) = > {
console . log ( 'getFileData' , path ) ;
let fileHandle = await dirHandle . getFileHandle ( path , { create : false } ) ;
console . log ( 'getFileData' , fileHandle ) ;
let file = await fileHandle . getFile ( ) ;
console . log ( 'getFileData' , file ) ;
let contents = await ( isProbablyBinary ( path ) ? file . binary ( ) : file . text ( ) ) ;
console . log ( fileHandle , file , contents ) ;
return contents ;
} ,
setFileData : async ( path , data ) = > {
//let vh = await dirHandle.getFileHandle(path, { create: true });
}
}
}
2018-08-05 14:00:53 +00:00
function getCurrentMainFilename ( ) : string {
2019-05-06 01:49:08 +00:00
return getFilenameForPath ( current_project . mainPath ) ;
2018-08-05 14:00:53 +00:00
}
function getCurrentEditorFilename ( ) : string {
return getFilenameForPath ( projectWindows . getActiveID ( ) ) ;
2017-02-02 19:11:52 +00:00
}
2019-05-06 01:49:08 +00:00
// GITHUB stuff (TODO: move)
2019-05-07 19:37:37 +00:00
var githubService : GithubService ;
2019-05-18 22:33:38 +00:00
function getCookie ( name ) : string {
2019-05-07 19:37:37 +00:00
var nameEQ = name + "=" ;
var ca = document . cookie . split ( ';' ) ;
for ( var i = 0 ; i < ca . length ; i ++ ) {
var c = ca [ i ] ;
while ( c . charAt ( 0 ) == ' ' ) c = c . substring ( 1 , c . length ) ;
if ( c . indexOf ( nameEQ ) == 0 ) return c . substring ( nameEQ . length , c . length ) ;
}
return null ;
}
2021-08-05 01:27:48 +00:00
async function getGithubService() {
2019-05-07 19:37:37 +00:00
if ( ! githubService ) {
2021-08-06 13:50:00 +00:00
// load github API client
2021-09-16 02:53:38 +00:00
await loadScript ( 'lib/octokat.js' ) ;
2021-08-06 13:50:00 +00:00
// load firebase
await loadScript ( 'https://www.gstatic.com/firebasejs/8.8.1/firebase-app.js' ) ;
await loadScript ( 'https://www.gstatic.com/firebasejs/8.8.1/firebase-auth.js' ) ;
2022-02-21 15:39:49 +00:00
await loadScript ( 'https://8bitworkshop.com/config.js' ) ;
2019-05-08 13:39:57 +00:00
// get github API key from cookie
2019-05-08 17:42:24 +00:00
// TODO: move to service?
2019-05-08 13:39:57 +00:00
var ghkey = getCookie ( '__github_key' ) ;
2021-08-05 01:27:48 +00:00
githubService = new GithubService ( Octokat , ghkey , store , current_project ) ;
2019-05-08 13:39:57 +00:00
console . log ( "loaded github service" ) ;
2019-05-07 19:37:37 +00:00
}
return githubService ;
}
2019-05-08 17:42:24 +00:00
function getBoundGithubURL ( ) : string {
2019-05-08 23:15:26 +00:00
var toks = ( repo_id || '' ) . split ( '/' ) ;
if ( toks . length != 2 ) {
2019-05-16 14:08:09 +00:00
alertError ( "<p>You are not in a GitHub repository.</p><p>Choose one from the pulldown, or Import or Publish one.</p>" ) ;
2019-05-08 23:15:26 +00:00
return null ;
}
return 'https://github.com/' + toks [ 0 ] + '/' + toks [ 1 ] ;
2019-05-08 13:39:57 +00:00
}
2021-08-05 01:27:48 +00:00
async function importProjectFromGithub ( githuburl :string , replaceURL :boolean ) {
2019-05-08 23:15:26 +00:00
var sess : GHSession ;
2019-05-09 17:22:24 +00:00
var urlparse = parseGithubURL ( githuburl ) ;
2019-05-08 23:15:26 +00:00
if ( ! urlparse ) {
2019-05-12 13:52:09 +00:00
alertError ( 'Could not parse Github URL.' ) ;
2019-05-08 23:15:26 +00:00
return ;
}
2019-05-09 12:44:47 +00:00
// redirect to repo if exists
var existing = getRepos ( ) [ urlparse . repopath ] ;
2019-05-22 21:03:56 +00:00
if ( existing && ! confirm ( "You've already imported " + urlparse . repopath + " -- do you want to replace all local files?" ) ) {
2019-05-09 12:44:47 +00:00
return ;
}
// create new store for imported repository
setWaitDialog ( true ) ;
2020-06-28 16:43:26 +00:00
var newstore = createNewPersistentStore ( urlparse . repopath ) ;
2019-05-09 12:44:47 +00:00
// import into new store
setWaitProgress ( 0.25 ) ;
2021-08-05 01:27:48 +00:00
var gh = await getGithubService ( ) ;
return gh . import ( githuburl ) . then ( ( sess1 :GHSession ) = > {
2019-05-09 12:44:47 +00:00
sess = sess1 ;
setWaitProgress ( 0.75 ) ;
2021-08-05 01:27:48 +00:00
return gh . pull ( githuburl , newstore ) ;
2019-05-09 12:44:47 +00:00
} ) . then ( ( sess2 :GHSession ) = > {
// TODO: only first session has mainPath?
// reload repo
qs = { repo :sess.repopath } ; // file:sess.mainPath, platform:sess.platform_id};
setWaitDialog ( false ) ;
2019-08-19 13:43:48 +00:00
gaEvent ( 'sync' , 'import' , githuburl ) ;
2019-05-22 21:03:56 +00:00
gotoNewLocation ( replaceURL ) ;
2019-05-09 12:44:47 +00:00
} ) . catch ( ( e ) = > {
setWaitDialog ( false ) ;
console . log ( e ) ;
2019-06-03 14:08:29 +00:00
alertError ( "<p>Could not import " + githuburl + ".</p>" + e ) ;
2016-12-16 01:21:51 +00:00
} ) ;
2019-05-06 01:49:08 +00:00
}
2021-08-05 01:27:48 +00:00
async function _loginToGithub ( e ) {
var gh = await getGithubService ( ) ;
gh . login ( ) . then ( ( ) = > {
2019-05-22 18:45:03 +00:00
alertInfo ( "You are signed in to Github." ) ;
2019-06-03 14:08:29 +00:00
} ) . catch ( ( e ) = > {
alertError ( "<p>Could not sign in.</p>" + e ) ;
2019-05-22 18:45:03 +00:00
} ) ;
}
2021-08-05 01:27:48 +00:00
async function _logoutOfGithub ( e ) {
var gh = await getGithubService ( ) ;
gh . logout ( ) . then ( ( ) = > {
2019-05-22 18:45:03 +00:00
alertInfo ( "You are logged out of Github." ) ;
} ) ;
2019-05-22 17:58:44 +00:00
}
2019-05-08 23:15:26 +00:00
function _importProjectFromGithub ( e ) {
var modal = $ ( "#importGithubModal" ) ;
var btn = $ ( "#importGithubButton" ) ;
modal . modal ( 'show' ) ;
btn . off ( 'click' ) . on ( 'click' , ( ) = > {
var githuburl = $ ( "#importGithubURL" ) . val ( ) + "" ;
2019-05-09 12:44:47 +00:00
modal . modal ( 'hide' ) ;
2019-05-22 21:03:56 +00:00
importProjectFromGithub ( githuburl , false ) ;
2019-05-08 23:15:26 +00:00
} ) ;
}
2019-05-07 19:37:37 +00:00
function _publishProjectToGithub ( e ) {
2019-05-08 23:15:26 +00:00
if ( repo_id ) {
2019-08-26 21:39:14 +00:00
if ( ! confirm ( "This project (" + current_project . mainPath + ") is already bound to a Github repository. Do you want to re-publish to a new repository? (You can instead choose 'Push Changes' to update files in the existing repository.)" ) )
return ;
2019-05-08 13:39:57 +00:00
}
2019-05-07 19:37:37 +00:00
var modal = $ ( "#publishGithubModal" ) ;
var btn = $ ( "#publishGithubButton" ) ;
2019-05-22 02:36:56 +00:00
$ ( "#githubRepoName" ) . val ( getFilenamePrefix ( getFilenameForPath ( current_project . mainPath ) ) ) ;
2019-05-06 01:49:08 +00:00
modal . modal ( 'show' ) ;
2021-08-05 01:27:48 +00:00
btn . off ( 'click' ) . on ( 'click' , async ( ) = > {
2019-05-07 19:37:37 +00:00
var name = $ ( "#githubRepoName" ) . val ( ) + "" ;
var desc = $ ( "#githubRepoDesc" ) . val ( ) + "" ;
var priv = $ ( "#githubRepoPrivate" ) . val ( ) == 'private' ;
var license = $ ( "#githubRepoLicense" ) . val ( ) + "" ;
2019-05-08 13:39:57 +00:00
var sess ;
2019-05-16 17:04:27 +00:00
if ( ! name ) {
alertError ( "You did not enter a project name." ) ;
return ;
}
2019-05-08 13:39:57 +00:00
modal . modal ( 'hide' ) ;
setWaitDialog ( true ) ;
2021-08-05 01:27:48 +00:00
var gh = await getGithubService ( ) ;
gh . login ( ) . then ( ( ) = > {
2019-05-09 12:44:47 +00:00
setWaitProgress ( 0.25 ) ;
2021-08-05 01:27:48 +00:00
return gh . publish ( name , desc , license , priv ) ;
2019-08-29 02:12:45 +00:00
} ) . then ( ( _sess ) = > {
sess = _sess ;
2019-05-09 12:44:47 +00:00
setWaitProgress ( 0.5 ) ;
2021-08-04 17:00:10 +00:00
repo_id = qs . repo = sess . repopath ;
2019-05-08 13:39:57 +00:00
return pushChangesToGithub ( 'initial import from 8bitworkshop.com' ) ;
} ) . then ( ( ) = > {
2019-08-19 13:43:48 +00:00
gaEvent ( 'sync' , 'publish' , priv ? "" : name ) ;
2019-08-29 02:12:45 +00:00
importProjectFromGithub ( sess . url , false ) ;
2019-05-07 19:37:37 +00:00
} ) . catch ( ( e ) = > {
2019-05-08 13:39:57 +00:00
setWaitDialog ( false ) ;
console . log ( e ) ;
2019-05-12 13:52:09 +00:00
alertError ( "Could not publish GitHub repository: " + e ) ;
2019-05-07 19:37:37 +00:00
} ) ;
2019-05-06 01:49:08 +00:00
} ) ;
}
2019-05-07 19:37:37 +00:00
function _pushProjectToGithub ( e ) {
2019-05-08 13:39:57 +00:00
var ghurl = getBoundGithubURL ( ) ;
if ( ! ghurl ) return ;
2019-05-07 19:37:37 +00:00
var modal = $ ( "#pushGithubModal" ) ;
var btn = $ ( "#pushGithubButton" ) ;
modal . modal ( 'show' ) ;
btn . off ( 'click' ) . on ( 'click' , ( ) = > {
var commitMsg = $ ( "#githubCommitMsg" ) . val ( ) + "" ;
modal . modal ( 'hide' ) ;
pushChangesToGithub ( commitMsg ) ;
} ) ;
2019-05-06 01:49:08 +00:00
}
2019-05-08 13:39:57 +00:00
function _pullProjectFromGithub ( e ) {
var ghurl = getBoundGithubURL ( ) ;
if ( ! ghurl ) return ;
2021-08-05 01:27:48 +00:00
bootbox . confirm ( "Pull from repository and replace all local files? Any changes you've made will be overwritten." ,
async ( ok ) = > {
2019-05-20 19:21:38 +00:00
if ( ok ) {
setWaitDialog ( true ) ;
2021-08-05 01:27:48 +00:00
var gh = await getGithubService ( ) ;
gh . pull ( ghurl ) . then ( ( sess :GHSession ) = > {
2019-05-20 19:21:38 +00:00
setWaitDialog ( false ) ;
projectWindows . updateAllOpenWindows ( store ) ;
} ) ;
}
2019-05-08 13:39:57 +00:00
} ) ;
}
2019-05-12 19:01:33 +00:00
function confirmCommit ( sess ) : Promise < GHSession > {
return new Promise ( ( resolve , reject ) = > {
var files = sess . commit . files ;
console . log ( files ) ;
// anything changed?
if ( files . length == 0 ) {
setWaitDialog ( false ) ;
bootbox . alert ( "No files changed." ) ;
2020-01-01 19:22:57 +00:00
return ;
2019-05-12 19:01:33 +00:00
}
// build commit confirm message
var msg = "" ;
for ( var f of files ) {
msg += f . filename + ": " + f . status ;
if ( f . additions || f . deletions || f . changes ) {
msg += " (" + f . additions + " additions, " + f . deletions + " deletions, " + f . changes + " changes)" ;
2019-05-16 14:08:09 +00:00
} ;
2019-05-12 19:01:33 +00:00
msg += "<br/>" ;
}
// show dialog, continue when yes
bootbox . confirm ( msg , ( ok ) = > {
if ( ok ) {
resolve ( sess ) ;
} else {
setWaitDialog ( false ) ;
}
} ) ;
} ) ;
}
2021-08-05 01:27:48 +00:00
async function pushChangesToGithub ( message :string ) {
2019-05-08 13:39:57 +00:00
var ghurl = getBoundGithubURL ( ) ;
if ( ! ghurl ) return ;
2019-05-07 19:37:37 +00:00
// build file list for push
var files = [ ] ;
for ( var path in current_project . filedata ) {
var newpath = current_project . stripLocalPath ( path ) ;
2019-05-08 13:39:57 +00:00
var data = current_project . filedata [ path ] ;
if ( newpath && data ) {
files . push ( { path :newpath , data :data } ) ;
}
2019-05-07 19:37:37 +00:00
}
2019-05-22 01:39:37 +00:00
// include built ROM file in bin/[mainfile].rom
2019-05-21 20:49:48 +00:00
if ( current_output instanceof Uint8Array ) {
let binpath = "bin/" + getCurrentMainFilename ( ) + ".rom" ;
files . push ( { path :binpath , data :current_output } ) ;
}
2019-05-07 19:37:37 +00:00
// push files
setWaitDialog ( true ) ;
2021-08-05 01:27:48 +00:00
var gh = await getGithubService ( ) ;
return gh . login ( ) . then ( ( ) = > {
2019-05-09 12:44:47 +00:00
setWaitProgress ( 0.5 ) ;
2021-08-05 01:27:48 +00:00
return gh . commit ( ghurl , message , files ) ;
2019-05-12 19:01:33 +00:00
} ) . then ( ( sess ) = > {
return confirmCommit ( sess ) ;
2019-05-12 17:33:21 +00:00
} ) . then ( ( sess ) = > {
2021-08-05 01:27:48 +00:00
return gh . push ( sess ) ;
2019-05-08 17:42:24 +00:00
} ) . then ( ( sess ) = > {
2019-05-08 13:39:57 +00:00
setWaitDialog ( false ) ;
2019-05-12 13:52:09 +00:00
alertInfo ( "Pushed files to " + ghurl ) ;
2019-05-08 13:39:57 +00:00
return sess ;
} ) . catch ( ( e ) = > {
2019-05-07 19:37:37 +00:00
setWaitDialog ( false ) ;
2019-05-08 13:39:57 +00:00
console . log ( e ) ;
2019-05-12 13:52:09 +00:00
alertError ( "Could not push GitHub repository: " + e ) ;
2019-05-06 01:49:08 +00:00
} ) ;
}
2019-05-16 14:08:09 +00:00
function _deleteRepository() {
var ghurl = getBoundGithubURL ( ) ;
if ( ! ghurl ) return ;
2019-07-22 01:28:44 +00:00
bootbox . prompt ( "<p>Are you sure you want to delete this repository (" + ghurl + ") from browser storage?</p><p>All changes since last commit will be lost.</p><p>Type DELETE to proceed.<p>" , ( yes ) = > {
if ( yes . trim ( ) . toUpperCase ( ) == "DELETE" ) {
2019-05-16 14:08:09 +00:00
deleteRepository ( ) ;
}
} ) ;
}
function deleteRepository() {
var ghurl = getBoundGithubURL ( ) ;
var gh ;
setWaitDialog ( true ) ;
// delete all keys in storage
store . keys ( ) . then ( ( keys :string [ ] ) = > {
return Promise . all ( keys . map ( ( key ) = > {
return store . removeItem ( key ) ;
} ) ) ;
} ) . then ( ( ) = > {
gh = getGithubService ( ) ;
return gh . getGithubSession ( ghurl ) ;
} ) . then ( ( sess ) = > {
// un-bind repo from list
gh . bind ( sess , false ) ;
} ) . then ( ( ) = > {
setWaitDialog ( false ) ;
// leave repository
qs = { repo : '/' } ;
gotoNewLocation ( ) ;
} ) ;
}
2018-08-24 03:58:35 +00:00
function _shareEmbedLink ( e ) {
2019-10-19 15:29:40 +00:00
if ( current_output == null ) {
2019-05-12 13:52:09 +00:00
alertError ( "Please fix errors before sharing." ) ;
2018-08-24 03:58:35 +00:00
return true ;
}
2018-11-22 17:28:50 +00:00
if ( ! ( current_output instanceof Uint8Array ) ) {
2019-05-12 13:52:09 +00:00
alertError ( "Can't share a Verilog executable yet. (It's not actually a ROM...)" ) ;
2018-11-22 17:28:50 +00:00
return true ;
}
2019-03-03 20:37:22 +00:00
loadClipboardLibrary ( ) ;
2019-08-23 19:05:12 +00:00
loadScript ( 'lib/liblzg.js' ) . then ( ( ) = > {
2018-08-26 02:00:45 +00:00
// TODO: Module is bad var name (conflicts with MAME)
2018-11-22 17:28:50 +00:00
var lzgrom = compressLZG ( window [ 'Module' ] , Array . from ( < Uint8Array > current_output ) ) ;
2018-08-24 03:58:35 +00:00
window [ 'Module' ] = null ; // so we load it again next time
var lzgb64 = btoa ( byteArrayToString ( lzgrom ) ) ;
var embed = {
p : platform_id ,
2019-05-06 01:49:08 +00:00
//n: current_project.mainPath,
2018-08-24 03:58:35 +00:00
r : lzgb64
} ;
var linkqs = $ . param ( embed ) ;
2021-08-07 14:04:17 +00:00
var fulllink = get8bitworkshopLink ( linkqs , 'player.html' ) ;
2018-08-24 03:58:35 +00:00
var iframelink = '<iframe width=640 height=600 src="' + fulllink + '">' ;
$ ( "#embedLinkTextarea" ) . text ( fulllink ) ;
$ ( "#embedIframeTextarea" ) . text ( iframelink ) ;
$ ( "#embedLinkModal" ) . modal ( 'show' ) ;
2018-08-25 02:55:16 +00:00
$ ( "#embedAdviceWarnAll" ) . hide ( ) ;
$ ( "#embedAdviceWarnIE" ) . hide ( ) ;
if ( fulllink . length >= 65536 ) $ ( "#embedAdviceWarnAll" ) . show ( ) ;
else if ( fulllink . length >= 5120 ) $ ( "#embedAdviceWarnIE" ) . show ( ) ;
2018-08-24 03:58:35 +00:00
} ) ;
2016-12-30 23:51:15 +00:00
return true ;
2016-12-16 01:21:51 +00:00
}
2019-03-03 20:37:22 +00:00
function loadClipboardLibrary() {
2021-08-03 17:13:59 +00:00
// can happen in background because it won't be used until user clicks
console . log ( 'clipboard' ) ;
import ( 'clipboard' ) . then ( ( clipmod ) = > {
let ClipboardJS = clipmod . default ;
2019-03-03 20:37:22 +00:00
new ClipboardJS ( ".btn" ) ;
} ) ;
}
function get8bitworkshopLink ( linkqs : string , fn : string ) {
console . log ( linkqs ) ;
var loc = window . location ;
var prefix = loc . pathname . replace ( 'index.html' , '' ) ;
var protocol = ( loc . host == '8bitworkshop.com' ) ? 'https:' : loc . protocol ;
2021-08-07 14:04:17 +00:00
var fulllink = protocol + '//' + loc . host + prefix + fn + '?' + linkqs ;
2019-03-03 20:37:22 +00:00
return fulllink ;
}
2020-07-25 23:33:55 +00:00
function _downloadCassetteFile_apple2 ( e ) {
2018-09-25 23:46:24 +00:00
var addr = compparams && compparams . code_start ;
2019-08-23 19:05:12 +00:00
loadScript ( 'lib/c2t.js' ) . then ( ( ) = > {
2018-09-25 23:46:24 +00:00
var stdout = '' ;
var print_fn = function ( s ) { stdout += s + "\n" ; }
var c2t = window [ 'c2t' ] ( {
noInitialRun :true ,
print :print_fn ,
printErr :print_fn
} ) ;
var FS = c2t [ 'FS' ] ;
var rompath = getCurrentMainFilename ( ) + ".bin" ;
var audpath = getCurrentMainFilename ( ) + ".wav" ;
FS . writeFile ( rompath , current_output , { encoding : 'binary' } ) ;
var args = [ "-2bc" , rompath + ',' + addr . toString ( 16 ) , audpath ] ;
c2t . callMain ( args ) ;
var audout = FS . readFile ( audpath , { 'encoding' : 'binary' } ) ;
if ( audout ) {
var blob = new Blob ( [ audout ] , { type : "audio/wav" } ) ;
saveAs ( blob , audpath ) ;
2018-09-26 01:16:01 +00:00
stdout += "Then connect your audio output to the cassette input, turn up the volume, and play the audio file." ;
2020-07-17 17:06:24 +00:00
alertInfo ( '<pre style="white-space: pre-wrap">' + stdout + '</pre>' ) ;
2018-09-25 23:46:24 +00:00
}
} ) ;
}
2020-07-25 23:33:55 +00:00
function _downloadCassetteFile_vcs ( e ) {
loadScript ( 'lib/makewav.js' ) . then ( ( ) = > {
let stdout = '' ;
let print_fn = function ( s ) { stdout += s + "\n" ; }
var prefix = getFilenamePrefix ( getCurrentMainFilename ( ) ) ;
let rompath = prefix + ".bin" ;
let audpath = prefix + ".wav" ;
let _makewav = window [ 'makewav' ] ( {
noInitialRun :false ,
print :print_fn ,
printErr :print_fn ,
arguments : [ '-ts' , '-f0' , '-v10' , rompath ] ,
preRun : ( mod ) = > {
let FS = mod [ 'FS' ] ;
FS . writeFile ( rompath , current_output , { encoding : 'binary' } ) ;
}
} ) ;
_makewav . ready . then ( ( makewav ) = > {
let args = [ rompath ] ;
makewav . run ( args ) ;
console . log ( stdout ) ;
let FS = makewav [ 'FS' ] ;
let audout = FS . readFile ( audpath , { 'encoding' : 'binary' } ) ;
if ( audout ) {
let blob = new Blob ( [ audout ] , { type : "audio/wav" } ) ;
saveAs ( blob , audpath ) ;
stdout += "\nConnect your audio output to the SuperCharger input, turn up the volume, and play the audio file." ;
alertInfo ( '<pre style="white-space: pre-wrap">' + stdout + '</pre>' ) ;
}
} ) ;
} ) ;
}
function _downloadCassetteFile ( e ) {
if ( current_output == null ) {
alertError ( "Please fix errors before exporting." ) ;
return true ;
}
2021-07-27 02:18:07 +00:00
var fn ;
switch ( getBasePlatform ( platform_id ) ) {
case 'vcs' : fn = _downloadCassetteFile_vcs ; break ;
case 'apple2' : fn = _downloadCassetteFile_apple2 ; break ;
}
2020-07-25 23:33:55 +00:00
if ( fn === undefined ) {
alertError ( "Cassette export is not supported on this platform." ) ;
return true ;
}
fn ( e ) ;
}
2018-08-21 14:16:47 +00:00
function _revertFile ( e ) {
2018-11-29 01:53:31 +00:00
var wnd = projectWindows . getActive ( ) ;
if ( wnd && wnd . setText ) {
2019-05-09 03:10:24 +00:00
var fn = projectWindows . getActiveID ( ) ;
2019-05-12 13:52:09 +00:00
$ . get ( "presets/" + getBasePlatform ( platform_id ) + "/" + fn , ( text ) = > {
bootbox . confirm ( "Reset '" + fn + "' to default?" , ( ok ) = > {
if ( ok ) {
wnd . setText ( text ) ;
}
} ) ;
2018-11-29 01:53:31 +00:00
} , 'text' )
2019-05-06 01:49:08 +00:00
. fail ( ( ) = > {
2019-08-22 14:30:33 +00:00
if ( repo_id ) alertError ( "Can only revert built-in examples. If you want to revert all files, You can pull from the repository." ) ;
else alertError ( "Can only revert built-in examples." ) ;
2018-11-29 01:53:31 +00:00
} ) ;
} else {
2019-05-12 13:52:09 +00:00
alertError ( "Cannot revert the active window. Please choose a text file." ) ;
2018-11-29 01:53:31 +00:00
}
2016-12-16 01:21:51 +00:00
}
2018-12-08 00:28:11 +00:00
function _deleteFile ( e ) {
var wnd = projectWindows . getActive ( ) ;
if ( wnd && wnd . getPath ) {
var fn = projectWindows . getActiveID ( ) ;
2019-05-12 19:39:09 +00:00
bootbox . confirm ( "Delete '" + fn + "'?" , ( ok ) = > {
if ( ok ) {
store . removeItem ( fn ) . then ( ( ) = > {
// if we delete what is selected
2021-08-04 17:00:10 +00:00
if ( qs . file == fn ) {
userPrefs . unsetLastPreset ( ) ;
2019-05-12 19:39:09 +00:00
gotoNewLocation ( ) ;
} else {
updateSelector ( ) ;
alertInfo ( "Deleted " + fn ) ;
}
} ) ;
}
} ) ;
2018-12-08 00:28:11 +00:00
} else {
2019-05-12 13:52:09 +00:00
alertError ( "Cannot delete the active window." ) ;
2018-12-08 00:28:11 +00:00
}
}
function _renameFile ( e ) {
var wnd = projectWindows . getActive ( ) ;
if ( wnd && wnd . getPath && current_project . getFile ( wnd . getPath ( ) ) ) {
var fn = projectWindows . getActiveID ( ) ;
2019-05-23 03:23:55 +00:00
bootbox . prompt ( {
title : "Rename '" + fn + "' to?" ,
value : fn ,
callback : ( newfn ) = > {
var data = current_project . getFile ( wnd . getPath ( ) ) ;
if ( newfn && newfn != fn && data ) {
if ( ! checkEnteredFilename ( newfn ) ) return ;
store . removeItem ( fn ) . then ( ( ) = > {
return store . setItem ( newfn , data ) ;
} ) . then ( ( ) = > {
updateSelector ( ) ;
alert ( "Renamed " + fn + " to " + newfn ) ; // need alert() so it pauses
if ( fn == current_project . mainPath ) {
reloadProject ( newfn ) ;
}
} ) ;
2019-05-10 18:28:09 +00:00
}
2019-05-23 03:23:55 +00:00
}
} ) ;
2018-12-08 00:28:11 +00:00
} else {
2019-05-12 13:52:09 +00:00
alertError ( "Cannot rename the active window." ) ;
2018-12-08 00:28:11 +00:00
}
}
2017-02-02 19:11:52 +00:00
function _downloadROMImage ( e ) {
2018-08-29 12:24:13 +00:00
if ( current_output == null ) {
2019-05-12 13:52:09 +00:00
alertError ( "Please finish compiling with no errors before downloading ROM." ) ;
2017-02-02 19:11:52 +00:00
return true ;
}
2020-07-11 14:51:26 +00:00
var prefix = getFilenamePrefix ( getCurrentMainFilename ( ) ) ;
2021-06-28 20:36:47 +00:00
if ( platform . getDownloadFile ) {
var dl = platform . getDownloadFile ( ) ;
var prefix = getFilenamePrefix ( getCurrentMainFilename ( ) ) ;
saveAs ( dl . blob , prefix + dl . extension ) ;
} else if ( current_output instanceof Uint8Array ) {
2018-08-29 12:24:13 +00:00
var blob = new Blob ( [ current_output ] , { type : "application/octet-stream" } ) ;
2020-07-11 14:51:26 +00:00
var suffix = ( platform . getROMExtension && platform . getROMExtension ( current_output ) )
|| "-" + getBasePlatform ( platform_id ) + ".bin" ;
saveAs ( blob , prefix + suffix ) ;
2020-08-27 01:42:18 +00:00
} else {
alertError ( ` The " ${ platform_id } " platform doesn't have downloadable ROMs. ` ) ;
2018-08-29 12:24:13 +00:00
}
2017-02-02 19:11:52 +00:00
}
2018-03-23 21:05:08 +00:00
function _downloadSourceFile ( e ) {
2018-07-03 14:22:21 +00:00
var text = projectWindows . getCurrentText ( ) ;
if ( ! text ) return false ;
2018-12-08 12:35:48 +00:00
var blob = new Blob ( [ text ] , { type : "text/plain;charset=utf-8" } ) ;
saveAs ( blob , getCurrentEditorFilename ( ) , { autoBom :false } ) ;
2018-03-23 21:05:08 +00:00
}
2021-08-03 17:13:59 +00:00
async function newJSZip() {
let JSZip = ( await import ( 'jszip' ) ) . default ;
return new JSZip ( ) ;
}
async function _downloadProjectZipFile ( e ) {
var zip = await newJSZip ( ) ;
current_project . iterateFiles ( ( id , data ) = > {
if ( data ) {
zip . file ( getFilenameForPath ( id ) , data ) ;
}
} ) ;
zip . generateAsync ( { type : "blob" } ) . then ( ( content ) = > {
saveAs ( content , getCurrentMainFilename ( ) + "-" + getBasePlatform ( platform_id ) + ".zip" ) ;
2018-08-25 18:29:51 +00:00
} ) ;
}
2021-08-03 17:13:59 +00:00
async function _downloadAllFilesZipFile ( e ) {
var zip = await newJSZip ( ) ;
2021-08-06 16:51:40 +00:00
var keys = await store . keys ( ) ;
setWaitDialog ( true ) ;
try {
2021-08-03 17:13:59 +00:00
var i = 0 ;
2021-08-06 16:51:40 +00:00
await Promise . all ( keys . map ( ( path ) = > {
2021-08-03 17:13:59 +00:00
return store . getItem ( path ) . then ( ( text ) = > {
setWaitProgress ( i ++ / ( k e y s . l e n g t h + 1 ) ) ;
if ( text ) {
zip . file ( path , text as any ) ;
}
} ) ;
2021-08-06 16:51:40 +00:00
} ) ) ;
var content = await zip . generateAsync ( { type : "blob" } ) ;
saveAs ( content , getBasePlatform ( platform_id ) + "-all.zip" ) ;
} finally {
setWaitDialog ( false ) ;
}
2018-08-26 13:19:09 +00:00
}
2016-12-16 01:21:51 +00:00
function populateExamples ( sel ) {
2019-05-12 19:39:09 +00:00
var files = { } ;
sel . append ( $ ( "<option />" ) . text ( "--------- Examples ---------" ) . attr ( 'disabled' , 'true' ) ) ;
for ( var i = 0 ; i < PRESETS.length ; i + + ) {
var preset = PRESETS [ i ] ;
var name = preset . chapter ? ( preset . chapter + ". " + preset . name ) : preset . name ;
2019-08-20 23:13:41 +00:00
var isCurrentPreset = preset . id == current_project . mainPath ;
sel . append ( $ ( "<option />" ) . val ( preset . id ) . text ( name ) . attr ( 'selected' , isCurrentPreset ? 'selected' : null ) ) ;
if ( isCurrentPreset ) current_preset = preset ;
2019-05-12 19:39:09 +00:00
files [ preset . id ] = name ;
}
return files ;
2016-12-16 01:21:51 +00:00
}
2019-05-09 17:22:24 +00:00
function populateRepos ( sel ) {
2020-08-27 01:42:18 +00:00
if ( hasLocalStorage && ! isElectron ) {
2019-05-09 17:22:24 +00:00
var n = 0 ;
var repos = getRepos ( ) ;
if ( repos ) {
for ( let repopath in repos ) {
var repo = repos [ repopath ] ;
2019-05-22 17:58:44 +00:00
if ( repo . platform_id && getBasePlatform ( repo . platform_id ) == getBasePlatform ( platform_id ) ) {
2019-05-09 17:22:24 +00:00
if ( n ++ == 0 )
sel . append ( $ ( "<option />" ) . text ( "------ Repositories ------" ) . attr ( 'disabled' , 'true' ) ) ;
sel . append ( $ ( "<option />" ) . val ( repo . url ) . text ( repo . url . substring ( repo . url . indexOf ( '/' ) ) ) ) ;
}
}
}
}
}
2019-12-18 17:20:15 +00:00
async function populateFiles ( sel :JQuery , category :string , prefix :string , foundFiles : { } ) {
var keys = await store . keys ( ) ;
var numFound = 0 ;
if ( ! keys ) keys = [ ] ;
for ( var i = 0 ; i < keys . length ; i ++ ) {
var key = keys [ i ] ;
if ( key . startsWith ( prefix ) && ! foundFiles [ key ] ) {
if ( numFound ++ == 0 )
sel . append ( $ ( "<option />" ) . text ( "------- " + category + " -------" ) . attr ( 'disabled' , 'true' ) ) ;
var name = key . substring ( prefix . length ) ;
sel . append ( $ ( "<option />" ) . val ( key ) . text ( name ) . attr ( 'selected' , ( key == current_project . mainPath ) ? 'selected' : null ) ) ;
2018-06-26 06:56:36 +00:00
}
2019-12-18 17:20:15 +00:00
}
2016-12-16 01:21:51 +00:00
}
2019-05-14 17:56:48 +00:00
function finishSelector ( sel ) {
sel . css ( 'visibility' , 'visible' ) ;
// create option if not selected
var main = current_project . mainPath ;
if ( sel . val ( ) != main ) {
sel . append ( $ ( "<option />" ) . val ( main ) . text ( main ) . attr ( 'selected' , 'selected' ) ) ;
}
}
2019-12-18 17:20:15 +00:00
async function updateSelector() {
2016-12-16 01:21:51 +00:00
var sel = $ ( "#preset_select" ) . empty ( ) ;
2019-05-08 23:15:26 +00:00
if ( ! repo_id ) {
2019-05-12 19:39:09 +00:00
// normal: populate repos, examples, and local files
2019-05-12 17:33:21 +00:00
populateRepos ( sel ) ;
2019-05-12 19:39:09 +00:00
var foundFiles = populateExamples ( sel ) ;
2019-12-18 17:20:15 +00:00
await populateFiles ( sel , "Local Files" , "" , foundFiles ) ;
finishSelector ( sel ) ;
2019-05-08 23:15:26 +00:00
} else {
2021-07-10 19:28:02 +00:00
sel . append ( $ ( "<option />" ) . val ( '/' ) . text ( 'Leave Repository' ) ) ;
2020-07-12 22:21:54 +00:00
$ ( "#repo_name" ) . text ( getFilenameForPath ( repo_id ) + '/' ) . show ( ) ;
2019-05-08 23:15:26 +00:00
// repo: populate all files
2019-12-18 17:20:15 +00:00
await populateFiles ( sel , repo_id , "" , { } ) ;
finishSelector ( sel ) ;
2019-05-08 23:15:26 +00:00
}
2016-12-16 01:21:51 +00:00
// set click handlers
sel . off ( 'change' ) . change ( function ( e ) {
2019-05-08 23:15:26 +00:00
reloadProject ( $ ( this ) . val ( ) . toString ( ) ) ;
2016-12-16 01:21:51 +00:00
} ) ;
}
2020-08-09 20:52:26 +00:00
function getErrorElement ( err : WorkerError ) {
var span = $ ( '<p/>' ) ;
if ( err . path != null ) {
2020-08-10 01:40:17 +00:00
var s = err . line ? err . label ? ` ( ${ err . path } @ ${ err . label } ) ` : ` ( ${ err . path } : ${ err . line } ) ` : ` ( ${ err . path } ) `
2020-08-09 20:52:26 +00:00
var link = $ ( '<a/>' ) . text ( s ) ;
var path = err . path ;
// TODO: hack because examples/foo.a only gets listed as foo.a
if ( path == getCurrentMainFilename ( ) ) path = current_project . mainPath ;
// click link to open file, if it's available...
if ( projectWindows . isWindow ( path ) ) {
link . click ( ( ev ) = > {
var wnd = projectWindows . createOrShow ( path ) ;
2021-08-04 22:14:26 +00:00
if ( wnd instanceof SourceEditor ) {
2020-08-11 02:35:25 +00:00
wnd . setCurrentLine ( err , true ) ;
2020-08-09 20:52:26 +00:00
}
} ) ;
}
span . append ( link ) ;
span . append ( ' ' ) ;
}
span . append ( $ ( '<span/>' ) . text ( err . msg ) ) ;
return span ;
}
2020-08-23 18:40:04 +00:00
function hideErrorAlerts() {
$ ( "#error_alert" ) . hide ( ) ;
2021-06-06 05:50:45 +00:00
errorWasRuntime = false ;
2020-08-23 18:40:04 +00:00
}
2021-06-06 05:50:45 +00:00
function showErrorAlert ( errors : WorkerError [ ] , runtime : boolean ) {
2018-09-05 02:28:12 +00:00
var div = $ ( "#error_alert_msg" ) . empty ( ) ;
for ( var err of errors . slice ( 0 , 10 ) ) {
2020-08-09 20:52:26 +00:00
div . append ( getErrorElement ( err ) ) ;
2018-09-05 02:28:12 +00:00
}
$ ( "#error_alert" ) . show ( ) ;
2021-06-06 05:50:45 +00:00
errorWasRuntime = runtime ;
2018-09-05 02:28:12 +00:00
}
2020-08-11 17:29:31 +00:00
function showExceptionAsError ( err , msg :string ) {
2021-03-02 21:51:27 +00:00
if ( msg != null ) {
var werr : WorkerError = { msg :msg , line :0 } ;
if ( err instanceof EmuHalt && err . $loc ) {
werr = Object . create ( err . $loc ) ;
werr . msg = msg ;
console . log ( werr ) ;
}
2021-06-06 05:50:45 +00:00
showErrorAlert ( [ werr ] , true ) ;
2020-08-11 17:29:31 +00:00
}
}
2019-08-19 15:16:02 +00:00
var measureTimeStart : Date = new Date ( ) ;
var measureTimeLoad : Date ;
function measureBuildTime() {
2020-07-30 19:43:55 +00:00
if ( window [ 'ga' ] && measureTimeLoad ) {
2019-08-19 15:16:02 +00:00
var measureTimeBuild = new Date ( ) ;
2019-08-22 17:23:04 +00:00
ga ( 'send' , 'timing' , 'load' , platform_id , ( measureTimeLoad . getTime ( ) - measureTimeStart . getTime ( ) ) ) ;
ga ( 'send' , 'timing' , 'build' , platform_id , ( measureTimeBuild . getTime ( ) - measureTimeLoad . getTime ( ) ) ) ;
2019-08-19 15:16:02 +00:00
measureTimeLoad = null ; // only measure once
}
2019-09-29 13:36:20 +00:00
//gaEvent('build', platform_id);
2019-08-19 15:16:02 +00:00
}
2021-07-09 18:57:54 +00:00
async function setCompileOutput ( data : WorkerResult ) {
2018-07-02 13:34:20 +00:00
// errors? mark them in editor
2021-08-08 18:40:19 +00:00
if ( 'errors' in data && data . errors . length > 0 ) {
2018-07-03 04:21:08 +00:00
toolbar . addClass ( "has-errors" ) ;
2018-09-05 02:28:12 +00:00
projectWindows . setErrors ( data . errors ) ;
2021-06-12 16:29:49 +00:00
refreshWindowList ( ) ; // to make sure windows are created for showErrorAlert()
2021-06-06 05:50:45 +00:00
showErrorAlert ( data . errors , false ) ;
2016-12-16 01:21:51 +00:00
} else {
2020-10-16 11:14:40 +00:00
toolbar . removeClass ( "has-errors" ) ; // may be added in next callback
projectWindows . setErrors ( null ) ;
hideErrorAlerts ( ) ;
// exit if compile output unchanged
2021-08-08 18:40:19 +00:00
if ( data == null || ( 'unchanged' in data && data . unchanged ) ) return ;
// make sure it's a WorkerOutputResult
if ( ! ( 'output' in data ) ) return ;
2018-07-03 14:22:21 +00:00
// process symbol map
2020-07-08 01:56:44 +00:00
platform . debugSymbols = new DebugSymbols ( data . symbolmap , data . debuginfo ) ;
2018-06-30 04:42:21 +00:00
compparams = data . params ;
2016-12-16 01:21:51 +00:00
// load ROM
2017-03-24 22:10:35 +00:00
var rom = data . output ;
2021-08-08 18:40:19 +00:00
if ( rom != null ) {
2016-12-16 01:21:51 +00:00
try {
2018-08-19 23:25:42 +00:00
clearBreakpoint ( ) ; // so we can replace memory (TODO: change toolbar btn)
2018-08-23 22:52:56 +00:00
_resetRecording ( ) ;
2021-07-09 18:57:54 +00:00
await platform . loadROM ( getCurrentPresetTitle ( ) , rom ) ;
2016-12-16 01:21:51 +00:00
current_output = rom ;
2018-11-12 12:38:02 +00:00
if ( ! userPaused ) _resume ( ) ;
2019-08-19 15:16:02 +00:00
measureBuildTime ( ) ;
2020-08-27 01:42:18 +00:00
writeOutputROMFile ( ) ;
2016-12-16 01:21:51 +00:00
} catch ( e ) {
2018-07-03 14:22:21 +00:00
console . log ( e ) ;
2017-01-16 19:13:03 +00:00
toolbar . addClass ( "has-errors" ) ;
2020-08-11 17:29:31 +00:00
showExceptionAsError ( e , e + "" ) ;
2016-12-16 01:21:51 +00:00
current_output = null ;
2021-06-28 20:36:47 +00:00
refreshWindowList ( ) ;
2018-08-26 13:19:09 +00:00
return ;
2016-12-16 01:21:51 +00:00
}
}
2018-07-03 14:22:21 +00:00
// update all windows (listings)
2020-10-16 11:14:40 +00:00
refreshWindowList ( ) ;
2018-08-02 17:08:37 +00:00
projectWindows . refresh ( false ) ;
2016-12-16 01:21:51 +00:00
}
}
2019-12-18 17:20:15 +00:00
async function loadBIOSFromProject() {
2019-05-08 02:36:06 +00:00
if ( platform . loadBIOS ) {
2019-05-12 19:39:09 +00:00
var biospath = platform_id + '.rom' ;
2019-12-18 17:20:15 +00:00
var biosdata = await store . getItem ( biospath ) ;
if ( biosdata instanceof Uint8Array ) {
2020-07-18 20:38:39 +00:00
console . log ( 'loading BIOS' , biospath , biosdata . length + " bytes" )
platform . loadBIOS ( biospath , biosdata ) ;
2019-12-18 17:20:15 +00:00
} else {
console . log ( 'BIOS file must be binary' )
}
2019-05-08 02:36:06 +00:00
}
}
2021-07-15 21:54:35 +00:00
function hideDebugInfo() {
var meminfo = $ ( "#mem_info" ) ;
meminfo . hide ( ) ;
lastDebugInfo = null ;
}
2018-07-29 20:26:05 +00:00
function showDebugInfo ( state ? ) {
2019-08-23 19:05:12 +00:00
if ( ! isDebuggable ( platform ) ) return ;
2018-07-29 19:53:50 +00:00
var meminfo = $ ( "#mem_info" ) ;
2019-08-23 19:05:12 +00:00
var allcats = platform . getDebugCategories ( ) ;
2018-07-29 20:26:05 +00:00
if ( allcats && ! debugCategory )
debugCategory = allcats [ 0 ] ;
2019-08-23 19:05:12 +00:00
var s = state && platform . getDebugInfo ( debugCategory , state ) ;
2018-06-18 08:12:52 +00:00
if ( s ) {
2016-12-16 01:21:51 +00:00
var hs = lastDebugInfo ? highlightDifferences ( lastDebugInfo , s ) : s ;
2018-07-29 19:53:50 +00:00
meminfo . show ( ) . html ( hs ) ;
2018-11-24 21:08:33 +00:00
var catspan = $ ( '<div class="mem_info_links">' ) ;
2018-07-29 20:26:05 +00:00
var addCategoryLink = ( cat :string ) = > {
var catlink = $ ( '<a>' + cat + '</a>' ) ;
if ( cat == debugCategory )
catlink . addClass ( 'selected' ) ;
catlink . click ( ( e ) = > {
debugCategory = cat ;
lastDebugInfo = null ;
showDebugInfo ( lastDebugState ) ;
} ) ;
catspan . append ( catlink ) ;
catspan . append ( '<span> </span>' ) ;
}
for ( var cat of allcats ) {
addCategoryLink ( cat ) ;
}
meminfo . append ( '<br>' ) ;
meminfo . append ( catspan ) ;
2016-12-16 01:21:51 +00:00
lastDebugInfo = s ;
} else {
2021-07-15 21:54:35 +00:00
hideDebugInfo ( ) ;
2016-12-16 01:21:51 +00:00
}
}
2018-07-08 14:07:19 +00:00
function setDebugButtonState ( btnid :string , btnstate :string ) {
2020-08-12 15:47:46 +00:00
$ ( "#debug_bar, #run_bar" ) . find ( "button" ) . removeClass ( "btn_active" ) . removeClass ( "btn_stopped" ) ;
2018-07-07 05:09:15 +00:00
$ ( "#dbg_" + btnid ) . addClass ( "btn_" + btnstate ) ;
}
2021-08-06 01:59:36 +00:00
function isPlatformReady() {
return platform && current_output != null ;
}
2018-11-24 21:51:29 +00:00
function checkRunReady() {
2021-08-06 01:59:36 +00:00
if ( ! isPlatformReady ( ) ) {
2019-05-21 17:54:51 +00:00
alertError ( "Can't do this until build successfully completes." ) ;
2018-11-24 21:51:29 +00:00
return false ;
} else
return true ;
}
2019-09-29 13:36:20 +00:00
function openRelevantListing ( state : EmuState ) {
2020-01-04 19:17:42 +00:00
// if we clicked on another window, retain it
if ( lastViewClicked != null ) return ;
2020-07-09 18:27:56 +00:00
// has to support disassembly, at least
if ( ! platform . disassemble ) return ;
2019-12-27 19:19:11 +00:00
// search through listings
2019-09-29 13:36:20 +00:00
var listings = current_project . getListings ( ) ;
2019-12-27 23:03:09 +00:00
var bestid = "#disasm" ;
var bestscore = 32 ;
2019-09-29 13:36:20 +00:00
if ( listings ) {
var pc = state . c ? ( state . c . EPC || state . c . PC ) : 0 ;
for ( var lstfn in listings ) {
2019-12-28 19:49:09 +00:00
var lst = listings [ lstfn ] ;
var file = lst . assemblyfile || lst . sourcefile ;
// pick either listing or source file
var wndid = current_project . filename2path [ lstfn ] || lstfn ;
if ( file == lst . sourcefile ) wndid = projectWindows . findWindowWithFilePrefix ( lstfn ) ;
// does this window exist?
2019-12-27 19:19:11 +00:00
if ( projectWindows . isWindow ( wndid ) ) {
2019-12-27 23:03:09 +00:00
var res = file && file . findLineForOffset ( pc , 32 ) ; // TODO: const
if ( res && pc - res . offset < bestscore ) {
bestid = wndid ;
bestscore = pc - res . offset ;
2019-12-27 19:19:11 +00:00
}
2020-07-09 18:27:56 +00:00
//console.log(hex(pc,4), wndid, lstfn, bestid, bestscore);
2019-09-29 13:36:20 +00:00
}
}
}
2019-12-27 19:19:11 +00:00
// if no appropriate listing found, use disassembly view
2019-12-27 23:03:09 +00:00
projectWindows . createOrShow ( bestid , true ) ;
2019-09-29 13:36:20 +00:00
}
function uiDebugCallback ( state : EmuState ) {
2019-04-03 20:40:16 +00:00
lastDebugState = state ;
showDebugInfo ( state ) ;
2019-12-27 23:03:09 +00:00
openRelevantListing ( state ) ;
2019-09-29 13:36:20 +00:00
projectWindows . refresh ( true ) ; // move cursor
2019-04-07 01:47:42 +00:00
debugTickPaused = true ;
2019-04-03 20:40:16 +00:00
}
2018-09-15 19:27:12 +00:00
function setupDebugCallback ( btnid? : string ) {
2019-12-01 17:47:42 +00:00
if ( platform . setupDebug ) platform . setupDebug ( ( state :EmuState , msg :string ) = > {
2019-04-03 20:40:16 +00:00
uiDebugCallback ( state ) ;
2018-09-15 19:27:12 +00:00
setDebugButtonState ( btnid || "pause" , "stopped" ) ;
2021-06-06 05:50:45 +00:00
msg && showErrorAlert ( [ { msg : "STOPPED: " + msg , line :0 } ] , true ) ;
2016-12-31 16:05:22 +00:00
} ) ;
2018-09-15 19:27:12 +00:00
}
function setupBreakpoint ( btnid? : string ) {
2018-11-24 21:51:29 +00:00
if ( ! checkRunReady ( ) ) return ;
2018-09-15 19:27:12 +00:00
_disableRecording ( ) ;
setupDebugCallback ( btnid ) ;
2018-07-07 05:09:15 +00:00
if ( btnid ) setDebugButtonState ( btnid , "active" ) ;
2016-12-16 01:21:51 +00:00
}
2017-11-16 17:08:06 +00:00
function _pause() {
2019-01-17 18:04:42 +00:00
if ( platform && platform . isRunning ( ) ) {
2016-12-31 16:05:22 +00:00
platform . pause ( ) ;
2018-02-21 18:54:53 +00:00
console . log ( "Paused" ) ;
2016-12-16 01:21:51 +00:00
}
2018-07-07 05:09:15 +00:00
setDebugButtonState ( "pause" , "stopped" ) ;
2016-12-16 01:21:51 +00:00
}
2017-11-16 17:08:06 +00:00
function pause() {
2021-08-18 01:01:43 +00:00
if ( ! checkRunReady ( ) ) return ;
2016-12-16 01:21:51 +00:00
clearBreakpoint ( ) ;
2017-11-16 17:08:06 +00:00
_pause ( ) ;
2017-11-28 02:08:19 +00:00
userPaused = true ;
2017-11-16 17:08:06 +00:00
}
function _resume() {
2018-11-12 12:38:02 +00:00
if ( ! platform . isRunning ( ) ) {
2016-12-31 16:05:22 +00:00
platform . resume ( ) ;
2018-02-21 18:54:53 +00:00
console . log ( "Resumed" ) ;
2016-12-16 01:21:51 +00:00
}
2018-07-07 05:09:15 +00:00
setDebugButtonState ( "go" , "active" ) ;
2021-06-06 05:50:45 +00:00
if ( errorWasRuntime ) { hideErrorAlerts ( ) ; }
2017-11-16 17:08:06 +00:00
}
function resume() {
2021-08-18 01:01:43 +00:00
if ( ! checkRunReady ( ) ) return ;
2017-11-16 17:08:06 +00:00
clearBreakpoint ( ) ;
2018-06-24 17:39:08 +00:00
if ( ! platform . isRunning ( ) ) {
2018-08-02 17:08:37 +00:00
projectWindows . refresh ( false ) ;
2018-06-24 17:39:08 +00:00
}
2017-11-16 17:08:06 +00:00
_resume ( ) ;
2017-11-28 02:08:19 +00:00
userPaused = false ;
2019-12-27 23:03:09 +00:00
lastViewClicked = null ;
2016-12-16 01:21:51 +00:00
}
function singleStep() {
2019-05-21 17:54:51 +00:00
if ( ! checkRunReady ( ) ) return ;
2018-07-07 05:09:15 +00:00
setupBreakpoint ( "step" ) ;
2016-12-31 16:05:22 +00:00
platform . step ( ) ;
2016-12-16 01:21:51 +00:00
}
2020-08-13 17:32:47 +00:00
function stepOver() {
if ( ! checkRunReady ( ) ) return ;
setupBreakpoint ( "stepover" ) ;
platform . stepOver ( ) ;
}
2017-11-24 19:14:22 +00:00
function singleFrameStep() {
2019-05-21 17:54:51 +00:00
if ( ! checkRunReady ( ) ) return ;
2018-07-07 05:09:15 +00:00
setupBreakpoint ( "tovsync" ) ;
2017-11-24 19:14:22 +00:00
platform . runToVsync ( ) ;
}
2018-07-08 14:07:19 +00:00
function getEditorPC ( ) : number {
2018-07-03 14:22:21 +00:00
var wnd = projectWindows . getActive ( ) ;
return wnd && wnd . getCursorPC && wnd . getCursorPC ( ) ;
2017-02-05 04:19:54 +00:00
}
2020-01-04 23:11:14 +00:00
export function runToPC ( pc : number ) {
if ( ! checkRunReady ( ) || ! ( pc >= 0 ) ) return ;
2018-07-07 05:09:15 +00:00
setupBreakpoint ( "toline" ) ;
2020-01-04 23:11:14 +00:00
console . log ( "Run to" , pc . toString ( 16 ) ) ;
if ( platform . runToPC ) {
platform . runToPC ( pc ) ;
} else {
platform . runEval ( ( c ) = > {
return c . PC == pc ;
} ) ;
2016-12-16 01:21:51 +00:00
}
}
2020-08-23 18:40:04 +00:00
function restartAtCursor() {
if ( platform . restartAtPC ( getEditorPC ( ) ) ) {
resume ( ) ;
} else alertError ( ` Could not restart program at selected line. ` ) ;
}
2020-01-04 23:11:14 +00:00
function runToCursor() {
runToPC ( getEditorPC ( ) ) ;
}
2017-01-07 18:05:02 +00:00
function runUntilReturn() {
2019-05-21 17:54:51 +00:00
if ( ! checkRunReady ( ) ) return ;
2018-07-07 05:09:15 +00:00
setupBreakpoint ( "stepout" ) ;
2017-01-14 02:31:04 +00:00
platform . runUntilReturn ( ) ;
2017-01-07 18:05:02 +00:00
}
function runStepBackwards() {
2019-05-21 17:54:51 +00:00
if ( ! checkRunReady ( ) ) return ;
2018-07-07 05:09:15 +00:00
setupBreakpoint ( "stepback" ) ;
2017-01-07 18:05:02 +00:00
platform . stepBack ( ) ;
}
2016-12-16 01:21:51 +00:00
function clearBreakpoint() {
2017-01-06 14:49:07 +00:00
lastDebugState = null ;
2017-11-11 19:45:32 +00:00
if ( platform . clearDebug ) platform . clearDebug ( ) ;
2018-09-15 19:27:12 +00:00
setupDebugCallback ( ) ; // in case of BRK/trap
2018-07-29 20:26:05 +00:00
showDebugInfo ( ) ;
2016-12-16 01:21:51 +00:00
}
2020-10-17 19:34:08 +00:00
function resetPlatform() {
platform . reset ( ) ;
2021-05-27 15:38:45 +00:00
_resetRecording ( ) ;
2020-10-17 19:34:08 +00:00
}
2020-08-12 15:47:46 +00:00
function resetAndRun() {
if ( ! checkRunReady ( ) ) return ;
clearBreakpoint ( ) ;
2020-10-17 19:34:08 +00:00
resetPlatform ( ) ;
2020-08-12 15:47:46 +00:00
_resume ( ) ;
}
2016-12-16 01:21:51 +00:00
function resetAndDebug() {
2019-05-21 17:54:51 +00:00
if ( ! checkRunReady ( ) ) return ;
2020-07-04 14:16:45 +00:00
var wasRecording = recorderActive ;
2018-08-23 22:52:56 +00:00
_disableRecording ( ) ;
2020-08-13 17:32:47 +00:00
if ( platform . setupDebug && platform . runEval ) { // TODO??
2017-11-11 19:45:32 +00:00
clearBreakpoint ( ) ;
2017-11-16 17:08:06 +00:00
_resume ( ) ;
2020-10-17 19:34:08 +00:00
resetPlatform ( ) ;
2020-08-12 15:47:46 +00:00
setupBreakpoint ( "restart" ) ;
2020-08-13 17:32:47 +00:00
platform . runEval ( ( c ) = > { return true ; } ) ; // break immediately
2017-11-11 19:45:32 +00:00
} else {
2020-10-17 19:34:08 +00:00
resetPlatform ( ) ;
2020-08-13 17:32:47 +00:00
_resume ( ) ;
2017-11-11 19:45:32 +00:00
}
2020-07-04 14:16:45 +00:00
if ( wasRecording ) _enableRecording ( ) ;
2017-01-03 01:42:15 +00:00
}
2017-01-06 16:57:28 +00:00
2017-01-07 18:05:02 +00:00
function _breakExpression() {
2019-05-14 13:38:11 +00:00
var modal = $ ( "#debugExprModal" ) ;
var btn = $ ( "#debugExprSubmit" ) ;
$ ( "#debugExprInput" ) . val ( lastBreakExpr ) ;
$ ( "#debugExprExamples" ) . text ( getDebugExprExamples ( ) ) ;
modal . modal ( 'show' ) ;
btn . off ( 'click' ) . on ( 'click' , ( ) = > {
var exprs = $ ( "#debugExprInput" ) . val ( ) + "" ;
modal . modal ( 'hide' ) ;
breakExpression ( exprs ) ;
} ) ;
}
function getDebugExprExamples ( ) : string {
var state = platform . saveState && platform . saveState ( ) ;
2019-05-14 16:01:54 +00:00
var cpu = state . c ;
2019-05-14 13:38:11 +00:00
console . log ( cpu , state ) ;
var s = '' ;
if ( cpu . PC ) s += "c.PC == 0x" + hex ( cpu . PC ) + "\n" ;
if ( cpu . SP ) s += "c.SP < 0x" + hex ( cpu . SP ) + "\n" ;
2019-06-03 14:08:29 +00:00
if ( cpu [ 'HL' ] ) s += "c.HL == 0x4000\n" ;
2019-05-14 13:38:11 +00:00
if ( platform . readAddress ) s += "this.readAddress(0x1234) == 0x0\n" ;
if ( platform . readVRAMAddress ) s += "this.readVRAMAddress(0x1234) != 0x80\n" ;
2019-05-31 19:05:33 +00:00
if ( platform [ 'getRasterScanline' ] ) s += "this.getRasterScanline() > 222\n" ;
2019-05-14 13:38:11 +00:00
return s ;
}
function breakExpression ( exprs : string ) {
var fn = new Function ( 'c' , 'return (' + exprs + ');' ) . bind ( platform ) ;
setupBreakpoint ( ) ;
platform . runEval ( fn as DebugEvalCondition ) ;
lastBreakExpr = exprs ;
2017-01-07 18:05:02 +00:00
}
2017-04-19 01:18:53 +00:00
function updateDebugWindows() {
if ( platform . isRunning ( ) ) {
2018-07-02 13:34:20 +00:00
projectWindows . tick ( ) ;
2019-03-03 20:37:22 +00:00
debugTickPaused = false ;
} else if ( ! debugTickPaused ) { // final tick after pausing
projectWindows . tick ( ) ;
debugTickPaused = true ;
2017-04-19 01:18:53 +00:00
}
2019-08-24 15:28:04 +00:00
setTimeout ( updateDebugWindows , 100 ) ;
2017-04-19 01:18:53 +00:00
}
2019-05-01 19:28:15 +00:00
function setWaitDialog ( b : boolean ) {
if ( b ) {
2019-05-09 12:44:47 +00:00
setWaitProgress ( 0 ) ;
2019-05-01 19:28:15 +00:00
$ ( "#pleaseWaitModal" ) . modal ( 'show' ) ;
} else {
2019-05-09 12:44:47 +00:00
setWaitProgress ( 1 ) ;
2019-05-01 19:28:15 +00:00
$ ( "#pleaseWaitModal" ) . modal ( 'hide' ) ;
}
}
2019-05-07 19:37:37 +00:00
function setWaitProgress ( prog : number ) {
$ ( "#pleaseWaitProgressBar" ) . css ( 'width' , ( prog * 100 ) + '%' ) . show ( ) ;
}
2019-05-01 20:47:04 +00:00
var recordingVideo = false ;
2017-05-20 19:13:23 +00:00
function _recordVideo() {
2019-05-01 20:47:04 +00:00
if ( recordingVideo ) return ;
2020-12-08 16:32:46 +00:00
loadScript ( "lib/gif.js" ) . then ( ( ) = > {
2018-10-11 15:33:09 +00:00
var canvas = $ ( "#emulator" ) . find ( "canvas" ) [ 0 ] as HTMLElement ;
2017-05-20 19:13:23 +00:00
if ( ! canvas ) {
2019-05-12 13:52:09 +00:00
alertError ( "Could not find canvas element to record video!" ) ;
2017-05-20 19:13:23 +00:00
return ;
}
2017-05-25 19:49:30 +00:00
var rotate = 0 ;
if ( canvas . style && canvas . style . transform ) {
if ( canvas . style . transform . indexOf ( "rotate(-90deg)" ) >= 0 )
rotate = - 1 ;
else if ( canvas . style . transform . indexOf ( "rotate(90deg)" ) >= 0 )
rotate = 1 ;
}
var gif = new GIF ( {
2020-12-08 16:32:46 +00:00
workerScript : 'lib/gif.worker.js' ,
2017-05-25 19:49:30 +00:00
workers : 4 ,
quality : 10 ,
rotate : rotate
} ) ;
2017-05-20 19:13:23 +00:00
var img = $ ( '#videoPreviewImage' ) ;
2019-05-07 19:37:37 +00:00
gif . on ( 'progress' , ( prog ) = > {
setWaitProgress ( prog ) ;
} ) ;
gif . on ( 'finished' , ( blob ) = > {
2017-05-20 19:13:23 +00:00
img . attr ( 'src' , URL . createObjectURL ( blob ) ) ;
2019-05-01 19:28:15 +00:00
setWaitDialog ( false ) ;
2017-11-16 17:08:06 +00:00
_resume ( ) ;
2017-05-20 19:13:23 +00:00
$ ( "#videoPreviewModal" ) . modal ( 'show' ) ;
} ) ;
2021-04-14 23:21:33 +00:00
var intervalMsec = 20 ;
var maxFrames = 300 ;
2017-05-20 19:13:23 +00:00
var nframes = 0 ;
console . log ( "Recording video" , canvas ) ;
2019-05-01 20:47:04 +00:00
$ ( "#emulator" ) . css ( 'backgroundColor' , '#cc3333' ) ;
2019-05-06 01:49:08 +00:00
var f = ( ) = > {
2017-05-20 19:13:23 +00:00
if ( nframes ++ > maxFrames ) {
console . log ( "Rendering video" ) ;
2019-05-01 20:47:04 +00:00
$ ( "#emulator" ) . css ( 'backgroundColor' , 'inherit' ) ;
2019-05-01 19:28:15 +00:00
setWaitDialog ( true ) ;
2017-11-16 17:08:06 +00:00
_pause ( ) ;
2017-05-20 19:13:23 +00:00
gif . render ( ) ;
2019-05-01 20:47:04 +00:00
recordingVideo = false ;
2017-05-20 19:13:23 +00:00
} else {
2017-05-25 19:49:30 +00:00
gif . addFrame ( canvas , { delay : intervalMsec , copy : true } ) ;
2017-05-20 19:13:23 +00:00
setTimeout ( f , intervalMsec ) ;
2019-05-01 20:47:04 +00:00
recordingVideo = true ;
2017-05-20 19:13:23 +00:00
}
} ;
f ( ) ;
2018-08-24 03:58:35 +00:00
} ) ;
2017-05-20 19:13:23 +00:00
}
2019-05-02 02:07:17 +00:00
export function setFrameRateUI ( fps :number ) {
2018-02-26 23:18:23 +00:00
platform . setFrameRate ( fps ) ;
if ( fps > 0.01 )
$ ( "#fps_label" ) . text ( fps . toFixed ( 2 ) ) ;
else
$ ( "#fps_label" ) . text ( "1/" + Math . round ( 1 / fps ) ) ;
}
function _slowerFrameRate() {
var fps = platform . getFrameRate ( ) ;
fps = fps / 2 ;
if ( fps > 0.00001 ) setFrameRateUI ( fps ) ;
}
function _fasterFrameRate() {
var fps = platform . getFrameRate ( ) ;
fps = Math . min ( 60 , fps * 2 ) ;
setFrameRateUI ( fps ) ;
}
2018-02-27 04:48:36 +00:00
function _slowestFrameRate() {
setFrameRateUI ( 60 / 65536 ) ;
}
function _fastestFrameRate() {
2018-07-07 05:09:15 +00:00
_resume ( ) ;
2018-02-27 04:48:36 +00:00
setFrameRateUI ( 60 ) ;
}
2018-07-04 15:36:32 +00:00
function traceTiming() {
2018-08-02 17:08:37 +00:00
projectWindows . refresh ( false ) ;
2018-07-04 15:36:32 +00:00
var wnd = projectWindows . getActive ( ) ;
2018-08-17 19:13:58 +00:00
if ( wnd . getSourceFile && wnd . setTimingResult ) { // is editor active?
var analyzer = platform . newCodeAnalyzer ( ) ;
analyzer . showLoopTimingForPC ( 0 ) ;
wnd . setTimingResult ( analyzer ) ;
2018-07-04 15:36:32 +00:00
}
2018-06-30 02:06:14 +00:00
}
2018-08-22 03:39:34 +00:00
function _disableRecording() {
if ( recorderActive ) {
platform . setRecorder ( null ) ;
$ ( "#dbg_record" ) . removeClass ( "btn_recording" ) ;
$ ( "#replaydiv" ) . hide ( ) ;
2021-07-15 21:54:35 +00:00
hideDebugInfo ( ) ;
2018-08-22 03:39:34 +00:00
recorderActive = false ;
}
}
2018-08-23 22:52:56 +00:00
function _resetRecording() {
if ( recorderActive ) {
stateRecorder . reset ( ) ;
}
}
2018-08-22 03:39:34 +00:00
function _enableRecording() {
stateRecorder . reset ( ) ;
platform . setRecorder ( stateRecorder ) ;
$ ( "#dbg_record" ) . addClass ( "btn_recording" ) ;
$ ( "#replaydiv" ) . show ( ) ;
recorderActive = true ;
}
function _toggleRecording() {
if ( recorderActive ) {
_disableRecording ( ) ;
} else {
_enableRecording ( ) ;
}
}
2018-11-20 19:39:33 +00:00
function _lookupHelp() {
2018-11-22 16:22:54 +00:00
if ( platform . showHelp ) {
2019-05-06 01:49:08 +00:00
let tool = platform . getToolForFilename ( current_project . mainPath ) ;
2018-11-22 16:22:54 +00:00
platform . showHelp ( tool ) ; // TODO: tool, identifier
}
2018-11-20 19:39:33 +00:00
}
2018-11-24 20:43:08 +00:00
function addFileToProject ( type , ext , linefn ) {
var wnd = projectWindows . getActive ( ) ;
if ( wnd && wnd . insertText ) {
2019-05-23 03:23:55 +00:00
bootbox . prompt ( {
title : "Add " + type + " File to Project" ,
value : "filename" + ext ,
callback : ( filename :string ) = > {
if ( filename && filename . trim ( ) . length > 0 ) {
if ( ! checkEnteredFilename ( filename ) ) return ;
var path = filename ;
var newline = "\n" + linefn ( filename ) + "\n" ;
2019-05-23 12:32:53 +00:00
current_project . loadFiles ( [ path ] ) . then ( ( result ) = > {
2019-05-23 03:23:55 +00:00
if ( result && result . length ) {
alertError ( filename + " already exists; including anyway" ) ;
} else {
current_project . updateFile ( path , "\n" ) ;
}
wnd . insertText ( newline ) ;
refreshWindowList ( ) ;
} ) ;
2018-11-24 20:43:08 +00:00
}
2019-05-23 03:23:55 +00:00
}
} ) ;
2018-11-24 20:43:08 +00:00
} else {
2019-05-12 13:52:09 +00:00
alertError ( "Can't insert text in this window -- switch back to main file" ) ;
2018-11-24 20:43:08 +00:00
}
}
2020-06-14 01:28:58 +00:00
// TODO: lwtools and smaller c
2018-11-24 20:43:08 +00:00
function _addIncludeFile() {
var fn = getCurrentMainFilename ( ) ;
var tool = platform . getToolForFilename ( fn ) ;
2021-06-20 21:38:54 +00:00
// TODO: more tools? make this a function of the platform / tool provider
2020-06-14 01:28:58 +00:00
if ( fn . endsWith ( ".c" ) || tool == 'sdcc' || tool == 'cc65' || tool == 'cmoc' || tool == 'smlrc' )
2018-11-24 20:43:08 +00:00
addFileToProject ( "Header" , ".h" , ( s ) = > { return '#include "' + s + '"' } ) ;
2018-11-24 21:08:33 +00:00
else if ( tool == 'dasm' || tool == 'zmac' )
2021-06-12 16:29:49 +00:00
addFileToProject ( "Include" , ".inc" , ( s ) = > { return '\tinclude "' + s + '"' } ) ;
else if ( tool == 'ca65' || tool == 'sdasz80' || tool == 'vasm' || tool == 'armips' )
addFileToProject ( "Include" , ".inc" , ( s ) = > { return '\t.include "' + s + '"' } ) ;
2018-11-24 20:43:08 +00:00
else if ( tool == 'verilator' )
2021-06-12 16:29:49 +00:00
addFileToProject ( "Verilog" , ".v" , ( s ) = > { return '`include "' + s + '"' } ) ;
2021-06-20 21:38:54 +00:00
else if ( tool == 'wiz' )
addFileToProject ( "Include" , ".wiz" , ( s ) = > { return 'import "' + s + '";' } ) ;
2018-11-24 20:43:08 +00:00
else
2019-05-12 13:52:09 +00:00
alertError ( "Can't add include file to this project type (" + tool + ")" ) ;
2018-11-24 20:43:08 +00:00
}
function _addLinkFile() {
var fn = getCurrentMainFilename ( ) ;
var tool = platform . getToolForFilename ( fn ) ;
2020-06-14 01:28:58 +00:00
if ( fn . endsWith ( ".c" ) || tool == 'sdcc' || tool == 'cc65' || tool == 'cmoc' || tool == 'smlrc' )
2019-05-28 01:52:00 +00:00
addFileToProject ( "Linked C (or .s)" , ".c" , ( s ) = > { return '//#link "' + s + '"' } ) ;
2020-06-14 01:28:58 +00:00
else if ( fn . endsWith ( "asm" ) || fn . endsWith ( ".s" ) || tool == 'ca65' || tool == 'lwasm' )
2019-05-23 03:23:55 +00:00
addFileToProject ( "Linked ASM" , ".inc" , ( s ) = > { return ';#link "' + s + '"' } ) ;
2018-11-24 20:43:08 +00:00
else
2019-05-12 13:52:09 +00:00
alertError ( "Can't add linked file to this project type (" + tool + ")" ) ;
2018-11-24 20:43:08 +00:00
}
2019-04-04 02:23:58 +00:00
function setupDebugControls() {
// create toolbar buttons
2019-04-05 12:58:26 +00:00
uitoolbar = new Toolbar ( $ ( "#toolbar" ) [ 0 ] , null ) ;
2020-08-12 15:47:46 +00:00
uitoolbar . grp . prop ( 'id' , 'run_bar' ) ;
uitoolbar . add ( 'ctrl+alt+r' , 'Reset' , 'glyphicon-refresh' , resetAndRun ) . prop ( 'id' , 'dbg_reset' ) ;
2019-08-05 15:15:26 +00:00
uitoolbar . add ( 'ctrl+alt+,' , 'Pause' , 'glyphicon-pause' , pause ) . prop ( 'id' , 'dbg_pause' ) ;
uitoolbar . add ( 'ctrl+alt+.' , 'Resume' , 'glyphicon-play' , resume ) . prop ( 'id' , 'dbg_go' ) ;
2020-08-23 18:40:04 +00:00
if ( platform . restartAtPC ) {
uitoolbar . add ( 'ctrl+alt+/' , 'Restart at Cursor' , 'glyphicon-play-circle' , restartAtCursor ) . prop ( 'id' , 'dbg_restartatline' ) ;
}
2020-08-12 15:47:46 +00:00
uitoolbar . newGroup ( ) ;
uitoolbar . grp . prop ( 'id' , 'debug_bar' ) ;
if ( platform . runEval ) {
2020-08-22 16:40:58 +00:00
uitoolbar . add ( 'ctrl+alt+e' , 'Reset and Debug' , 'glyphicon-fast-backward' , resetAndDebug ) . prop ( 'id' , 'dbg_restart' ) ;
2020-08-13 17:32:47 +00:00
}
if ( platform . stepBack ) {
uitoolbar . add ( 'ctrl+alt+b' , 'Step Backwards' , 'glyphicon-step-backward' , runStepBackwards ) . prop ( 'id' , 'dbg_stepback' ) ;
2020-08-12 15:47:46 +00:00
}
2018-09-13 00:54:25 +00:00
if ( platform . step ) {
2019-04-05 12:58:26 +00:00
uitoolbar . add ( 'ctrl+alt+s' , 'Single Step' , 'glyphicon-step-forward' , singleStep ) . prop ( 'id' , 'dbg_step' ) ;
2019-04-04 02:23:58 +00:00
}
2020-08-13 17:32:47 +00:00
if ( platform . stepOver ) {
uitoolbar . add ( 'ctrl+alt+t' , 'Step Over' , 'glyphicon-hand-right' , stepOver ) . prop ( 'id' , 'dbg_stepover' ) ;
}
if ( platform . runUntilReturn ) {
uitoolbar . add ( 'ctrl+alt+o' , 'Step Out of Subroutine' , 'glyphicon-hand-up' , runUntilReturn ) . prop ( 'id' , 'dbg_stepout' ) ;
}
2018-09-13 00:54:25 +00:00
if ( platform . runToVsync ) {
2019-08-05 15:15:26 +00:00
uitoolbar . add ( 'ctrl+alt+n' , 'Next Frame/Interrupt' , 'glyphicon-forward' , singleFrameStep ) . prop ( 'id' , 'dbg_tovsync' ) ;
2019-04-04 02:23:58 +00:00
}
2018-11-18 17:30:41 +00:00
if ( ( platform . runEval || platform . runToPC ) && ! platform_id . startsWith ( 'verilog' ) ) {
2019-04-05 12:58:26 +00:00
uitoolbar . add ( 'ctrl+alt+l' , 'Run To Line' , 'glyphicon-save' , runToCursor ) . prop ( 'id' , 'dbg_toline' ) ;
2019-04-04 02:23:58 +00:00
}
uitoolbar . newGroup ( ) ;
2020-09-23 17:00:47 +00:00
uitoolbar . grp . prop ( 'id' , 'xtra_bar' ) ;
2019-04-04 02:23:58 +00:00
// add menu clicks
2016-12-30 23:51:15 +00:00
$ ( ".dropdown-menu" ) . collapse ( { toggle : false } ) ;
$ ( "#item_new_file" ) . click ( _createNewFile ) ;
2018-06-26 23:57:03 +00:00
$ ( "#item_upload_file" ) . click ( _uploadNewFile ) ;
2021-07-20 15:40:21 +00:00
$ ( "#item_open_directory" ) . click ( _openLocalDirectory ) ;
2019-05-22 17:58:44 +00:00
$ ( "#item_github_login" ) . click ( _loginToGithub ) ;
2019-05-22 18:45:03 +00:00
$ ( "#item_github_logout" ) . click ( _logoutOfGithub ) ;
2019-05-06 01:49:08 +00:00
$ ( "#item_github_import" ) . click ( _importProjectFromGithub ) ;
2019-05-07 19:37:37 +00:00
$ ( "#item_github_publish" ) . click ( _publishProjectToGithub ) ;
$ ( "#item_github_push" ) . click ( _pushProjectToGithub ) ;
2019-05-08 13:39:57 +00:00
$ ( "#item_github_pull" ) . click ( _pullProjectFromGithub ) ;
2019-05-16 14:08:09 +00:00
$ ( "#item_repo_delete" ) . click ( _deleteRepository ) ;
2018-08-24 03:58:35 +00:00
$ ( "#item_share_file" ) . click ( _shareEmbedLink ) ;
2018-08-21 14:16:47 +00:00
$ ( "#item_reset_file" ) . click ( _revertFile ) ;
2018-12-08 00:28:11 +00:00
$ ( "#item_rename_file" ) . click ( _renameFile ) ;
$ ( "#item_delete_file" ) . click ( _deleteFile ) ;
2017-11-24 19:14:22 +00:00
if ( platform . runEval )
$ ( "#item_debug_expr" ) . click ( _breakExpression ) . show ( ) ;
else
$ ( "#item_debug_expr" ) . hide ( ) ;
2017-02-02 19:11:52 +00:00
$ ( "#item_download_rom" ) . click ( _downloadROMImage ) ;
2018-03-23 21:05:08 +00:00
$ ( "#item_download_file" ) . click ( _downloadSourceFile ) ;
2018-08-25 18:29:51 +00:00
$ ( "#item_download_zip" ) . click ( _downloadProjectZipFile ) ;
2018-08-26 13:19:09 +00:00
$ ( "#item_download_allzip" ) . click ( _downloadAllFilesZipFile ) ;
2017-05-20 19:13:23 +00:00
$ ( "#item_record_video" ) . click ( _recordVideo ) ;
2020-07-25 23:33:55 +00:00
if ( platform_id . startsWith ( 'apple2' ) || platform_id . startsWith ( 'vcs' ) ) // TODO: look for function
2018-09-25 23:46:24 +00:00
$ ( "#item_export_cassette" ) . click ( _downloadCassetteFile ) ;
else
$ ( "#item_export_cassette" ) . hide ( ) ;
2018-02-26 23:18:23 +00:00
if ( platform . setFrameRate && platform . getFrameRate ) {
$ ( "#dbg_slower" ) . click ( _slowerFrameRate ) ;
$ ( "#dbg_faster" ) . click ( _fasterFrameRate ) ;
2018-02-27 04:48:36 +00:00
$ ( "#dbg_slowest" ) . click ( _slowestFrameRate ) ;
$ ( "#dbg_fastest" ) . click ( _fastestFrameRate ) ;
2018-02-26 23:18:23 +00:00
}
2018-11-24 20:43:08 +00:00
$ ( "#item_addfile_include" ) . click ( _addIncludeFile ) ;
$ ( "#item_addfile_link" ) . click ( _addLinkFile ) ;
2020-07-13 04:54:09 +00:00
$ ( "#item_request_persist" ) . click ( ( ) = > requestPersistPermission ( true , false ) ) ;
2017-04-19 01:18:53 +00:00
updateDebugWindows ( ) ;
2019-04-04 02:23:58 +00:00
// show help button?
if ( platform . showHelp ) {
uitoolbar . add ( 'ctrl+alt+?' , 'Show Help' , 'glyphicon-question-sign' , _lookupHelp ) ;
}
2020-07-09 18:27:56 +00:00
if ( platform . newCodeAnalyzer ) {
uitoolbar . add ( null , 'Analyze CPU Timing' , 'glyphicon-time' , traceTiming ) ;
}
2018-08-22 03:39:34 +00:00
// setup replay slider
2018-08-28 12:28:53 +00:00
if ( platform . setRecorder && platform . advance ) {
2018-08-23 20:02:13 +00:00
setupReplaySlider ( ) ;
}
}
function setupReplaySlider() {
2018-08-22 03:39:34 +00:00
var replayslider = $ ( "#replayslider" ) ;
2020-07-04 14:16:45 +00:00
var clockslider = $ ( "#clockslider" ) ;
2018-08-23 20:02:13 +00:00
var replayframeno = $ ( "#replay_frame" ) ;
2020-07-04 14:16:45 +00:00
var clockno = $ ( "#replay_clock" ) ;
if ( ! platform . advanceFrameClock ) $ ( "#clockdiv" ) . hide ( ) ; // TODO: put this test in recorder?
var updateFrameNo = ( ) = > {
replayframeno . text ( stateRecorder . lastSeekFrame + "" ) ;
clockno . text ( stateRecorder . lastSeekStep + "" ) ;
2018-08-22 03:39:34 +00:00
} ;
2018-08-23 20:02:13 +00:00
var sliderChanged = ( e ) = > {
2018-08-22 03:39:34 +00:00
_pause ( ) ;
2020-07-04 14:16:45 +00:00
var frame : number = parseInt ( replayslider . val ( ) . toString ( ) ) ;
var step : number = parseInt ( clockslider . val ( ) . toString ( ) ) ;
2020-07-11 17:45:22 +00:00
if ( stateRecorder . loadFrame ( frame , step ) >= 0 ) {
2020-07-04 14:16:45 +00:00
clockslider . attr ( 'min' , 0 ) ;
clockslider . attr ( 'max' , stateRecorder . lastStepCount ) ;
updateFrameNo ( ) ;
uiDebugCallback ( platform . saveState ( ) ) ;
2018-08-23 20:02:13 +00:00
}
} ;
var setFrameTo = ( frame :number ) = > {
_pause ( ) ;
2020-07-11 17:45:22 +00:00
if ( stateRecorder . loadFrame ( frame ) >= 0 ) {
2018-08-23 20:02:13 +00:00
replayslider . val ( frame ) ;
2020-07-04 14:16:45 +00:00
updateFrameNo ( ) ;
uiDebugCallback ( platform . saveState ( ) ) ;
}
} ;
var setClockTo = ( clock :number ) = > {
_pause ( ) ;
var frame : number = parseInt ( replayslider . val ( ) . toString ( ) ) ;
2020-07-11 17:45:22 +00:00
if ( stateRecorder . loadFrame ( frame , clock ) >= 0 ) {
2020-07-04 14:16:45 +00:00
clockslider . val ( clock ) ;
updateFrameNo ( ) ;
uiDebugCallback ( platform . saveState ( ) ) ;
2018-08-23 20:02:13 +00:00
}
} ;
stateRecorder . callbackStateChanged = ( ) = > {
2020-07-11 17:45:22 +00:00
replayslider . attr ( 'min' , 0 ) ;
2018-08-23 20:02:13 +00:00
replayslider . attr ( 'max' , stateRecorder . numFrames ( ) ) ;
2018-08-23 22:52:56 +00:00
replayslider . val ( stateRecorder . currentFrame ( ) ) ;
2020-07-04 14:16:45 +00:00
clockslider . val ( stateRecorder . currentStep ( ) ) ;
updateFrameNo ( ) ;
2020-07-02 17:33:22 +00:00
showDebugInfo ( platform . saveState ( ) ) ;
2018-08-23 20:02:13 +00:00
} ;
replayslider . on ( 'input' , sliderChanged ) ;
2020-07-04 14:16:45 +00:00
clockslider . on ( 'input' , sliderChanged ) ;
//replayslider.on('change', sliderChanged);
2018-08-23 20:02:13 +00:00
$ ( "#replay_min" ) . click ( ( ) = > { setFrameTo ( 1 ) } ) ;
$ ( "#replay_max" ) . click ( ( ) = > { setFrameTo ( stateRecorder . numFrames ( ) ) ; } ) ;
2018-11-21 20:58:44 +00:00
$ ( "#replay_back" ) . click ( ( ) = > { setFrameTo ( parseInt ( replayslider . val ( ) . toString ( ) ) - 1 ) ; } ) ;
$ ( "#replay_fwd" ) . click ( ( ) = > { setFrameTo ( parseInt ( replayslider . val ( ) . toString ( ) ) + 1 ) ; } ) ;
2020-07-04 14:16:45 +00:00
$ ( "#clock_back" ) . click ( ( ) = > { setClockTo ( parseInt ( clockslider . val ( ) . toString ( ) ) - 1 ) ; } ) ;
$ ( "#clock_fwd" ) . click ( ( ) = > { setClockTo ( parseInt ( clockslider . val ( ) . toString ( ) ) + 1 ) ; } ) ;
2018-08-22 03:39:34 +00:00
$ ( "#replay_bar" ) . show ( ) ;
2019-04-04 02:23:58 +00:00
uitoolbar . add ( 'ctrl+alt+0' , 'Start/Stop Replay Recording' , 'glyphicon-record' , _toggleRecording ) . prop ( 'id' , 'dbg_record' ) ;
2016-12-16 01:21:51 +00:00
}
2018-12-30 18:57:33 +00:00
function isLandscape() {
try {
2019-02-15 17:33:13 +00:00
var object = window . screen [ 'orientation' ] || window . screen [ 'msOrientation' ] || window . screen [ 'mozOrientation' ] || null ;
2018-12-30 18:57:33 +00:00
if ( object ) {
if ( object . type . indexOf ( 'landscape' ) !== - 1 ) { return true ; }
if ( object . type . indexOf ( 'portrait' ) !== - 1 ) { return false ; }
}
if ( 'orientation' in window ) {
var value = window . orientation ;
if ( value === 0 || value === 180 ) {
return false ;
} else if ( value === 90 || value === 270 ) {
return true ;
}
}
} catch ( e ) { }
// fallback to comparing width to height
return window . innerWidth > window . innerHeight ;
}
2021-08-04 17:00:10 +00:00
async function showWelcomeMessage() {
if ( userPrefs . shouldCompleteTour ( ) ) {
2021-08-06 02:19:43 +00:00
await loadScript ( 'lib/bootstrap-tourist.js' ) ;
2019-05-08 23:15:26 +00:00
var is_vcs = platform_id . startsWith ( 'vcs' ) ;
2018-07-06 23:12:58 +00:00
var steps = [
2016-12-30 23:51:15 +00:00
{
2019-08-15 16:18:16 +00:00
element : "#platformsMenuButton" ,
placement : 'right' ,
2020-01-05 02:45:27 +00:00
title : "Platform Selector" ,
2019-08-20 15:42:39 +00:00
content : "You're currently on the \"<b>" + platform_id + "</b>\" platform. You can choose a different one from the menu."
} ,
{
element : "#preset_select" ,
title : "Project Selector" ,
content : "You can choose different code examples, create your own files, or import projects from GitHub."
2019-08-15 16:18:16 +00:00
} ,
{
element : "#workspace" ,
title : "Code Editor" ,
2020-07-13 04:54:09 +00:00
content : is_vcs ? "Type your 6502 assembly code into the editor, and it'll be assembled in real-time."
: "Type your source code into the editor, and it'll be compiled in real-time."
2016-12-30 23:51:15 +00:00
} ,
{
element : "#emulator" ,
placement : 'left' ,
2017-04-20 00:55:13 +00:00
title : "Emulator" ,
2019-08-15 16:18:16 +00:00
content : "We'll load your compiled code into the emulator whenever you make changes."
2016-12-30 23:51:15 +00:00
} ,
{
element : "#debug_bar" ,
placement : 'bottom' ,
title : "Debug Tools" ,
2017-04-20 00:55:13 +00:00
content : "Use these buttons to set breakpoints, single step through code, pause/resume, and use debugging tools."
2016-12-30 23:51:15 +00:00
} ,
{
element : "#dropdownMenuButton" ,
title : "Main Menu" ,
2019-08-15 16:18:16 +00:00
content : "Click the menu to create new files, download your code, or share your work with others."
2018-12-30 18:57:33 +00:00
} ,
{
element : "#sidebar" ,
title : "Sidebar" ,
2019-08-15 16:18:16 +00:00
content : "Pull right to expose the sidebar. It lets you switch between source files, view assembly listings, and use other tools like Disassembler, Memory Browser, and Asset Editor."
2018-12-30 18:57:33 +00:00
}
] ;
if ( ! isLandscape ( ) ) {
steps . unshift ( {
element : "#controls_top" ,
placement : 'bottom' ,
title : "Portrait mode detected" ,
content : "This site works best on desktop browsers. For best results, rotate your device to landscape orientation."
} ) ;
}
2020-07-30 19:43:55 +00:00
if ( window . location . host . endsWith ( '8bitworkshop.com' ) ) {
steps . unshift ( {
element : "#dropdownMenuButton" ,
placement : 'right' ,
title : "Cookie Consent" ,
content : 'Before we start, we should tell you that this website stores cookies and other data in your browser. You can review our <a href="/privacy.html" target="_new">privacy policy</a>.'
} ) ;
steps . push ( {
element : "#booksMenuButton" ,
placement : 'left' ,
title : "Books" ,
content : "Get some books that explain how to program all of this stuff, and write some games!"
} ) ;
}
2021-07-10 20:43:01 +00:00
if ( isElectron ) {
steps . unshift ( {
element : "#dropdownMenuButton" ,
placement : 'right' ,
2021-07-13 16:34:47 +00:00
title : "Developer Analytics" ,
content : 'BTW, we send stack traces to sentry.io when exceptions are thrown. Hope that\'s ok.'
} ) ;
steps . unshift ( {
element : "#dropdownMenuButton" ,
placement : 'right' ,
title : "Welcome to 8bitworkshop Desktop!" ,
content : 'The directory "~/8bitworkshop" contains all of your file edits and built ROM images. You can create new projects under the platform directories (e.g. "c64/myproject")'
2021-07-10 20:43:01 +00:00
} ) ;
}
2018-07-06 23:12:58 +00:00
var tour = new Tour ( {
autoscroll :false ,
//storage:false,
2020-07-12 19:41:45 +00:00
steps :steps ,
onEnd : ( ) = > {
2021-08-04 17:00:10 +00:00
userPrefs . completedTour ( ) ;
2020-07-14 12:32:45 +00:00
//requestPersistPermission(false, true);
2020-07-12 19:41:45 +00:00
}
2018-07-06 23:12:58 +00:00
} ) ;
2021-08-04 17:00:10 +00:00
setTimeout ( ( ) = > { tour . start ( ) ; } , 2500 ) ;
2016-12-18 20:59:31 +00:00
}
}
2016-12-16 01:21:51 +00:00
///////////////////////////////////////////////////
2019-12-21 18:10:08 +00:00
function globalErrorHandler ( msgevent ) {
var msg = ( msgevent . message || msgevent . error || msgevent ) + "" ;
// storage quota full? (Chrome) try to expand it
if ( msg . indexOf ( "QuotaExceededError" ) >= 0 ) {
2020-07-13 04:54:09 +00:00
requestPersistPermission ( false , false ) ;
2019-12-21 18:10:08 +00:00
} else {
2020-08-16 17:16:29 +00:00
var err = msgevent . error || msgevent . reason ;
2020-08-24 16:51:47 +00:00
if ( err != null && err instanceof EmuHalt ) {
2020-10-24 00:34:19 +00:00
haltEmulation ( err ) ;
2020-08-24 16:51:47 +00:00
}
2019-12-21 18:10:08 +00:00
}
}
2020-10-24 00:34:19 +00:00
export function haltEmulation ( err? : EmuHalt ) {
console . log ( "haltEmulation" ) ;
_pause ( ) ;
emulationHalted ( err ) ;
// TODO: reset platform?
}
2019-12-21 18:10:08 +00:00
// catch errors
function installErrorHandler() {
window . addEventListener ( 'error' , globalErrorHandler ) ;
2020-08-16 17:16:29 +00:00
window . addEventListener ( 'unhandledrejection' , globalErrorHandler ) ;
2019-12-21 18:10:08 +00:00
}
function uninstallErrorHandler() {
window . removeEventListener ( 'error' , globalErrorHandler ) ;
2020-08-16 17:16:29 +00:00
window . removeEventListener ( 'unhandledrejection' , globalErrorHandler ) ;
2019-12-21 18:10:08 +00:00
}
2019-05-22 21:03:56 +00:00
function gotoNewLocation ( replaceHistory? : boolean ) {
2019-12-21 18:10:08 +00:00
uninstallErrorHandler ( ) ;
2019-05-22 21:03:56 +00:00
if ( replaceHistory )
window . location . replace ( "?" + $ . param ( qs ) ) ;
else
window . location . href = "?" + $ . param ( qs ) ;
2018-07-04 02:14:07 +00:00
}
2018-08-23 21:46:25 +00:00
function replaceURLState() {
2021-08-04 17:00:10 +00:00
if ( platform_id ) qs . platform = platform_id ;
2019-08-20 23:13:41 +00:00
delete qs [ '' ] ; // remove null parameter
2018-08-23 21:46:25 +00:00
history . replaceState ( { } , "" , "?" + $ . param ( qs ) ) ;
}
2017-05-01 15:30:47 +00:00
function addPageFocusHandlers() {
2017-05-02 13:09:53 +00:00
var hidden = false ;
2019-05-06 01:49:08 +00:00
document . addEventListener ( "visibilitychange" , ( ) = > {
2020-10-07 20:56:38 +00:00
if ( document . visibilityState == 'hidden' && platform && platform . isRunning ( ) ) {
2017-11-16 17:08:06 +00:00
_pause ( ) ;
2017-05-02 13:09:53 +00:00
hidden = true ;
} else if ( document . visibilityState == 'visible' && hidden ) {
2017-11-16 17:08:06 +00:00
_resume ( ) ;
2017-05-02 13:09:53 +00:00
hidden = false ;
}
} ) ;
2019-05-06 01:49:08 +00:00
$ ( window ) . on ( "focus" , ( ) = > {
2017-05-02 13:09:53 +00:00
if ( hidden ) {
2017-11-16 17:08:06 +00:00
_resume ( ) ;
2017-05-02 13:09:53 +00:00
hidden = false ;
}
} ) ;
2019-05-06 01:49:08 +00:00
$ ( window ) . on ( "blur" , ( ) = > {
2020-10-07 20:56:38 +00:00
if ( platform && platform . isRunning ( ) ) {
2017-11-16 17:08:06 +00:00
_pause ( ) ;
2017-05-02 13:09:53 +00:00
hidden = true ;
}
2017-05-01 15:30:47 +00:00
} ) ;
2020-10-07 20:56:38 +00:00
$ ( window ) . on ( "orientationchange" , ( ) = > {
2020-10-31 17:04:55 +00:00
if ( platform && platform . resize ) setTimeout ( platform . resize . bind ( platform ) , 200 ) ;
2020-10-07 20:56:38 +00:00
} ) ;
2017-05-01 15:30:47 +00:00
}
2021-08-07 14:04:17 +00:00
// TODO: merge w/ player.html somehow?
2019-05-17 04:42:52 +00:00
function showInstructions() {
2019-05-18 15:19:12 +00:00
var div = $ ( document ) . find ( ".emucontrols-" + getRootBasePlatform ( platform_id ) ) ;
2020-07-29 00:14:42 +00:00
if ( platform_id . endsWith ( ".mame" ) ) div . show ( ) ; // TODO: MAME seems to eat the focus() event
2019-05-18 15:19:12 +00:00
var vcanvas = $ ( "#emulator" ) . find ( "canvas" ) ;
if ( vcanvas ) {
vcanvas . on ( 'focus' , ( ) = > {
2019-08-22 14:30:33 +00:00
if ( platform . isRunning ( ) ) {
div . fadeIn ( 200 ) ;
// toggle sound for browser autoplay
platform . resume ( ) ;
}
2019-05-18 15:19:12 +00:00
} ) ;
vcanvas . on ( 'blur' , ( ) = > {
div . fadeOut ( 200 ) ;
} ) ;
}
2019-05-17 04:42:52 +00:00
}
2019-05-17 19:45:19 +00:00
function installGAHooks() {
2020-07-30 19:43:55 +00:00
if ( window [ 'ga' ] ) {
2019-05-17 19:45:19 +00:00
$ ( ".dropdown-item" ) . click ( ( e ) = > {
if ( e . target && e . target . id ) {
2019-08-19 13:43:48 +00:00
gaEvent ( 'menu' , e . target . id ) ;
2019-05-17 19:45:19 +00:00
}
} ) ;
2021-08-04 17:00:10 +00:00
ga ( 'send' , 'pageview' , location . pathname + '?platform=' + platform_id + ( repo_id ? ( '&repo=' + repo_id ) : ( '&file=' + qs . file ) ) ) ;
2019-05-17 19:45:19 +00:00
}
}
2019-12-18 01:30:42 +00:00
async function startPlatform() {
2017-01-29 21:06:05 +00:00
if ( ! PLATFORMS [ platform_id ] ) throw Error ( "Invalid platform '" + platform_id + "'." ) ;
2019-05-21 17:06:48 +00:00
platform = new PLATFORMS [ platform_id ] ( $ ( "#emuscreen" ) [ 0 ] ) ;
2019-08-20 15:42:39 +00:00
setPlatformUI ( ) ;
2018-08-22 18:50:10 +00:00
stateRecorder = new StateRecorderImpl ( platform ) ;
2021-06-03 23:17:06 +00:00
PRESETS = platform . getPresets ? platform . getPresets ( ) : [ ] ;
2021-08-04 17:00:10 +00:00
if ( ! qs . file ) {
2017-01-16 15:35:19 +00:00
// try to load last file (redirect)
2021-08-04 17:00:10 +00:00
var lastid = userPrefs . getLastPreset ( ) ;
2019-05-22 18:18:44 +00:00
// load first preset file, unless we're in a repo
var defaultfile = lastid || ( repo_id ? null : PRESETS [ 0 ] . id ) ;
2021-08-04 17:00:10 +00:00
qs . file = defaultfile || 'DEFAULT' ;
2019-05-22 18:18:44 +00:00
if ( ! defaultfile ) {
alertError ( "There is no default main file for this project. Try selecting one from the pulldown." ) ;
}
2018-08-23 21:46:25 +00:00
}
2019-05-09 03:10:24 +00:00
// legacy vcs stuff
2021-08-04 17:00:10 +00:00
if ( platform_id == 'vcs' && qs . file . startsWith ( 'examples/' ) && ! qs . file . endsWith ( '.a' ) ) {
qs . file += '.a' ;
2019-05-09 03:10:24 +00:00
}
2018-08-23 21:46:25 +00:00
// start platform and load file
2019-05-09 12:44:47 +00:00
replaceURLState ( ) ;
2019-12-21 18:10:08 +00:00
installErrorHandler ( ) ;
installGAHooks ( ) ;
2019-12-18 01:30:42 +00:00
await platform . start ( ) ;
2019-12-18 17:20:15 +00:00
await loadBIOSFromProject ( ) ;
await initProject ( ) ;
2021-08-04 17:00:10 +00:00
await loadProject ( qs . file ) ;
2021-08-01 18:03:50 +00:00
platform . sourceFileFetch = ( path ) = > current_project . filedata [ path ] ;
2018-11-20 19:39:33 +00:00
setupDebugControls ( ) ;
2018-08-23 21:46:25 +00:00
addPageFocusHandlers ( ) ;
2019-05-17 04:42:52 +00:00
showInstructions ( ) ;
2021-08-04 17:00:10 +00:00
if ( isEmbed ) {
2020-09-11 18:50:42 +00:00
hideControlsForEmbed ( ) ;
} else {
updateSelector ( ) ;
updateBooksMenu ( ) ;
showWelcomeMessage ( ) ;
}
2019-05-22 14:53:39 +00:00
revealTopBar ( ) ;
2017-01-14 16:14:25 +00:00
}
2020-09-11 18:50:42 +00:00
function hideControlsForEmbed() {
$ ( '#dropdownMenuButton' ) . hide ( ) ;
$ ( '#platformsMenuButton' ) . hide ( ) ;
$ ( '#booksMenuButton' ) . hide ( ) ;
}
2020-07-17 17:06:24 +00:00
function updateBooksMenu() {
if ( getRootBasePlatform ( platform_id ) == 'nes' ) $ ( ".book-nes" ) . addClass ( "book-active" ) ;
else if ( getRootBasePlatform ( platform_id ) == 'vcs' ) $ ( ".book-vcs" ) . addClass ( "book-active" ) ;
else if ( getRootBasePlatform ( platform_id ) == 'verilog' ) $ ( ".book-verilog" ) . addClass ( "book-active" ) ;
else if ( platform . getToolForFilename ( getCurrentMainFilename ( ) ) == 'sdcc' ) $ ( ".book-arcade" ) . addClass ( "book-active" ) ;
}
2019-05-22 14:53:39 +00:00
function revealTopBar() {
setTimeout ( ( ) = > { $ ( "#controls_dynamic" ) . css ( 'visibility' , 'inherit' ) ; } , 250 ) ;
}
2018-11-23 18:29:11 +00:00
export function setupSplits() {
2021-08-04 17:00:10 +00:00
var splitName = 'workspace-split3-' + platform_id ;
if ( isEmbed ) splitName = 'embed-' + splitName ;
2020-10-26 18:36:38 +00:00
var sizes ;
if ( platform_id . startsWith ( 'vcs' ) )
sizes = [ 0 , 50 , 50 ] ;
2021-08-04 22:14:26 +00:00
else if ( isEmbed || isMobileDevice )
2021-08-05 02:20:55 +00:00
sizes = [ 0 , 55 , 45 ] ;
2020-10-26 18:36:38 +00:00
else
2019-03-03 20:37:22 +00:00
sizes = [ 12 , 44 , 44 ] ;
2018-12-22 23:50:39 +00:00
var sizesStr = hasLocalStorage && localStorage . getItem ( splitName ) ;
2018-11-23 18:29:11 +00:00
if ( sizesStr ) {
try {
sizes = JSON . parse ( sizesStr ) ;
} catch ( e ) { console . log ( e ) ; }
}
2019-03-03 20:37:22 +00:00
var split = Split ( [ '#sidebar' , '#workspace' , '#emulator' ] , {
2018-11-23 18:29:11 +00:00
sizes : sizes ,
2018-11-24 16:33:28 +00:00
minSize : [ 0 , 250 , 250 ] ,
2018-11-26 11:12:45 +00:00
onDrag : ( ) = > {
if ( platform && platform . resize ) platform . resize ( ) ;
} ,
onDragEnd : ( ) = > {
2021-08-04 17:00:10 +00:00
if ( hasLocalStorage ) localStorage . setItem ( splitName , JSON . stringify ( split . getSizes ( ) ) )
2019-08-19 15:16:02 +00:00
if ( projectWindows ) projectWindows . resize ( ) ;
2018-11-23 18:29:11 +00:00
} ,
} ) ;
}
2019-05-01 19:28:15 +00:00
function loadImportedURL ( url : string ) {
// TODO: zip file?
2021-08-06 16:51:40 +00:00
const ignore = parseBool ( qs . ignore ) || isEmbed ;
2019-05-01 19:28:15 +00:00
setWaitDialog ( true ) ;
2021-08-06 16:51:40 +00:00
getWithBinary ( url , async ( data ) = > {
2019-05-01 19:28:15 +00:00
if ( data ) {
2021-08-06 16:51:40 +00:00
var path = getFilenameForPath ( url ) ;
2019-05-01 19:28:15 +00:00
console . log ( "Importing " + data . length + " bytes as " + path ) ;
2021-08-06 16:51:40 +00:00
try {
var olddata = await store . getItem ( path ) ;
2019-05-01 19:28:15 +00:00
setWaitDialog ( false ) ;
2021-08-06 16:51:40 +00:00
if ( olddata != null && ignore ) {
// ignore=1, do nothing
} else if ( olddata == null || confirm ( "Replace existing file '" + path + "'?" ) ) {
await store . setItem ( path , data ) ;
2019-05-01 19:28:15 +00:00
}
2021-08-06 16:51:40 +00:00
delete qs . importURL ;
qs . file = path ;
replaceURLState ( ) ;
loadAndStartPlatform ( ) ;
} finally {
setWaitDialog ( false ) ;
}
2019-05-01 19:28:15 +00:00
} else {
2019-05-12 13:52:09 +00:00
alertError ( "Could not load source code from URL: " + url ) ;
2019-05-01 19:28:15 +00:00
setWaitDialog ( false ) ;
}
} , 'text' ) ;
}
2020-06-28 16:43:26 +00:00
async function loadFormDataUpload() {
2021-08-04 17:00:10 +00:00
var ignore = parseBool ( qs . ignore ) ;
var force = parseBool ( qs . force ) ;
if ( isEmbed ) {
ignore = ! force ; // ignore is default when embed=1 unless force=1
} else {
force = false ; // can't use force w/o embed=1
}
2020-06-28 16:43:26 +00:00
for ( var i = 0 ; i < 20 ; i + + ) {
let path = qs [ 'file' + i + '_name' ] ;
let dataenc = qs [ 'file' + i + '_data' ] ;
if ( path == null || dataenc == null ) break ;
var olddata = await store . getItem ( path ) ;
2020-10-14 22:33:15 +00:00
if ( ! ( ignore && olddata ) ) {
let value = dataenc ;
if ( qs [ 'file' + i + '_type' ] == 'binary' ) {
value = stringToByteArray ( atob ( value ) ) ;
}
if ( ! olddata || force || confirm ( "Replace existing file '" + path + "'?" ) ) {
await store . setItem ( path , value ) ;
}
2020-06-28 16:43:26 +00:00
}
2021-08-04 17:00:10 +00:00
if ( i == 0 ) { qs . file = path ; } // set main filename
2020-06-28 16:43:26 +00:00
delete qs [ 'file' + i + '_name' ] ;
delete qs [ 'file' + i + '_data' ] ;
delete qs [ 'file' + i + '_type' ] ;
}
2021-08-04 17:00:10 +00:00
delete qs . ignore ;
delete qs . force ;
2020-06-28 16:43:26 +00:00
replaceURLState ( ) ;
}
2019-08-20 15:42:39 +00:00
function setPlatformUI() {
2019-08-27 16:12:56 +00:00
var name = platform . getPlatformName && platform . getPlatformName ( ) ;
2019-08-20 16:29:31 +00:00
var menuitem = $ ( 'a[href="?platform=' + platform_id + '"]' ) ;
2019-08-20 15:42:39 +00:00
if ( menuitem . length ) {
menuitem . addClass ( "dropdown-item-checked" ) ;
name = name || menuitem . text ( ) || name ;
}
$ ( ".platform_name" ) . text ( name || platform_id ) ;
}
2021-07-10 19:28:02 +00:00
export function getPlatformAndRepo() {
2022-02-12 16:55:05 +00:00
// lookup repository for this platform (TODO: enable cross-platform repos)
platform_id = qs . platform || userPrefs . getLastPlatformID ( ) ;
repo_id = qs . repo ;
// only look at cached repo_id if file= is not present, so back button works
if ( ! qs . repo && ! qs . file )
repo_id = userPrefs . getLastRepoID ( platform_id ) ;
// are we in a repo?
2019-05-12 13:52:09 +00:00
if ( hasLocalStorage && repo_id && repo_id !== '/' ) {
2019-05-09 12:44:47 +00:00
var repo = getRepos ( ) [ repo_id ] ;
2022-02-12 16:55:05 +00:00
// override query string params w/ repo settings
2019-05-12 13:52:09 +00:00
if ( repo ) {
2022-02-13 19:25:57 +00:00
console . log ( platform_id , qs , repo ) ;
2021-08-04 17:00:10 +00:00
qs . repo = repo_id ;
2022-02-13 19:25:57 +00:00
if ( repo . platform_id && ! qs . platform )
2021-08-04 17:00:10 +00:00
qs . platform = platform_id = repo . platform_id ;
2022-02-12 16:55:05 +00:00
if ( ! qs . file && repo . mainPath )
2021-08-04 17:00:10 +00:00
qs . file = repo . mainPath ;
2022-02-12 16:55:05 +00:00
//requestPersistPermission(true, true);
2019-05-12 13:52:09 +00:00
}
} else {
repo_id = '' ;
2021-08-04 17:00:10 +00:00
delete qs . repo ;
2019-05-09 12:44:47 +00:00
}
2022-02-04 00:52:20 +00:00
// add default platform
if ( ! platform_id ) {
if ( isEmbed ) fatalError ( ` The 'platform' must be specified when embed=1 ` ) ;
platform_id = qs . platform = "vcs" ;
}
2021-07-10 19:28:02 +00:00
}
// start
export async function startUI() {
// import from github?
2021-08-04 17:00:10 +00:00
if ( qs . githubURL ) {
importProjectFromGithub ( qs . githubURL , true ) ;
2021-07-10 19:28:02 +00:00
return ;
}
getPlatformAndRepo ( ) ;
2018-11-23 18:29:11 +00:00
setupSplits ( ) ;
2021-08-03 21:27:20 +00:00
// get store ID, repo id or platform id
2019-05-08 23:15:26 +00:00
store_id = repo_id || getBasePlatform ( platform_id ) ;
2021-08-03 21:27:20 +00:00
// are we embedded?
2021-08-04 17:00:10 +00:00
if ( isEmbed ) {
store_id = ( document . referrer || document . location . href ) + store_id ;
}
2021-08-03 21:27:20 +00:00
// create store
2020-06-28 16:43:26 +00:00
store = createNewPersistentStore ( store_id ) ;
// is this an importURL?
2021-08-04 17:00:10 +00:00
if ( qs . importURL ) {
loadImportedURL ( qs . importURL ) ;
2020-06-28 16:43:26 +00:00
return ; // TODO: make async
}
// is this a file POST?
2021-08-04 17:00:10 +00:00
if ( qs . file0_name ) {
2020-06-28 16:43:26 +00:00
await loadFormDataUpload ( ) ;
}
// load and start platform object
loadAndStartPlatform ( ) ;
2016-12-16 01:21:51 +00:00
}
2019-05-01 19:28:15 +00:00
2019-12-18 01:30:42 +00:00
async function loadAndStartPlatform() {
try {
2021-08-02 19:04:56 +00:00
var module = await importPlatform ( getRootBasePlatform ( platform_id ) ) ;
2019-08-23 19:05:12 +00:00
console . log ( "starting platform" , platform_id ) ; // loaded required <platform_id>.js file
2021-08-02 19:04:56 +00:00
await startPlatform ( ) ;
document . title = document . title + " [" + platform_id + "] - " + ( repo_id ? ( '[' + repo_id + '] - ' ) : '' ) + current_project . mainPath ;
2019-12-18 01:30:42 +00:00
} catch ( e ) {
2019-09-08 15:39:54 +00:00
console . log ( e ) ;
2019-12-18 01:30:42 +00:00
alertError ( 'Platform "' + platform_id + '" failed to load.' ) ;
2021-08-02 19:04:56 +00:00
} finally {
revealTopBar ( ) ;
2019-12-18 01:30:42 +00:00
}
2019-05-01 19:28:15 +00:00
}
2019-05-09 03:10:24 +00:00
2019-06-03 14:08:29 +00:00
// HTTPS REDIRECT
2019-05-18 22:33:38 +00:00
const useHTTPSCookieName = "__use_https" ;
2019-05-26 03:15:25 +00:00
function setHTTPSCookie ( val : number ) {
document . cookie = useHTTPSCookieName + "=" + val + ";domain=8bitworkshop.com;path=/;max-age=315360000" ;
}
2019-05-21 19:41:17 +00:00
function shouldRedirectHTTPS ( ) : boolean {
// cookie set? either true or false
var shouldRedir = getCookie ( useHTTPSCookieName ) ;
if ( typeof shouldRedir === 'string' ) {
return ! ! shouldRedir ; // convert to bool
}
// set a 10yr cookie, value depends on if it's our first time here
var val = hasLocalStorage && ! localStorage . getItem ( "__lastplatform" ) ? 1 : 0 ;
2019-05-26 03:15:25 +00:00
setHTTPSCookie ( val ) ;
2019-05-21 19:41:17 +00:00
return ! ! val ;
2019-05-18 22:33:38 +00:00
}
2019-05-26 03:15:25 +00:00
function _switchToHTTPS() {
bootbox . confirm ( '<p>Do you want to force the browser to use HTTPS from now on?</p>' +
'<p>WARNING: This will make all of your local files unavailable, so you should "Download All Changes" first for each platform where you have done work.</p>' +
'<p>You can go back to HTTP by setting the "' + useHTTPSCookieName + '" cookie to 0.</p>' , ( ok ) = > {
if ( ok ) {
setHTTPSCookie ( 1 ) ;
redirectToHTTPS ( ) ;
}
} ) ;
}
2019-05-18 22:33:38 +00:00
function redirectToHTTPS() {
if ( window . location . protocol == 'http:' && window . location . host == '8bitworkshop.com' ) {
if ( shouldRedirectHTTPS ( ) ) {
2019-12-21 18:10:08 +00:00
uninstallErrorHandler ( ) ;
2019-05-18 22:33:38 +00:00
window . location . replace ( window . location . href . replace ( /^http:/ , 'https:' ) ) ;
} else {
2019-05-26 03:15:25 +00:00
$ ( "#item_switch_https" ) . click ( _switchToHTTPS ) . show ( ) ;
2019-05-18 22:33:38 +00:00
}
}
}
// redirect to HTTPS after script loads?
redirectToHTTPS ( ) ;
2020-07-30 19:43:55 +00:00
2021-07-10 19:28:02 +00:00
//// ELECTRON (and other external) STUFF
2020-08-02 04:06:18 +00:00
2020-10-17 19:34:08 +00:00
export function setTestInput ( path : string , data : FileData ) {
platform . writeFile ( path , data ) ;
}
export function getTestOutput ( path : string ) : FileData {
return platform . readFile ( path ) ;
}
export function getSaveState() {
return platform . saveState ( ) ;
}
export function emulationHalted ( err : EmuHalt ) {
var msg = ( err && err . message ) || msg ;
showExceptionAsError ( err , msg ) ;
2021-06-14 19:38:51 +00:00
projectWindows . refresh ( false ) ; // don't mess with cursor
2021-06-12 16:29:49 +00:00
if ( platform . saveState ) showDebugInfo ( platform . saveState ( ) ) ;
2020-10-17 19:34:08 +00:00
}
2020-08-02 04:06:18 +00:00
// get remote file from local fs
2021-07-10 19:28:02 +00:00
declare var alternateLocalFilesystem : ProjectFilesystem ;
export async function reloadWorkspaceFile ( path : string ) {
2020-08-29 22:30:39 +00:00
var oldval = current_project . filedata [ path ] ;
if ( oldval != null ) {
2021-07-10 19:28:02 +00:00
projectWindows . updateFile ( path , await alternateLocalFilesystem . getFileData ( path ) ) ;
console . log ( 'updating file' , path ) ;
2020-08-29 22:30:39 +00:00
}
2020-08-02 04:06:18 +00:00
}
2020-08-27 01:42:18 +00:00
function writeOutputROMFile() {
2021-07-10 19:28:02 +00:00
if ( isElectron && current_output instanceof Uint8Array ) {
2020-08-27 01:42:18 +00:00
var prefix = getFilenamePrefix ( getCurrentMainFilename ( ) ) ;
var suffix = ( platform . getROMExtension && platform . getROMExtension ( current_output ) )
|| "-" + getBasePlatform ( platform_id ) + ".bin" ;
2021-07-10 19:28:02 +00:00
alternateLocalFilesystem . setFileData ( ` bin/ ${ prefix } ${ suffix } ` , current_output ) ;
2020-08-27 01:42:18 +00:00
}
}
2020-10-26 18:36:38 +00:00
export function highlightSearch ( query : string ) { // TODO: filename?
var wnd = projectWindows . getActive ( ) ;
2021-08-04 22:14:26 +00:00
if ( wnd instanceof SourceEditor ) {
2020-10-26 18:36:38 +00:00
var sc = wnd . editor . getSearchCursor ( query ) ;
if ( sc . findNext ( ) ) {
wnd . editor . setSelection ( sc . pos . to , sc . pos . from ) ;
}
}
}
2021-08-01 18:03:50 +00:00
2021-08-06 01:59:36 +00:00
function startUIWhenVisible() {
let started = false ;
let observer = new IntersectionObserver ( ( entries , observer ) = > {
for ( var entry of entries ) {
if ( entry . isIntersecting && ! started ) {
startUI ( ) ;
started = true ;
}
if ( entry . intersectionRatio == 0 && isPlatformReady ( ) && platform . isRunning ( ) ) {
_pause ( ) ;
}
if ( entry . intersectionRatio > 0 && isPlatformReady ( ) && ! platform . isRunning ( ) ) {
_resume ( ) ;
}
}
} , { } ) ;
observer . observe ( $ ( "#emulator" ) [ 0 ] ) ; //window.document.body);
}
2021-08-01 18:03:50 +00:00
/// start UI if in browser (not node)
if ( typeof process === 'undefined' ) {
2021-08-06 01:59:36 +00:00
// if embedded, do not start UI until we scroll past it
if ( isEmbed && typeof IntersectionObserver === 'function' ) {
startUIWhenVisible ( ) ;
} else {
startUI ( ) ;
}
2021-08-01 18:03:50 +00:00
}