2017-01-03 15:43:40 +00:00
"use strict" ;
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
2018-07-08 03:10:51 +00:00
import $ = require ( "jquery" ) ;
import * as bootstrap from "bootstrap" ;
2018-07-08 14:07:19 +00:00
import { CodeProject } from "./project" ;
2018-11-30 15:43:23 +00:00
import { WorkerResult , WorkerOutput , VerilogOutput , SourceFile , WorkerError , FileData } from "./workertypes" ;
2018-07-08 03:10:51 +00:00
import { ProjectWindows } from "./windows" ;
2019-08-23 19:05:12 +00:00
import { Platform , Preset , DebugSymbols , DebugEvalCondition , isDebuggable } from "./baseplatform" ;
2019-04-04 02:23:58 +00:00
import { PLATFORMS , EmuHalt , Toolbar } from "./emu" ;
2018-07-08 03:10:51 +00:00
import * as Views from "./views" ;
2018-08-17 19:13:58 +00:00
import { createNewPersistentStore } from "./store" ;
2018-12-07 22:24:27 +00:00
import { getFilenameForPath , getFilenamePrefix , highlightDifferences , invertMap , byteArrayToString , compressLZG ,
2019-05-17 04:42:52 +00:00
byteArrayToUTF8 , isProbablyBinary , getWithBinary , getBasePlatform , getRootBasePlatform , hex } from "./util" ;
2018-08-22 03:39:34 +00:00
import { StateRecorderImpl } from "./recorder" ;
2019-05-09 17:22:24 +00:00
import { GHSession , GithubService , getRepos , parseGithubURL } from "./services" ;
2018-07-08 03:10:51 +00:00
// external libs (TODO)
2019-05-07 19:37:37 +00:00
declare var Tour , GIF , saveAs , JSZip , Mousetrap , Split , firebase ;
2019-05-17 19:45:19 +00:00
declare var ga ;
2018-08-24 03:58:35 +00:00
// in index.html
declare var exports ;
2017-01-14 05:47:26 +00:00
2018-07-08 03:10:51 +00:00
// make sure VCS doesn't start
if ( window [ 'Javatari' ] ) window [ 'Javatari' ] . AUTO_START = false ;
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?
var current_output : WorkerOutput ; // current ROM
2019-08-20 23:13:41 +00:00
var current_preset : Preset ; // current preset object (if selected)
2019-04-05 12:58:26 +00:00
var store ; // persistent store
export var compparams ; // received build params from worker
export var lastDebugState ; // last debug state (object)
var lastDebugInfo ; // last debug info (CPU text)
var debugCategory ; // current debug category
var debugTickPaused = false ;
var recorderActive = false ;
var lastBreakExpr = "c.PC == 0x6000" ;
2018-06-29 23:52:09 +00:00
// TODO: codemirror multiplex support?
2017-01-16 04:47:12 +00:00
var TOOL_TO_SOURCE_STYLE = {
'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-03-21 01:38:53 +00:00
'xasm6809' : 'z80'
2017-01-16 04:47:12 +00:00
}
2019-08-19 13:43:48 +00:00
function gaEvent ( category :string , action :string , label? :string , value? :string ) {
if ( ga ) ga ( 'send' , 'event' , category , action , label , value ) ;
}
2019-05-12 13:52:09 +00:00
function alertError ( s :string ) {
2019-08-19 15:16:02 +00:00
gaEvent ( 'error' , platform_id || 'error' , s ) ;
2019-05-20 19:21:38 +00:00
setWaitDialog ( false ) ;
2019-05-12 13:52:09 +00:00
bootbox . alert ( s ) ;
}
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 ) ;
}
2019-08-23 19:05:12 +00:00
export function loadScript ( scriptfn :string ) : Promise < Event > {
return new Promise ( ( resolve , reject ) = > {
var script = document . createElement ( 'script' ) ;
script . onload = resolve ;
script . onerror = reject ;
script . src = scriptfn ;
document . getElementsByTagName ( 'head' ) [ 0 ] . appendChild ( script ) ;
} ) ;
}
2018-07-08 14:07:19 +00:00
function newWorker ( ) : Worker {
2018-10-05 13:47:15 +00:00
return new Worker ( "./src/worker/loader.js" ) ;
2018-06-29 02:42:47 +00:00
}
2018-06-28 04:57:06 +00:00
2018-12-22 23:50:39 +00:00
var hasLocalStorage : boolean = function ( ) {
try {
const key = "__some_random_key_you_are_not_going_to_use__" ;
localStorage . setItem ( key , key ) ;
localStorage . removeItem ( key ) ;
return true ;
} catch ( e ) {
return false ;
}
} ( ) ;
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
}
2018-07-08 14:07:19 +00:00
function setLastPreset ( id :string ) {
2018-12-22 23:50:39 +00:00
if ( hasLocalStorage ) {
2019-08-10 15:11:22 +00:00
if ( repo_id && platform_id )
localStorage . setItem ( "__lastrepo_" + platform_id , repo_id ) ;
2019-05-12 13:52:09 +00:00
else
2019-08-10 15:11:22 +00:00
localStorage . removeItem ( "__lastrepo_" + platform_id ) ;
2017-04-29 15:31:11 +00:00
localStorage . setItem ( "__lastplatform" , platform_id ) ;
2019-08-10 15:11:22 +00:00
localStorage . setItem ( "__lastid_" + store_id , id ) ;
2017-04-29 15:31:11 +00:00
}
2016-12-16 01:21:51 +00:00
}
2019-05-03 23:19:24 +00:00
function unsetLastPreset() {
if ( hasLocalStorage ) {
delete qs [ 'file' ] ;
2019-05-09 12:44:47 +00:00
localStorage . removeItem ( "__lastid_" + store_id ) ;
2019-05-03 23:19:24 +00:00
}
}
2018-06-29 23:44:04 +00:00
function initProject() {
2018-06-29 02:42:47 +00:00
current_project = new CodeProject ( newWorker ( ) , platform_id , platform , store ) ;
2018-10-11 15:33:09 +00:00
projectWindows = new ProjectWindows ( $ ( "#workspace" ) [ 0 ] as HTMLElement , current_project ) ;
2019-03-02 23:15:03 +00:00
current_project . callbackGetRemote = getWithBinary ;
2018-07-26 13:43:49 +00:00
current_project . callbackBuildResult = ( result :WorkerResult ) = > {
setCompileOutput ( result ) ;
2018-07-02 13:34:20 +00:00
refreshWindowList ( ) ;
2018-06-29 02:42:47 +00:00
} ;
2018-07-25 17:29:09 +00:00
current_project . callbackBuildStatus = ( busy :boolean ) = > {
2018-06-29 02:42:47 +00:00
if ( busy ) {
toolbar . addClass ( "is-busy" ) ;
} else {
toolbar . removeClass ( "is-busy" ) ;
2018-06-30 04:42:21 +00:00
toolbar . removeClass ( "has-errors" ) ; // may be added in next callback
2018-07-26 13:43:49 +00:00
projectWindows . setErrors ( null ) ;
2018-09-05 02:28:12 +00:00
$ ( "#error_alert" ) . hide ( ) ;
2018-06-29 02:42:47 +00:00
}
$ ( '#compile_spinner' ) . css ( 'visibility' , busy ? 'visible' : 'hidden' ) ;
} ;
2018-06-29 23:44:04 +00:00
}
2018-06-29 02:42:47 +00:00
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 ) {
projectWindows . setCreateFunc ( id , createfn ) ;
2019-05-06 01:49:08 +00:00
$ ( a ) . click ( ( e ) = > {
2018-07-02 13:34:20 +00:00
projectWindows . createOrShow ( id ) ;
2018-07-14 20:10:09 +00:00
ul . find ( 'a' ) . removeClass ( "dropdown-item-checked" ) ;
ul . find ( e . target ) . addClass ( "dropdown-item-checked" ) ;
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 ] ;
2018-07-08 03:10:51 +00:00
return new Views . 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 ) {
var data = current_project . getFile ( id ) ;
if ( typeof data === 'string' )
addWindowItem ( id , getFilenameForPath ( id ) , loadEditor ) ;
else if ( data instanceof Uint8Array )
2018-12-08 00:28:11 +00:00
addWindowItem ( id , getFilenameForPath ( id ) , ( ) = > { return new Views . BinaryFileView ( id , data as Uint8Array ) ; } ) ;
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 ) = > {
if ( text && id != current_project . mainPath )
2018-12-01 09:45:55 +00:00
addEditorItem ( id ) ;
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-08-19 23:25:42 +00:00
// TODO: update listing when recompiling
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 ) = > {
return new Views . ListingView ( path ) ;
2018-07-04 01:09:58 +00:00
} ) ;
}
}
}
2018-07-02 13:34:20 +00:00
// add other tools
separate = true ;
2018-07-04 01:09:58 +00:00
if ( platform . disassemble ) {
2019-05-06 01:49:08 +00:00
addWindowItem ( "#disasm" , "Disassembly" , ( ) = > {
2018-07-08 03:10:51 +00:00
return new Views . 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" , ( ) = > {
2018-07-08 03:10:51 +00:00
return new Views . 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" , ( ) = > {
2019-02-21 21:47:25 +00:00
return new Views . MemoryMapView ( ) ;
} ) ;
}
2019-08-26 17:44:06 +00:00
if ( platform . readVRAMAddress ) {
addWindowItem ( "#memvram" , "VRAM Browser" , ( ) = > {
return new Views . 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" , ( ) = > {
2019-08-24 22:08:37 +00:00
return new Views . AddressHeatMapView ( ) ;
} ) ;
// TODO: only if raster
2019-08-27 20:10:10 +00:00
addWindowItem ( "#crtheatmap" , "CRT Probe" , ( ) = > {
2019-08-25 14:45:36 +00:00
return new Views . RasterPCHeatMapView ( ) ;
2019-08-24 15:28:04 +00:00
} ) ;
}
2019-08-26 17:44:06 +00:00
else if ( platform . getRasterScanline && platform . setBreakpoint && platform . getCPUState ) { // TODO: use profiler class to determine compat
addWindowItem ( "#profiler" , "Profiler" , ( ) = > {
return new Views . ProfileView ( ) ;
} ) ;
}
2019-05-06 01:49:08 +00:00
addWindowItem ( '#asseteditor' , 'Asset Editor' , ( ) = > {
2019-03-18 18:39:02 +00:00
return new Views . AssetEditorView ( ) ;
} ) ;
2018-07-02 13:34:20 +00:00
}
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 ) ;
}
2018-07-08 14:07:19 +00:00
function loadProject ( preset_id :string ) {
2018-06-29 23:52:09 +00:00
// set current file ID
2019-05-14 17:56:48 +00:00
// TODO: this is done twice
2018-07-06 00:13:07 +00:00
current_project . mainPath = preset_id ;
2019-05-06 01:49:08 +00:00
setLastPreset ( preset_id ) ;
2018-06-29 23:52:09 +00:00
// load files from storage or web URLs
2019-05-23 12:32:53 +00:00
current_project . loadFiles ( [ preset_id ] ) . then ( ( result ) = > {
2019-08-19 15:16:02 +00:00
measureTimeLoad = new Date ( ) ; // for timing calc.
2019-05-23 12:32:53 +00:00
if ( result && result . length ) {
2019-05-14 17:56:48 +00:00
// file found; continue
loadMainWindow ( preset_id ) ;
} else {
getSkeletonFile ( preset_id ) . then ( ( skel ) = > {
current_project . filedata [ preset_id ] = skel || "\n" ;
loadMainWindow ( preset_id ) ;
2019-08-27 23:32:44 +00:00
// don't alert if we selected "new file"
if ( ! qs [ 'newfile' ] ) {
alertInfo ( "Could not find file \"" + preset_id + "\". Loading default file." ) ;
}
delete qs [ 'newfile' ] ;
replaceURLState ( ) ;
2019-05-14 17:56:48 +00:00
} ) ;
2018-06-29 23:44:04 +00:00
}
} ) ;
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 {
qs [ 'platform' ] = platform_id ;
qs [ 'file' ] = id ;
}
2017-01-16 19:13:03 +00:00
gotoNewLocation ( ) ;
2016-12-16 01:21:51 +00:00
}
2019-05-10 18:28:09 +00:00
function getSkeletonFile ( fileid :string ) : Promise < string > {
2018-06-29 23:44:04 +00:00
var ext = platform . getToolForFilename ( fileid ) ;
2019-05-10 18:28:09 +00:00
return $ . 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" ) ;
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' ) ;
2019-08-27 23:32:44 +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 ) {
$ ( "#uploadFileElem" ) . click ( ) ;
}
2018-07-08 14:07:19 +00:00
function handleFileUpload ( files : File [ ] ) {
2018-06-26 23:57:03 +00:00
console . log ( files ) ;
var index = 0 ;
2018-11-30 15:43:23 +00:00
var gotoMainFile = ( files . length == 1 ) ;
2018-11-21 12:21:07 +00:00
function uploadNextFile() {
2018-06-26 23:57:03 +00:00
var f = files [ index ++ ] ;
if ( ! f ) {
console . log ( "Done uploading" ) ;
2018-12-01 12:19:26 +00:00
if ( gotoMainFile ) {
gotoNewLocation ( ) ;
} else {
updateSelector ( ) ;
2019-05-12 13:52:09 +00:00
alertInfo ( "Files uploaded." ) ;
2019-08-19 13:43:48 +00:00
gaEvent ( 'workspace' , 'file' , 'upload' ) ;
2018-12-01 12:19:26 +00:00
}
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 ) ) {
2018-11-30 15:43:23 +00:00
gotoMainFile = false ;
} 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-05-06 01:49:08 +00:00
// TODO: use projectWindows uploadFile()
2018-06-26 23:57:03 +00:00
store . setItem ( path , data , function ( err , result ) {
if ( err )
2019-05-12 13:52:09 +00:00
alertError ( "Error uploading " + path + ": " + err ) ;
2018-06-26 23:57:03 +00:00
else {
console . log ( "Uploaded " + path + " " + data . length + " bytes" ) ;
2019-05-03 23:19:24 +00:00
if ( index == 1 ) {
2019-05-01 19:28:15 +00:00
qs [ 'file' ] = path ; // TODO?
2019-05-03 23:19:24 +00:00
}
2018-06-26 23:57:03 +00:00
uploadNextFile ( ) ;
}
} ) ;
}
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 ( ) ;
}
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 ;
}
function getGithubService() {
if ( ! githubService ) {
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' ) ;
2019-05-08 17:42:24 +00:00
githubService = new GithubService ( exports [ '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
}
2019-05-22 21:03:56 +00:00
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 ) ;
var newstore = createNewPersistentStore ( urlparse . repopath , ( ) = > { } ) ;
// import into new store
setWaitProgress ( 0.25 ) ;
return getGithubService ( ) . import ( githuburl ) . then ( ( sess1 :GHSession ) = > {
sess = sess1 ;
setWaitProgress ( 0.75 ) ;
return getGithubService ( ) . pull ( githuburl , newstore ) ;
} ) . 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
}
2019-05-22 17:58:44 +00:00
function _loginToGithub ( e ) {
2019-05-22 18:45:03 +00:00
getGithubService ( ) . login ( ) . then ( ( ) = > {
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
} ) ;
}
function _logoutOfGithub ( e ) {
getGithubService ( ) . logout ( ) . then ( ( ) = > {
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' ) ;
btn . off ( 'click' ) . on ( 'click' , ( ) = > {
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 ) ;
2019-05-08 17:42:24 +00:00
getGithubService ( ) . login ( ) . then ( ( ) = > {
2019-05-09 12:44:47 +00:00
setWaitProgress ( 0.25 ) ;
2019-05-08 17:42:24 +00:00
return getGithubService ( ) . 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 ) ;
2019-05-08 23:15:26 +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 ;
2019-05-22 18:45:03 +00:00
bootbox . confirm ( "Pull from repository and replace all local files? Any changes you've made will be overwritten." , ( ok ) = > {
2019-05-20 19:21:38 +00:00
if ( ok ) {
setWaitDialog ( true ) ;
getGithubService ( ) . pull ( ghurl ) . then ( ( sess :GHSession ) = > {
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." ) ;
}
// 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 ) ;
}
} ) ;
} ) ;
}
2019-05-07 19:37:37 +00:00
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 ) ;
2019-05-08 17:42:24 +00:00
return getGithubService ( ) . login ( ) . then ( ( ) = > {
2019-05-09 12:44:47 +00:00
setWaitProgress ( 0.5 ) ;
2019-05-12 17:33:21 +00:00
return getGithubService ( ) . 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 ) = > {
return getGithubService ( ) . 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 ) {
if ( current_output == null ) { // TODO
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 ) ;
2019-03-03 20:37:22 +00:00
var fulllink = get8bitworkshopLink ( linkqs , 'embed.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() {
2019-08-23 19:05:12 +00:00
loadScript ( 'lib/clipboard.min.js' ) . then ( ( ) = > {
2019-03-03 20:37:22 +00:00
var ClipboardJS = exports [ 'ClipboardJS' ] ;
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 ;
var fulllink = protocol + '//' + loc . host + prefix + fn + '?' + linkqs ;
return fulllink ;
}
2018-09-25 23:46:24 +00:00
function _downloadCassetteFile ( e ) {
2019-08-06 03:47:23 +00:00
if ( current_output == null ) {
2019-05-12 13:52:09 +00:00
alertError ( "Please fix errors before exporting." ) ;
2018-09-25 23:46:24 +00:00
return true ;
}
var addr = compparams && compparams . code_start ;
if ( addr === undefined ) {
2019-05-12 13:52:09 +00:00
alertError ( "Cassette export is not supported on this platform." ) ;
2018-09-25 23:46:24 +00:00
return true ;
}
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." ;
2019-05-12 13:52:09 +00:00
alertInfo ( stdout ) ;
2018-09-25 23:46:24 +00:00
}
} ) ;
}
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
if ( qs [ 'file' ] == fn ) {
unsetLastPreset ( ) ;
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 ;
}
2018-11-22 17:28:50 +00:00
if ( current_output instanceof Uint8Array ) {
2018-08-29 12:24:13 +00:00
var blob = new Blob ( [ current_output ] , { type : "application/octet-stream" } ) ;
saveAs ( blob , getCurrentMainFilename ( ) + ".rom" ) ;
2018-11-22 17:28:50 +00:00
} else {
var blob = new Blob ( [ ( < VerilogOutput > current_output ) . code ] , { type : "text/plain" } ) ;
saveAs ( blob , getCurrentMainFilename ( ) + ".js" ) ;
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
}
2018-08-25 18:29:51 +00:00
function _downloadProjectZipFile ( e ) {
2019-08-23 19:05:12 +00:00
loadScript ( 'lib/jszip.min.js' ) . then ( ( ) = > {
2018-08-25 18:29:51 +00:00
var zip = new JSZip ( ) ;
2019-03-18 18:39:02 +00:00
current_project . iterateFiles ( ( id , data ) = > {
if ( data ) {
zip . file ( getFilenameForPath ( id ) , data ) ;
}
2018-08-25 18:29:51 +00:00
} ) ;
zip . generateAsync ( { type : "blob" } ) . then ( ( content ) = > {
saveAs ( content , getCurrentMainFilename ( ) + ".zip" ) ;
} ) ;
} ) ;
}
2018-08-26 13:19:09 +00:00
function _downloadAllFilesZipFile ( e ) {
2019-08-23 19:05:12 +00:00
loadScript ( 'lib/jszip.min.js' ) . then ( ( ) = > {
2018-08-26 13:19:09 +00:00
var zip = new JSZip ( ) ;
store . keys ( ( err , keys : string [ ] ) = > {
2019-05-10 18:28:09 +00:00
return Promise . all ( keys . map ( ( path ) = > {
return store . getItem ( path ) . then ( ( text ) = > {
2018-08-26 13:19:09 +00:00
if ( text ) {
2019-05-09 03:10:24 +00:00
zip . file ( path , text ) ;
2018-08-26 13:19:09 +00:00
}
} ) ;
2019-05-10 18:28:09 +00:00
} ) ) . then ( ( ) = > {
return zip . generateAsync ( { type : "blob" } ) ;
} ) . then ( ( content ) = > {
return saveAs ( content , platform_id + "-all.zip" ) ;
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 ) {
if ( hasLocalStorage ) {
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-05-12 19:39:09 +00:00
function populateFiles ( sel :JQuery , category :string , prefix :string , foundFiles : { } , callback : ( ) = > void ) {
2019-05-12 17:33:21 +00:00
store . keys ( ) . then ( ( keys :string [ ] ) = > {
2018-06-26 06:56:36 +00:00
var numFound = 0 ;
if ( ! keys ) keys = [ ] ;
for ( var i = 0 ; i < keys . length ; i ++ ) {
var key = keys [ i ] ;
2019-05-12 19:39:09 +00:00
if ( key . startsWith ( prefix ) && ! foundFiles [ key ] ) {
2018-06-26 06:56:36 +00:00
if ( numFound ++ == 0 )
2018-07-08 03:10:51 +00:00
sel . append ( $ ( "<option />" ) . text ( "------- " + category + " -------" ) . attr ( 'disabled' , 'true' ) ) ;
2018-06-26 06:56:36 +00:00
var name = key . substring ( prefix . length ) ;
2019-05-06 01:49:08 +00:00
sel . append ( $ ( "<option />" ) . val ( key ) . text ( name ) . attr ( 'selected' , ( key == current_project . mainPath ) ? 'selected' : null ) ) ;
2018-06-26 06:56:36 +00:00
}
}
2019-05-03 22:44:59 +00:00
if ( callback ) { callback ( ) ; }
2018-06-26 06:56:36 +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' ) ) ;
}
}
2016-12-16 01:21:51 +00:00
function updateSelector() {
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 ) ;
populateFiles ( sel , "Local Files" , "" , foundFiles , ( ) = > {
2019-05-14 17:56:48 +00:00
finishSelector ( sel ) ;
2019-05-08 23:15:26 +00:00
} ) ;
} else {
2019-05-12 13:52:09 +00:00
sel . append ( $ ( "<option />" ) . val ( '/' ) . text ( 'Leave Repository' ) ) ;
2019-05-08 23:15:26 +00:00
// repo: populate all files
2019-05-12 19:39:09 +00:00
populateFiles ( sel , repo_id , "" , { } , ( ) = > {
2019-05-14 17:56:48 +00:00
finishSelector ( sel ) ;
2019-05-03 22:44:59 +00:00
} ) ;
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
} ) ;
}
2018-09-05 02:28:12 +00:00
function showErrorAlert ( errors : WorkerError [ ] ) {
var div = $ ( "#error_alert_msg" ) . empty ( ) ;
for ( var err of errors . slice ( 0 , 10 ) ) {
var s = '' ;
if ( err . path ) s += err . path + ":" ;
if ( err . line ) s += err . line + ":" ;
s += err . msg ;
div . append ( $ ( "<p>" ) . text ( s ) ) ;
}
$ ( "#error_alert" ) . show ( ) ;
}
2019-08-19 15:16:02 +00:00
var measureTimeStart : Date = new Date ( ) ;
var measureTimeLoad : Date ;
function measureBuildTime() {
if ( ga && measureTimeLoad ) {
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-08-22 20:10:15 +00:00
gaEvent ( 'build' , platform_id ) ;
2019-08-19 15:16:02 +00:00
}
2018-07-26 13:43:49 +00:00
function setCompileOutput ( data : WorkerResult ) {
2018-07-02 13:34:20 +00:00
// errors? mark them in editor
2018-06-25 23:47:40 +00:00
if ( data . errors && 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 ) ;
showErrorAlert ( data . errors ) ;
2016-12-16 01:21:51 +00:00
} else {
2018-07-03 14:22:21 +00:00
// process symbol map
2018-09-17 20:09:09 +00:00
platform . debugSymbols = new DebugSymbols ( data . symbolmap ) ;
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 ;
2018-07-10 01:46:45 +00:00
if ( rom ) { // TODO instanceof Uint8Array) {
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 ( ) ;
2016-12-31 16:05:22 +00:00
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 ( ) ;
2018-08-02 17:08:37 +00:00
// TODO: reset profiler etc? (Tell views?)
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" ) ;
2018-09-15 22:47:40 +00:00
showErrorAlert ( [ { msg :e + "" , line :0 } ] ) ;
2016-12-16 01:21:51 +00:00
current_output = null ;
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)
2018-08-02 17:08:37 +00:00
projectWindows . refresh ( false ) ;
2016-12-16 01:21:51 +00:00
}
}
2019-05-08 02:36:06 +00:00
function loadBIOSFromProject() {
if ( platform . loadBIOS ) {
2019-05-12 19:39:09 +00:00
var biospath = platform_id + '.rom' ;
2019-05-08 02:36:06 +00:00
store . getItem ( biospath ) . then ( ( biosdata ) = > {
2019-05-26 23:22:51 +00:00
if ( biosdata instanceof Uint8Array ) {
console . log ( 'loading BIOS' )
platform . loadBIOS ( 'BIOS' , biosdata ) ;
}
2019-05-08 02:36:06 +00:00
} ) ;
}
}
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 {
2018-07-29 19:53:50 +00:00
meminfo . hide ( ) ;
2016-12-16 01:21:51 +00:00
lastDebugInfo = null ;
}
}
2018-07-08 14:07:19 +00:00
function setDebugButtonState ( btnid :string , btnstate :string ) {
2018-07-07 05:09:15 +00:00
$ ( "#debug_bar" ) . find ( "button" ) . removeClass ( "btn_active" ) . removeClass ( "btn_stopped" ) ;
$ ( "#dbg_" + btnid ) . addClass ( "btn_" + btnstate ) ;
}
2018-11-24 21:51:29 +00:00
function checkRunReady() {
if ( current_output == null ) {
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-04-03 20:40:16 +00:00
function uiDebugCallback ( state ) {
lastDebugState = state ;
showDebugInfo ( state ) ;
projectWindows . refresh ( true ) ;
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 ) {
2018-09-16 23:45:32 +00:00
if ( platform . setupDebug ) platform . setupDebug ( ( state ) = > {
2019-04-03 20:40:16 +00:00
uiDebugCallback ( state ) ;
2018-09-15 19:27:12 +00:00
setDebugButtonState ( btnid || "pause" , "stopped" ) ;
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() {
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-24 21:51:29 +00:00
if ( ! checkRunReady ( ) ) return ;
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" ) ;
2017-11-16 17:08:06 +00:00
}
function resume() {
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 ;
2016-12-16 01:21:51 +00:00
}
2018-09-13 00:54:25 +00:00
function togglePause() {
if ( platform . isRunning ( ) )
pause ( ) ;
else
resume ( ) ;
}
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
}
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
}
function runToCursor() {
2018-11-24 21:51:29 +00:00
if ( ! checkRunReady ( ) ) return ;
2018-07-07 05:09:15 +00:00
setupBreakpoint ( "toline" ) ;
2018-07-02 13:34:20 +00:00
var pc = getEditorPC ( ) ;
2017-01-14 02:31:04 +00:00
if ( pc >= 0 ) {
2017-02-05 04:19:54 +00:00
console . log ( "Run to" , pc . toString ( 16 ) ) ;
2018-06-18 08:12:52 +00:00
if ( platform . runToPC ) {
platform . runToPC ( pc ) ;
} else {
2018-12-01 14:48:30 +00:00
platform . runEval ( ( c ) = > {
2018-06-18 08:12:52 +00:00
return c . PC == pc ;
} ) ;
}
2016-12-16 01:21:51 +00:00
}
}
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
}
function resetAndDebug() {
2019-05-21 17:54:51 +00:00
if ( ! checkRunReady ( ) ) return ;
2018-08-23 22:52:56 +00:00
_disableRecording ( ) ;
2017-11-29 01:48:27 +00:00
if ( platform . setupDebug && platform . readAddress ) { // TODO??
2017-11-11 19:45:32 +00:00
clearBreakpoint ( ) ;
2017-11-16 17:08:06 +00:00
_resume ( ) ;
2017-11-11 19:45:32 +00:00
platform . reset ( ) ;
2018-07-07 05:09:15 +00:00
setupBreakpoint ( "reset" ) ;
2018-06-22 06:24:52 +00:00
if ( platform . runEval )
2018-12-01 14:48:30 +00:00
platform . runEval ( ( c ) = > { return true ; } ) ; // break immediately
2018-06-22 06:24:52 +00:00
else
; // TODO???
2017-11-11 19:45:32 +00:00
} else {
platform . reset ( ) ;
}
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
}
2018-07-08 14:07:19 +00:00
function getSymbolAtAddress ( a : number ) {
2018-09-17 20:09:09 +00:00
var addr2symbol = platform . debugSymbols && platform . debugSymbols . addr2symbol ;
if ( addr2symbol ) {
if ( addr2symbol [ a ] ) return addr2symbol [ a ] ;
var i = 0 ;
while ( -- a >= 0 ) {
i ++ ;
if ( addr2symbol [ a ] ) return addr2symbol [ a ] + '+' + i ;
}
2017-04-22 01:56:49 +00:00
}
return '' ;
}
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 ;
2019-08-23 19:05:12 +00:00
loadScript ( "gif.js/dist/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 ( {
workerScript : 'gif.js/dist/gif.worker.js' ,
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' ) ;
} ) ;
2019-05-01 20:47:04 +00:00
var intervalMsec = 33 ;
var maxFrames = 200 ;
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 ( ) ;
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
}
}
function _addIncludeFile() {
var fn = getCurrentMainFilename ( ) ;
var tool = platform . getToolForFilename ( fn ) ;
if ( fn . endsWith ( ".c" ) || tool == 'sdcc' || tool == 'cc65' )
addFileToProject ( "Header" , ".h" , ( s ) = > { return '#include "' + s + '"' } ) ;
2018-11-24 21:08:33 +00:00
else if ( tool == 'dasm' || tool == 'zmac' )
addFileToProject ( "Include File" , ".inc" , ( s ) = > { return '\tinclude "' + s + '"' } ) ;
else if ( tool == 'ca65' || tool == 'sdasz80' )
addFileToProject ( "Include File" , ".inc" , ( s ) = > { return '\t.include "' + s + '"' } ) ;
2018-11-24 20:43:08 +00:00
else if ( tool == 'verilator' )
addFileToProject ( "Verilog File" , ".v" , ( s ) = > { return '`include "' + s + '"' } ) ;
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 ) ;
if ( fn . endsWith ( ".c" ) || tool == 'sdcc' || tool == 'cc65' )
2019-05-28 01:52:00 +00:00
addFileToProject ( "Linked C (or .s)" , ".c" , ( s ) = > { return '//#link "' + s + '"' } ) ;
2019-05-23 03:23:55 +00:00
else if ( fn . endsWith ( "asm" ) || fn . endsWith ( ".s" ) || tool == 'ca65' )
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 ) ;
2019-04-04 02:23:58 +00:00
uitoolbar . grp . prop ( 'id' , 'debug_bar' ) ;
2019-08-05 15:15:26 +00:00
uitoolbar . add ( 'ctrl+alt+r' , 'Reset' , 'glyphicon-refresh' , resetAndDebug ) . prop ( 'id' , 'dbg_reset' ) ;
uitoolbar . add ( 'ctrl+alt+,' , 'Pause' , 'glyphicon-pause' , pause ) . prop ( 'id' , 'dbg_pause' ) ;
uitoolbar . add ( 'ctrl+alt+.' , 'Resume' , 'glyphicon-play' , resume ) . prop ( 'id' , 'dbg_go' ) ;
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
}
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
}
2018-09-13 00:54:25 +00:00
if ( platform . runUntilReturn ) {
2019-04-05 12:58:26 +00:00
uitoolbar . add ( 'ctrl+alt+o' , 'Step Out of Subroutine' , 'glyphicon-hand-up' , runUntilReturn ) . prop ( 'id' , 'dbg_stepout' ) ;
2019-04-04 02:23:58 +00:00
}
2018-09-13 00:54:25 +00:00
if ( platform . stepBack ) {
2019-04-05 12:58:26 +00:00
uitoolbar . add ( 'ctrl+alt+b' , 'Step Backwards' , 'glyphicon-step-backward' , runStepBackwards ) . prop ( 'id' , 'dbg_stepback' ) ;
2019-04-04 02:23:58 +00:00
}
uitoolbar . newGroup ( ) ;
2018-08-17 19:13:58 +00:00
if ( platform . newCodeAnalyzer ) {
2019-04-04 02:23:58 +00:00
uitoolbar . add ( null , 'Analyze CPU Timing' , 'glyphicon-time' , traceTiming ) ;
2017-01-16 15:35:19 +00:00
}
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 ) ;
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 ) ;
2019-05-08 23:15:26 +00:00
if ( platform_id . startsWith ( 'apple2' ) )
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 ) ;
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 ) ;
}
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" ) ;
2018-08-23 20:02:13 +00:00
var replayframeno = $ ( "#replay_frame" ) ;
var updateFrameNo = ( n ) = > {
replayframeno . text ( n + "" ) ;
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 ( ) ;
2018-08-23 20:02:13 +00:00
var frame = ( < any > e . target ) . value ;
if ( stateRecorder . loadFrame ( frame ) ) {
updateFrameNo ( frame ) ;
2019-06-09 15:13:25 +00:00
projectWindows . tick ( ) ;
2018-08-23 20:02:13 +00:00
}
} ;
var setFrameTo = ( frame :number ) = > {
_pause ( ) ;
if ( stateRecorder . loadFrame ( frame ) ) {
replayslider . val ( frame ) ;
updateFrameNo ( frame ) ;
2019-06-09 15:13:25 +00:00
projectWindows . tick ( ) ;
2018-08-23 20:02:13 +00:00
console . log ( 'seek to frame' , frame ) ;
}
} ;
stateRecorder . callbackStateChanged = ( ) = > {
replayslider . attr ( 'min' , 1 ) ;
replayslider . attr ( 'max' , stateRecorder . numFrames ( ) ) ;
2018-08-23 22:52:56 +00:00
replayslider . val ( stateRecorder . currentFrame ( ) ) ;
updateFrameNo ( stateRecorder . currentFrame ( ) ) ;
2018-08-23 20:02:13 +00:00
} ;
replayslider . on ( 'input' , sliderChanged ) ;
replayslider . on ( 'change' , sliderChanged ) ;
$ ( "#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 ) ; } ) ;
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 ;
}
2016-12-18 20:59:31 +00:00
function showWelcomeMessage() {
2018-12-22 23:50:39 +00:00
if ( hasLocalStorage && ! localStorage . getItem ( "8bitworkshop.hello" ) ) {
2016-12-30 23:51:15 +00:00
// Instance the tour
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' ,
2016-12-30 23:51:15 +00:00
title : "Welcome to 8bitworkshop!" ,
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" ,
2017-04-20 00:55:13 +00:00
content : is_vcs ? "Type your 6502 assembly code into the editor, and it'll be assembled in real-time. All changes are saved to browser local storage."
2018-07-06 23:12:58 +00:00
: "Type your source code into the editor, and it'll be compiled in real-time. All changes are saved to browser local storage."
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
}
] ;
2018-07-07 14:55:27 +00:00
steps . push ( {
element : "#booksMenuButton" ,
2019-08-22 17:40:53 +00:00
placement : 'left' ,
title : "Books" ,
2019-08-15 16:18:16 +00:00
content : "Get some books that explain how to program all of this stuff, and write some games!"
2018-07-07 14:55:27 +00:00
} ) ;
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."
} ) ;
}
2018-07-06 23:12:58 +00:00
var tour = new Tour ( {
autoscroll :false ,
//storage:false,
steps :steps
} ) ;
2019-05-06 01:49:08 +00:00
setTimeout ( ( ) = > { tour . start ( ) ; } , 2000 ) ;
2016-12-18 20:59:31 +00:00
}
}
2016-12-16 01:21:51 +00:00
///////////////////////////////////////////////////
2018-07-08 03:10:51 +00:00
var qs = ( function ( a : string [ ] ) {
if ( ! a || a . length == 0 )
2016-12-16 01:21:51 +00:00
return { } ;
var b = { } ;
for ( var i = 0 ; i < a . length ; ++ i ) {
var p = a [ i ] . split ( '=' , 2 ) ;
if ( p . length == 1 )
b [ p [ 0 ] ] = "" ;
else
b [ p [ 0 ] ] = decodeURIComponent ( p [ 1 ] . replace ( /\+/g , " " ) ) ;
}
return b ;
} ) ( window . location . search . substr ( 1 ) . split ( '&' ) ) ;
2018-07-04 02:14:07 +00:00
// catch errors
function installErrorHandler() {
2019-05-10 18:28:09 +00:00
if ( typeof window . onerror == "object" ) {
2018-07-04 02:14:07 +00:00
window . onerror = function ( msgevent , url , line , col , error ) {
2019-08-22 17:14:35 +00:00
var msgstr = msgevent + "" ;
2019-03-21 00:45:03 +00:00
console . log ( msgevent , url , line , col , error ) ;
2019-05-17 19:45:19 +00:00
// emulation threw EmuHalt
2019-03-21 00:45:03 +00:00
if ( error instanceof EmuHalt || msgstr . indexOf ( "CPU STOP" ) >= 0 ) {
showErrorAlert ( [ { msg :msgstr , line :0 } ] ) ;
2019-08-16 09:53:49 +00:00
uiDebugCallback ( platform . saveState && platform . saveState ( ) ) ;
2019-08-06 03:47:23 +00:00
setDebugButtonState ( "pause" , "stopped" ) ;
2019-03-21 00:45:03 +00:00
} else {
2019-05-17 19:45:19 +00:00
// send exception msg to GA
2019-06-03 14:08:29 +00:00
var msg = msgstr ;
2019-06-08 16:01:22 +00:00
//if (typeof error == 'string') msg += ": " + error;
2019-08-16 09:53:49 +00:00
if ( url ) msg += " " + url ;
2019-06-03 14:08:29 +00:00
if ( line ) msg += " (" + line + ":" + col + ")" ;
2019-05-17 19:45:19 +00:00
if ( msg . length > 256 ) { msg = msg . substring ( 0 , 256 ) ; }
if ( ga ) ga ( 'send' , 'exception' , {
'exDescription' : msg ,
'exFatal' : true
} ) ;
2019-05-06 01:49:08 +00:00
$ . get ( "/error?msg=" + encodeURIComponent ( msg ) , "text" ) ;
2019-05-17 19:45:19 +00:00
alertError ( msg ) ;
2019-03-21 00:45:03 +00:00
}
2018-08-26 01:03:29 +00:00
_pause ( ) ;
2018-07-04 02:14:07 +00:00
} ;
2019-08-22 17:14:35 +00:00
}
if ( typeof window . onunhandledrejection == "object" ) {
window . onunhandledrejection = function ( event ) {
var msg = ( event && event . reason ) + "" ;
if ( ga ) ga ( 'send' , 'exception' , {
'exDescription' : msg ,
'exFatal' : true
} ) ;
alertError ( msg ) ;
}
2019-05-10 18:28:09 +00:00
}
2018-07-04 02:14:07 +00:00
}
function uninstallErrorHandler() {
window . onerror = null ;
}
2019-05-22 21:03:56 +00:00
function gotoNewLocation ( replaceHistory? : boolean ) {
2018-07-04 02:14:07 +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() {
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" , ( ) = > {
2017-05-02 13:09:53 +00:00
if ( document . visibilityState == 'hidden' && 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" , ( ) = > {
2017-05-02 13:09:53 +00:00
if ( 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
} ) ;
}
2019-08-22 14:30:33 +00:00
// TODO: merge w/ embed.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 ) ) ;
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() {
if ( ga ) {
$ ( ".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
}
} ) ;
2019-08-22 14:30:33 +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
}
}
2017-01-14 16:14:25 +00:00
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 ) ;
2017-01-14 16:14:25 +00:00
PRESETS = platform . getPresets ( ) ;
2018-08-23 21:46:25 +00:00
if ( ! qs [ 'file' ] ) {
2017-01-16 15:35:19 +00:00
// try to load last file (redirect)
2018-12-22 23:50:39 +00:00
var lastid ;
if ( hasLocalStorage ) {
2019-05-09 12:44:47 +00:00
lastid = localStorage . getItem ( "__lastid_" + store_id ) ;
2018-12-22 23:50:39 +00:00
}
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 ) ;
qs [ 'file' ] = defaultfile || 'DEFAULT' ;
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
if ( platform_id == 'vcs' && qs [ 'file' ] . startsWith ( 'examples/' ) && ! qs [ 'file' ] . endsWith ( '.a' ) ) {
qs [ 'file' ] += '.a' ;
}
2018-08-23 21:46:25 +00:00
// start platform and load file
2019-05-09 12:44:47 +00:00
replaceURLState ( ) ;
2018-08-23 21:46:25 +00:00
platform . start ( ) ;
2019-06-06 19:40:31 +00:00
// TODO: ordering of loads?
2019-08-20 23:13:41 +00:00
installGAHooks ( ) ;
2019-05-08 02:36:06 +00:00
loadBIOSFromProject ( ) ;
2018-08-23 21:46:25 +00:00
initProject ( ) ;
loadProject ( qs [ 'file' ] ) ;
2018-11-20 19:39:33 +00:00
setupDebugControls ( ) ;
2018-08-23 21:46:25 +00:00
updateSelector ( ) ;
addPageFocusHandlers ( ) ;
2019-05-17 04:42:52 +00:00
showInstructions ( ) ;
2019-05-22 14:53:39 +00:00
revealTopBar ( ) ;
2018-08-23 21:46:25 +00:00
return true ;
2017-01-14 16:14:25 +00:00
}
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() {
2018-11-24 16:33:28 +00:00
const splitName = 'workspace-split3-' + platform_id ;
var sizes = [ 0 , 50 , 50 ] ;
2019-05-09 12:44:47 +00:00
if ( ! platform_id . startsWith ( 'vcs' ) )
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 : ( ) = > {
2018-12-22 23:50:39 +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?
setWaitDialog ( true ) ;
getWithBinary ( url , ( data ) = > {
if ( data ) {
2019-05-12 19:39:09 +00:00
var path = 'shared/' + getFilenameForPath ( url ) ; // TODO: shared prefix?
2019-05-01 19:28:15 +00:00
// TODO: progress dialog
console . log ( "Importing " + data . length + " bytes as " + path ) ;
store . getItem ( path , ( err , olddata ) = > {
setWaitDialog ( false ) ;
if ( ! olddata || confirm ( "Replace existing file '" + path + "'?" ) ) {
store . setItem ( path , data , ( err , result ) = > {
if ( err )
2019-05-12 13:52:09 +00:00
alert ( err + "" ) ; // need to wait
2019-05-01 19:28:15 +00:00
if ( result != null ) {
delete qs [ 'importURL' ] ;
qs [ 'file' ] = path ;
replaceURLState ( ) ;
loadAndStartPlatform ( ) ;
}
} ) ;
}
} ) ;
} 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' ) ;
}
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 ) ;
}
2017-01-03 01:42:15 +00:00
// start
2018-09-16 23:45:32 +00:00
export function startUI ( loadplatform : boolean ) {
2017-01-16 19:13:03 +00:00
installErrorHandler ( ) ;
2019-05-09 12:44:47 +00:00
// import from github?
if ( qs [ 'githubURL' ] ) {
2019-05-22 21:03:56 +00:00
importProjectFromGithub ( qs [ 'githubURL' ] , true ) ;
2019-05-09 12:44:47 +00:00
return ;
}
2019-08-10 15:11:22 +00:00
// add default platform?
platform_id = qs [ 'platform' ] || ( hasLocalStorage && localStorage . getItem ( "__lastplatform" ) ) ;
if ( ! platform_id ) {
platform_id = qs [ 'platform' ] = "vcs" ;
}
2019-08-22 14:30:33 +00:00
// lookup repository for this platform
2019-08-10 15:11:22 +00:00
repo_id = qs [ 'repo' ] || ( hasLocalStorage && localStorage . getItem ( "__lastrepo_" + platform_id ) ) ;
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 ] ;
2019-05-12 13:52:09 +00:00
if ( repo ) {
qs [ 'repo' ] = repo_id ;
2019-08-15 01:31:38 +00:00
if ( repo . platform_id )
qs [ 'platform' ] = platform_id = repo . platform_id ;
2019-05-12 13:52:09 +00:00
if ( ! qs [ 'file' ] )
qs [ 'file' ] = repo . mainPath ;
}
} else {
repo_id = '' ;
delete qs [ 'repo' ] ;
2019-05-09 12:44:47 +00:00
}
2018-11-23 18:29:11 +00:00
setupSplits ( ) ;
2019-05-08 23:15:26 +00:00
// create store
store_id = repo_id || getBasePlatform ( platform_id ) ;
store = createNewPersistentStore ( store_id , ( store ) = > {
// is this an importURL?
if ( qs [ 'importURL' ] ) {
loadImportedURL ( qs [ 'importURL' ] ) ;
return ;
}
2019-05-09 03:10:24 +00:00
// is vcs? convert legacy stuff
convertLegacyVCS ( store ) ;
2019-05-08 23:15:26 +00:00
// load and start platform object
if ( loadplatform ) {
loadAndStartPlatform ( ) ;
} else {
startPlatform ( ) ;
2019-05-22 14:53:39 +00:00
revealTopBar ( ) ;
2019-05-08 23:15:26 +00:00
}
} ) ;
2016-12-16 01:21:51 +00:00
}
2019-05-01 19:28:15 +00:00
function loadAndStartPlatform() {
2019-08-23 19:05:12 +00:00
var platformfn = 'gen/platform/' + platform_id . split ( /[.-]/ ) [ 0 ] + '.js' ; // required file
var machinefn = platformfn . replace ( '/platform/' , '/machine/' ) ; // optional file
loadScript ( platformfn ) . then ( ( ) = > {
return loadScript ( machinefn ) . catch ( ( ) = > { console . log ( 'skipped' , machinefn ) ; } ) ; // optional file skipped
} ) . then ( ( ) = > {
console . log ( "starting platform" , platform_id ) ; // loaded required <platform_id>.js file
2019-05-22 14:53:39 +00:00
try {
startPlatform ( ) ;
showWelcomeMessage ( ) ;
document . title = document . title + " [" + platform_id + "] - " + ( repo_id ? ( '[' + repo_id + '] - ' ) : '' ) + current_project . mainPath ;
} finally {
revealTopBar ( ) ;
}
2019-08-23 19:05:12 +00:00
} ) . catch ( ( ) = > {
2019-05-12 13:52:09 +00:00
alertError ( 'Platform "' + platform_id + '" not supported.' ) ;
2019-05-01 19:28:15 +00:00
} ) ;
}
2019-05-09 03:10:24 +00:00
// TODO: remove eventually
function convertLegacyVCS ( store ) {
if ( platform_id == 'vcs' && hasLocalStorage && ! localStorage . getItem ( "__migratevcs" ) ) {
store . keys ( ) . then ( ( keys :string [ ] ) = > {
keys . forEach ( ( key ) = > {
if ( key . startsWith ( 'examples/' ) && ! key . endsWith ( '.a' ) ) {
store . getItem ( key ) . then ( ( val ) = > {
if ( val ) {
return store . setItem ( key + '.a' , val ) ;
}
} ) ;
}
} ) ;
localStorage . setItem ( "__migratevcs" , "1" ) ;
} )
}
}
2019-05-18 22:33:38 +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 ( ) ) {
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 ( ) ;