2025-02-13 19:04:05 -05:00
// 1 - border
// 10 - padding
// 35 - header
const _windowPaddingX : number = 1 * 2 + 10 * 2 ;
const _windowPaddingY : number = 1 * 2 + 10 * 2 + 35 ;
2025-01-05 16:10:40 -05:00
let WINDOWS : { [ key : string ] : _winConf } = { } ;
let MOUSE_MOVE_PROCESSING : {
[ key : string ] : {
callback : ( x : number , y : number ) = > void ,
mouseUp : boolean
}
} = { } ;
let globalIncrement : number = 1 ;
function escapeHTML ( string : string ) : string {
return string . replaceAll ( "&" , "&" ) . replaceAll ( "<" , "<" ) . replaceAll ( "\"" , """ ) ;
}
function incrementZIndex ( windowID : string , focus : boolean = false ) : void {
WINDOWS [ windowID ] . element . style . zIndex = String ( globalIncrement ) ;
WINDOWS [ windowID ] . zIndex = globalIncrement ;
globalIncrement ++ ;
if ( focus ) {
WINDOWS [ windowID ] . element . focus ( ) ;
}
}
2025-02-13 19:04:05 -05:00
function edgeMoveEvent ( x : number , y : number , pos : "top" | "bottom" | "left" | "right" | "top-left" | "top-right" | "bottom-left" | "bottom-right" | string , windowID : string ) : void {
let w : _winConf = WINDOWS [ windowID ] ;
2025-02-13 19:31:05 -05:00
if ( w . fullscreen ) { return ; }
2025-02-13 19:04:05 -05:00
if ( pos == "top" || pos . startsWith ( "top-" ) ) {
w . height = Math . max ( - ( y - w . vars . mouseOffsetY - w . vars . startingPosY ) + w . vars . startingHeight , w . minHeight ) ;
w . posY = Math . max ( w . vars . startingHeight - w . height + w . vars . startingPosY , 0 ) ;
2025-02-13 19:14:21 -05:00
if ( w . posY < 0 ) {
w . height -= w . posY ;
w . posY = 0 ;
}
2025-02-13 19:04:05 -05:00
} else if ( pos == "bottom" || pos . startsWith ( "bottom-" ) ) {
w . height = Math . max ( y - w . vars . mouseOffsetY - w . vars . startingPosY + w . vars . startingHeight , w . minHeight ) ;
}
if ( pos == "left" || pos . endsWith ( "-left" ) ) {
w . width = Math . max ( - ( x - w . vars . mouseOffsetX - w . vars . startingPosX ) + w . vars . startingWidth , w . minWidth ) ;
w . posX = Math . max ( w . vars . startingWidth - w . width + w . vars . startingPosX , 0 ) ;
2025-02-13 19:14:21 -05:00
if ( w . posX < 0 ) {
w . width -= w . posX ;
w . posX = 0 ;
}
2025-02-13 19:04:05 -05:00
} else if ( pos == "right" || pos . endsWith ( "-right" ) ) {
w . width = Math . max ( x - w . vars . mouseOffsetX - w . vars . startingPosX + w . vars . startingWidth , w . minWidth ) ;
}
2025-02-13 19:14:21 -05:00
if ( w . posX + w . width + _windowPaddingX > innerWidth ) { w . width = Math . max ( innerWidth - w . posX - _windowPaddingX , w . minWidth ) ; }
if ( w . posY + w . height + _windowPaddingY > innerHeight ) { w . height = Math . max ( innerHeight - w . posY - _windowPaddingY , w . minHeight ) ; }
2025-02-13 19:04:05 -05:00
w . element . style . left = ` ${ w . posX } px ` ;
w . element . style . top = ` ${ w . posY } px ` ;
w . element . style . width = ` ${ w . width + _windowPaddingX - 2 } px ` ;
( w . element . querySelector ( ".window" ) as HTMLElement ) . style . width = ` ${ w . width } px ` ;
( w . element . querySelector ( ".window" ) as HTMLElement ) . style . height = ` ${ w . height } px ` ;
}
2025-03-10 18:09:59 -04:00
function mouseMoveEvent ( windowID : string , x : number , y : number ) : void {
let w : _winConf = WINDOWS [ windowID ] ;
w . posX = Math . max ( 0 , Math . min ( innerWidth - w . width - _windowPaddingX , x - w . vars . mouseOffsetX ) ) ;
w . posY = Math . max ( 0 , Math . min ( innerHeight - w . height - _windowPaddingY , y - w . vars . mouseOffsetY ) ) ;
w . element . style . left = ` ${ w . posX } px ` ;
w . element . style . top = ` ${ w . posY } px ` ;
}
function syncInputs ( windowID : string ) : void {
let windowInput : HTMLInputElement = WINDOWS [ windowID ] . element . querySelector ( "input.window-input" ) ;
let windowVisualText : HTMLDivElement = WINDOWS [ windowID ] . element . querySelector ( "[data-type-area]" ) ;
let w : HTMLDivElement = WINDOWS [ windowID ] . element . querySelector ( ".window" ) ;
if ( ! windowVisualText ) { return ; }
setTimeout ( function ( ) : void {
let text : string = windowInput . value ;
let cursor : number = windowInput . selectionStart ;
if ( cursor == text . length ) {
windowVisualText . innerHTML = ` ${ escapeHTML ( text ) } <i class="cursor"> </i> ` ;
} else {
windowVisualText . innerHTML = ` ${ escapeHTML ( text . slice ( 0 , cursor ) ) } <span class="cursor"> ${ escapeHTML ( text [ cursor ] ) } </span> ${ escapeHTML ( text . slice ( cursor + 1 ) ) } ` ;
}
} , 1 ) ;
w . scrollTop = w . scrollHeight ;
}
function setCursor ( windowID : string ) : void {
let windowInput : HTMLInputElement = WINDOWS [ windowID ] . element . querySelector ( "input.window-input" ) ;
setTimeout ( ( ) : void = > {
windowInput . setSelectionRange ( windowInput . value . length , windowInput . value . length ) ;
syncInputs ( windowID ) ;
} , 1 ) ;
}
function toggleFullscreen ( windowID : string ) : void {
let w : _winConf = WINDOWS [ windowID ] ;
if ( w . fullscreen ) {
w . posX = Math . max ( 0 , Math . min ( innerWidth - w . vars . oldWidth - _windowPaddingX , w . vars . oldPosX ) ) ; // window.vars.oldPosX;
w . posY = Math . max ( 0 , Math . min ( innerHeight - w . vars . oldHeight - _windowPaddingY , w . vars . oldPosY ) ) ; // window.vars.oldPosY;
w . width = Math . max ( w . minWidth , Math . min ( w . vars . oldWidth , innerWidth - _windowPaddingX ) ) ; // window.vars.oldWidth;
w . height = Math . max ( w . minHeight , Math . min ( w . vars . oldHeight , innerHeight - _windowPaddingY ) ) ; // window.vars.oldHeight;
w . fullscreen = false ;
delete w . vars . oldPosX ;
delete w . vars . oldPosY ;
delete w . vars . oldWidth ;
delete w . vars . oldHeight ;
w . element . style . left = ` ${ w . posX } px ` ;
w . element . style . top = ` ${ w . posY } px ` ;
w . element . style . width = ` ${ w . width + _windowPaddingX - 2 } px ` ;
( w . element . querySelector ( ".window" ) as HTMLElement ) . style . width = ` ${ w . width } px ` ;
( w . element . querySelector ( ".window" ) as HTMLElement ) . style . height = ` ${ w . height } px ` ;
} else {
w . vars . oldPosX = w . posX ;
w . vars . oldPosY = w . posY ;
w . vars . oldWidth = w . width ;
w . vars . oldHeight = w . height ;
w . fullscreen = true ;
w . posX = 0 ;
w . posY = 0 ;
w . width = innerWidth ;
w . height = innerHeight ;
w . element . style . left = "0px" ;
w . element . style . top = "0px" ;
w . element . style . width = ` ${ w . width } px ` ;
( w . element . querySelector ( ".window" ) as HTMLElement ) . style . width = ` ${ w . width - _windowPaddingX } px ` ;
( w . element . querySelector ( ".window" ) as HTMLElement ) . style . height = ` ${ w . height - _windowPaddingY } px ` ;
}
}
2025-01-05 16:10:40 -05:00
function createWindow ( config : _winInitConf ) : void {
if ( document . getElementById ( config . id ) ) {
incrementZIndex ( config . id ) ;
return ;
}
config . width = config . width || 600 ;
config . height = config . height || 400 ;
config . minWidth = config . minWidth || 200 ;
config . minHeight = config . minHeight || 200 ;
let realWidth : number = Math . max ( config . minWidth , Math . min ( config . width , innerWidth - _windowPaddingX - 20 ) ) ;
let realHeight : number = Math . max ( config . minHeight , Math . min ( config . height , innerHeight - _windowPaddingY - 20 ) ) ;
let posX : number = config . posX || Math . round ( ( innerWidth / 2 ) - ( ( realWidth + _windowPaddingX ) / 2 ) ) ;
let posY : number = config . posY || Math . round ( ( innerHeight / 2 ) - ( ( realHeight + _windowPaddingY ) / 2 ) ) ;
let wO : HTMLDivElement = document . createElement ( "div" ) ;
wO . classList . add ( "window-outer" ) ;
let w : HTMLDivElement = document . createElement ( "div" ) ;
w . classList . add ( "window" ) ;
w . style . width = ` ${ realWidth } px ` ;
w . style . height = ` ${ realHeight } px ` ;
w . innerHTML = config . content ;
let wH : HTMLDivElement = document . createElement ( "div" ) ;
wH . classList . add ( "window-header" ) ;
wH . innerHTML = `
< i class = "window-header-button blank" > < / i >
< i class = "window-header-button blank" > < / i >
< i class = "window-header-button blank" > < / i >
< strong class = "window-header-title" > $ { config . title } < / strong >
< i data - no - move class = "window-header-button minimize" > < / i >
< i data - no - move class = "window-header-button fullscreen" > < / i >
< i data - no - move class = "window-header-button close" > < / i >
` ;
let wC : HTMLDivElement | HTMLLabelElement ;
let wI : HTMLInputElement = null ;
2025-02-13 19:04:05 -05:00
2025-01-05 16:10:40 -05:00
if ( config . typeable !== false ) {
2025-02-13 19:04:05 -05:00
wI = document . createElement ( "input" ) ;
wI . classList . add ( "window-input" ) ;
wI . id = ` ${ config . id } __input ` ;
2025-01-05 16:10:40 -05:00
wI . oninput = ( event : KeyboardEvent ) : void = > {
2025-03-10 18:09:59 -04:00
syncInputs ( config . id ) ;
2025-01-05 16:10:40 -05:00
w . scrollTop = w . scrollHeight ;
} ;
wI . onkeydown = ( event : KeyboardEvent ) : void = > {
if ( event . key == "Enter" ) {
commandManager ( config . id , wI . value . trim ( ) ) ;
w . scrollTop = w . scrollHeight ;
wI . value = "" ;
2025-03-10 18:09:59 -04:00
} else if ( event . key == "Tab" ) {
event . preventDefault ( ) ;
let val : string = wI . value . trim ( ) ;
let possibilities : string [ ] = [ ] ;
let parent : _file | null ;
if ( ! val ) { return ; }
if ( val . split ( " " ) . length == 1 && wI . value [ wI . value . length - 1 ] != " " ) {
possibilities = Object . keys ( _internal_commands ) . filter ( ( cmd : string ) : boolean = > ( cmd . startsWith ( val ) && ! cmd . startsWith ( "_" ) ) ) ;
} else if ( _internal_commands [ val . split ( " " ) [ 0 ] ] && _internal_commands [ val . split ( " " ) [ 0 ] ] . autocomplete ) {
let ac : "dir" | "file" | string [ ] = _internal_commands [ val . split ( " " ) [ 0 ] ] . autocomplete ;
let path : string = val . split ( " " ) . slice ( 1 ) . join ( " " ) . trim ( ) ;
let sw : string = path . split ( "/" ) [ path . split ( "/" ) . length - 1 ] ;
if ( typeof ac == "object" ) {
possibilities = ac ;
} else {
if ( path ) {
if ( path [ path . length - 1 ] == "/" ) {
parent = _internal_getFile ( _internal_sanitizePath ( _internal_joinPaths ( windowInformation [ config . id ] . PWD , path ) ) ) ;
} else {
parent = _internal_getFile ( _internal_sanitizePath ( _internal_joinPaths ( windowInformation [ config . id ] . PWD , path + "/.." ) ) ) ;
}
} else {
parent = _internal_getFile ( windowInformation [ config . id ] . PWD ) ;
}
if ( parent && parent . type == "directory" ) {
let f : _files = parent . files ;
possibilities = Object . keys ( f ) ;
if ( ac == "dir" ) {
possibilities = possibilities . filter ( ( file : string ) : boolean = > ( f [ file ] && f [ file ] . type == "directory" ) ) ;
}
} else {
parent = null ;
}
}
possibilities = possibilities . filter ( ( v : string ) : boolean = > v . startsWith ( sw ) ) ;
}
if ( possibilities . length == 1 ) {
if ( val . split ( " " ) . length == 1 && wI . value [ wI . value . length - 1 ] != " " ) {
wI . value = possibilities [ 0 ] + " " ;
} else if ( _internal_commands [ val . split ( " " ) [ 0 ] ] && _internal_commands [ val . split ( " " ) [ 0 ] ] . autocomplete ) {
let path : string = val ;
if ( val [ val . length - 1 ] == "/" ) {
path += possibilities [ 0 ] ;
} else {
let p : string [ ] = path . split ( "/" ) ;
if ( p . length == 1 ) {
p = p [ 0 ] . split ( " " , 2 ) ;
if ( p . length == 1 ) {
p . push ( "" ) ;
}
p . pop ( ) ;
path = p . join ( " " ) + " " + possibilities [ 0 ] ;
} else {
p . pop ( ) ;
path = p . join ( "/" ) + "/" + possibilities [ 0 ] ;
}
}
wI . value = path + ( parent && parent . type == "directory" && parent . files [ possibilities [ 0 ] ] . type == "directory" ? "/" : " " ) ;
}
syncInputs ( config . id ) ;
} else if ( possibilities ) {
addWindowCommand ( config . id , possibilities . join ( " " ) ) ;
syncInputs ( config . id ) ;
}
2025-01-05 16:10:40 -05:00
} else {
2025-03-10 18:09:59 -04:00
syncInputs ( config . id ) ;
2025-01-05 16:10:40 -05:00
}
} ;
2025-03-10 18:09:59 -04:00
wI . onfocus = ( ) : void = > { setCursor ( config . id ) ; } ;
wI . onclick = ( ) : void = > { setCursor ( config . id ) ; } ;
2025-01-05 16:10:40 -05:00
wC = document . createElement ( "label" ) ;
wC . htmlFor = ` ${ config . id } __input ` ;
} else {
wC = document . createElement ( "div" ) ;
}
wC . classList . add ( "window-container" ) ;
wC . tabIndex = 0 ;
wC . style . left = ` ${ posX } px ` ;
wC . style . top = ` ${ posY } px ` ;
wC . id = config . id ;
wC . style . zIndex = String ( globalIncrement ) ;
wC . style . width = ` ${ realWidth + _windowPaddingX - 2 } px ` ;
2025-02-13 19:04:05 -05:00
let edges : DocumentFragment = document . createDocumentFragment ( ) ;
for ( const pos of [ "top" , "bottom" , "left" , "right" , "top-left" , "top-right" , "bottom-left" , "bottom-right" ] ) {
let el : HTMLDivElement = document . createElement ( "div" ) ;
el . classList . add ( "edge" , pos ) ;
el . addEventListener ( "mousedown" , function ( e : MouseEvent ) : void {
2025-03-10 18:09:59 -04:00
incrementZIndex ( config . id ) ;
2025-02-13 19:04:05 -05:00
e . preventDefault ( ) ;
WINDOWS [ config . id ] . vars . mouseOffsetX = e . clientX - WINDOWS [ config . id ] . posX ;
WINDOWS [ config . id ] . vars . mouseOffsetY = e . clientY - WINDOWS [ config . id ] . posY ;
WINDOWS [ config . id ] . vars . startingWidth = WINDOWS [ config . id ] . width ;
WINDOWS [ config . id ] . vars . startingHeight = WINDOWS [ config . id ] . height ;
WINDOWS [ config . id ] . vars . startingPosX = WINDOWS [ config . id ] . posX ;
WINDOWS [ config . id ] . vars . startingPosY = WINDOWS [ config . id ] . posY ;
MOUSE_MOVE_PROCESSING [ config . id ] = {
callback : ( x : number , y : number ) : void = > { edgeMoveEvent ( x , y , pos , config . id ) ; } ,
mouseUp : true
} ;
} ) ;
edges . append ( el ) ;
}
2025-01-05 16:10:40 -05:00
wO . append ( w ) ;
2025-02-13 19:04:05 -05:00
wC . append ( wH , wO , edges ) ;
2025-01-05 16:10:40 -05:00
document . body . append ( wC ) ;
if ( config . typeable !== false ) {
wC . append ( wI ) ;
wI . focus ( ) ;
} else {
wC . focus ( ) ;
}
WINDOWS [ config . id ] = {
element : wC ,
height : realHeight ,
width : realWidth ,
2025-02-13 19:04:05 -05:00
minHeight : config.minHeight ,
minWidth : config.minWidth ,
2025-01-05 16:10:40 -05:00
posX : posX ,
posY : posY ,
fullscreen : false ,
zIndex : globalIncrement ,
vars : { }
} ;
2025-03-10 18:09:59 -04:00
windowInformation [ config . id ] = {
PWD : HOME_DIR
} ;
2025-01-05 16:10:40 -05:00
// wC.addEventListener("mousedown", function(): void { incrementZIndex(config.id); });
wC . addEventListener ( "focus" , function ( ) : void { incrementZIndex ( config . id ) ; } ) ;
for ( const link of wC . querySelectorAll ( "a" ) ) {
link . addEventListener ( "focus" , function ( ) : void { incrementZIndex ( config . id ) ; } ) ;
}
wH . addEventListener ( "mousedown" , function ( e : MouseEvent ) : void {
if ( ( e . target as HTMLElement ) . dataset . noMove !== undefined ) {
return ;
}
WINDOWS [ config . id ] . vars . mouseOffsetX = e . clientX - WINDOWS [ config . id ] . posX ;
WINDOWS [ config . id ] . vars . mouseOffsetY = e . clientY - WINDOWS [ config . id ] . posY ;
MOUSE_MOVE_PROCESSING [ config . id ] = {
2025-03-10 18:09:59 -04:00
callback : ( x : number , y : number ) : void = > { mouseMoveEvent ( config . id , x , y ) ; } ,
2025-01-05 16:10:40 -05:00
mouseUp : true
} ;
} ) ;
wH . querySelector ( ".close" ) . addEventListener ( "click" , function ( ) : void {
delete WINDOWS [ config . id ] ;
delete windowInformation [ config . id ] ;
delete MOUSE_MOVE_PROCESSING [ config . id ] ;
wC . remove ( ) ;
if ( typeof config . onDestroy === "function" ) {
config . onDestroy ( ) ;
}
} ) ;
2025-03-10 18:09:59 -04:00
wH . querySelector ( ".fullscreen" ) . addEventListener ( "click" , ( ) : void = > ( toggleFullscreen ( config . id ) ) ) ;
wH . addEventListener ( "dblclick" , ( ) : void = > ( toggleFullscreen ( config . id ) ) ) ;
2025-01-05 16:10:40 -05:00
globalIncrement ++ ;
}
2025-02-15 10:57:30 -05:00
function windowPreset ( template : string , dontDisableTyping : boolean = false ) : void {
2025-01-05 16:10:40 -05:00
let el : HTMLElement = document . querySelector ( ` #window-templates > [data-template-id=" ${ template } "] ` ) ;
if ( ! el ) { return ; }
if ( WINDOWS [ template ] ) {
incrementZIndex ( template , true ) ;
return ;
}
let config : _winInitConf = {
id : template ,
title : "~ - tSh" ,
content : "<div><b class=\"green\">trinkey@website</b>:<b class=\"blue\">~</b>$ <span data-type-area><i class=\"cursor\"> </i></span></div>"
} ;
for ( const field of el . querySelectorAll ( "[data-template-field]" ) ) {
config [ ( field as HTMLElement ) . dataset . templateField ] = ( field as HTMLElement ) . dataset . isNumber === "" ? + ( field as HTMLElement ) . innerText : ( field as HTMLElement ) . innerHTML ;
}
createWindow ( config ) ;
for ( const command of el . querySelectorAll ( "li" ) ) {
WINDOWS [ template ] . element . querySelector ( "[data-type-area]" ) . innerHTML = command . innerHTML ;
commandManager ( template , command . innerHTML ) ;
}
2025-02-15 10:57:30 -05:00
if ( ! dontDisableTyping ) {
WINDOWS [ template ] . element . querySelector ( "input" ) . remove ( ) ;
}
}
function emptyWindow ( ) : void {
createWindow ( {
id : ` terminal- ${ Math . random ( ) } ` ,
title : "~ - tSh" ,
content : "<div><b class=\"green\">trinkey@website</b>:<b class=\"blue\">~</b>$ <span data-type-area><i class=\"cursor\"> </i></span></div>"
} ) ;
2025-01-05 16:10:40 -05:00
}
function copyButton ( ) : void {
navigator . clipboard . writeText ( "<a href=\"https://trinkey.com/\" target=\"_blank\"><img src=\"https://trinkey.com/img/88x31.png\" alt=\"trinkey's 88x31. image of her cat on the right with the word 'trinkey' taking up the rest of the button.\" title=\"trinkey's 88x31. image of her cat on the right with the word 'trinkey' taking up the rest of the button.\"></a>" ) ;
}
2025-02-13 19:04:05 -05:00
onmousemove = function ( e : MouseEvent ) : void {
e . preventDefault ( ) ;
for ( const key of Object . keys ( MOUSE_MOVE_PROCESSING ) ) {
MOUSE_MOVE_PROCESSING [ key ] . callback ( e . clientX , e . clientY ) ;
}
}
onmouseup = function ( ) : void {
for ( const key of Object . keys ( MOUSE_MOVE_PROCESSING ) ) {
if ( MOUSE_MOVE_PROCESSING [ key ] . mouseUp ) {
delete MOUSE_MOVE_PROCESSING [ key ] ;
} ;
}
}
onresize = function ( ) : void {
for ( const window of Object . keys ( WINDOWS ) ) {
2025-02-13 19:31:05 -05:00
let w : _winConf = WINDOWS [ window ] ;
2025-02-13 19:04:05 -05:00
2025-02-13 19:31:05 -05:00
if ( w . fullscreen ) {
w . width = innerWidth - _windowPaddingX + 2 ;
w . height = innerHeight - _windowPaddingY + 2 ;
} else {
w . posX = Math . max ( 0 , Math . min ( innerWidth - w . width - _windowPaddingX , w . posX ) ) ;
w . posY = Math . max ( 0 , Math . min ( innerHeight - w . height - _windowPaddingY , w . posY ) ) ;
2025-02-13 19:04:05 -05:00
2025-02-13 19:31:05 -05:00
w . width = Math . max ( w . minWidth , Math . min ( w . width , innerWidth - _windowPaddingX ) ) ;
w . height = Math . max ( w . minHeight , Math . min ( w . height , innerHeight - _windowPaddingY ) ) ;
}
2025-02-13 19:04:05 -05:00
2025-02-13 19:31:05 -05:00
w . element . style . left = ` ${ w . posX } px ` ;
w . element . style . top = ` ${ w . posY } px ` ;
w . element . style . width = ` ${ w . width + _windowPaddingX - 2 } px ` ;
( w . element . querySelector ( ".window" ) as HTMLElement ) . style . width = ` ${ w . width } px ` ;
( w . element . querySelector ( ".window" ) as HTMLElement ) . style . height = ` ${ w . height } px ` ;
2025-02-13 19:04:05 -05:00
}
}