2024-03-05 05:00:26 +08:00
/ * *
* Copyright ( C ) 2024 Puter Technologies Inc .
*
* This file is part of Puter .
*
* Puter is free software : you can redistribute it and / or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation , either version 3 of the License , or
* ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU Affero General Public License for more details .
*
* You should have received a copy of the GNU Affero General Public License
* along with this program . If not , see < https : //www.gnu.org/licenses/>.
* /
2024-03-03 11:59:32 +08:00
2024-03-03 10:39:14 +08:00
import path from "./lib/path.js"
import mime from "./lib/mime.js" ;
import UIAlert from './UI/UIAlert.js'
import UIItem from './UI/UIItem.js'
import UIWindow from './UI/UIWindow.js'
import UIWindowLogin from './UI/UIWindowLogin.js' ;
import UIWindowSaveAccount from './UI/UIWindowSaveAccount.js' ;
import UIWindowCopyProgress from './UI/UIWindowCopyProgress.js' ;
import UIWindowMoveProgress from './UI/UIWindowMoveProgress.js' ;
import UIWindowNewFolderProgress from './UI/UIWindowNewFolderProgress.js' ;
import UIWindowDownloadProgress from './UI/UIWindowDownloadProgress.js' ;
import UIWindowUploadProgress from './UI/UIWindowUploadProgress.js' ;
import UIWindowProgressEmptyTrash from './UI/UIWindowProgressEmptyTrash.js' ;
import download from './helpers/download.js' ;
import update _username _in _gui from './helpers/update_username_in_gui.js' ;
import update _title _based _on _uploads from './helpers/update_title_based_on_uploads.js' ;
import content _type _to _icon from './helpers/content_type_to_icon.js' ;
import UIWindowDownloadDirProg from './UI/UIWindowDownloadDirProg.js' ;
2024-04-15 15:37:04 +08:00
import { PROCESS _RUNNING , PortalProcess , PseudoProcess } from "./definitions.js" ;
2024-03-03 10:39:14 +08:00
window . is _auth = ( ) => {
if ( localStorage . getItem ( "auth_token" ) === null || auth _token === null )
return false ;
else
return true ;
}
window . suggest _apps _for _fsentry = async ( options ) => {
let res = await $ . ajax ( {
url : api _origin + "/suggest_apps" ,
type : 'POST' ,
contentType : "application/json" ,
data : JSON . stringify ( {
uid : options . uid ? ? undefined ,
path : options . path ? ? undefined ,
} ) ,
headers : {
"Authorization" : "Bearer " + auth _token
} ,
statusCode : {
401 : function ( ) {
logout ( ) ;
} ,
} ,
success : function ( res ) {
if ( options . onSuccess && typeof options . onSuccess == "function" )
options . onSuccess ( res ) ;
}
} ) ;
return res ;
}
/ * *
* Formats a binary - byte integer into the human - readable form with units .
*
* @ param { integer } bytes
* @ returns
* /
window . byte _format = ( bytes ) => {
const sizes = [ 'Bytes' , 'KB' , 'MB' , 'GB' , 'TB' ] ;
if ( bytes === 0 ) return '0 Byte' ;
const i = parseInt ( Math . floor ( Math . log ( bytes ) / Math . log ( 1024 ) ) ) ;
return ( bytes / Math . pow ( 1024 , i ) ) . toFixed ( 2 ) + ' ' + sizes [ i ] ;
} ;
/ * *
* A function that generates a UUID ( Universally Unique Identifier ) using the version 4 format ,
* which are random UUIDs . It uses the cryptographic number generator available in modern browsers .
*
* The generated UUID is a 36 character string ( 32 alphanumeric characters separated by 4 hyphens ) .
* It follows the pattern : xxxxxxxx - xxxx - 4 xxx - yxxx - xxxxxxxxxxxx , where x is any hexadecimal digit
* and y is one of 8 , 9 , A , or B .
*
* @ returns { string } Returns a new UUID v4 string .
*
* @ example
*
* let id = window . uuidv4 ( ) ; // Generate a new UUID
*
* /
window . uuidv4 = ( ) => {
return ( [ 1e7 ] + - 1e3 + - 4e3 + - 8e3 + - 1e11 ) . replace ( /[018]/g , c =>
( c ^ crypto . getRandomValues ( new Uint8Array ( 1 ) ) [ 0 ] & 15 >> c / 4 ) . toString ( 16 )
) ;
}
/ * *
* Checks if the provided string is a valid email format .
*
* @ function
* @ global
* @ param { string } email - The email string to be validated .
* @ returns { boolean } ` true ` if the email is valid , otherwise ` false ` .
* @ example
* window . is _email ( "test@example.com" ) ; // true
* window . is _email ( "invalid-email" ) ; // false
* /
window . is _email = ( email ) => {
const re = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ ;
return re . test ( String ( email ) . toLowerCase ( ) ) ;
}
/ * *
* A function that truncates a file name if it exceeds a certain length , while preserving the file extension .
* An ellipsis character '…' is added to indicate the truncation . If the original filename is short enough ,
* it is returned unchanged .
*
* @ param { string } input - The original filename to be potentially truncated .
* @ param { number } max _length - The maximum length for the filename . If the original filename ( excluding the extension ) exceeds this length , it will be truncated .
*
* @ returns { string } The truncated filename with preserved extension if original filename is too long ; otherwise , the original filename .
*
* @ example
*
* let truncatedFilename = window . truncate _filename ( 'really_long_filename.txt' , 10 ) ;
* // truncatedFilename would be something like 'really_lo…me.txt'
*
* /
window . truncate _filename = ( input , max _length ) => {
const extname = path . extname ( '/' + input ) ;
if ( ( input . length - 15 ) > max _length ) {
if ( extname !== '' )
return input . substring ( 0 , max _length ) + '…' + input . slice ( - 1 * ( extname . length + 2 ) ) ;
else
return input . substring ( 0 , max _length ) + '…' ;
}
return input ;
} ;
/ * *
* A function that scrolls the parent element so that the child element is in view .
* If the child element is already in view , no scrolling occurs .
* The function decides the best scroll direction based on which requires the smaller adjustment .
*
* @ param { HTMLElement } parent - The parent HTML element that might be scrolled .
* @ param { HTMLElement } child - The child HTML element that should be made viewable .
*
* @ returns { void }
*
* @ example
*
* let parentElem = document . querySelector ( '#parent' ) ;
* let childElem = document . querySelector ( '#child' ) ;
* window . scrollParentToChild ( parentElem , childElem ) ;
* // Scrolls parentElem so that childElem is in view
*
* /
window . scrollParentToChild = ( parent , child ) => {
// Where is the parent on page
var parentRect = parent . getBoundingClientRect ( ) ;
// What can you see?
var parentViewableArea = {
height : parent . clientHeight ,
width : parent . clientWidth
} ;
// Where is the child
var childRect = child . getBoundingClientRect ( ) ;
// Is the child viewable?
var isViewable = ( childRect . top >= parentRect . top ) && ( childRect . bottom <= parentRect . top + parentViewableArea . height ) ;
// if you can't see the child try to scroll parent
if ( ! isViewable ) {
// Should we scroll using top or bottom? Find the smaller ABS adjustment
const scrollTop = childRect . top - parentRect . top ;
const scrollBot = childRect . bottom - parentRect . bottom ;
if ( Math . abs ( scrollTop ) < Math . abs ( scrollBot ) ) {
// we're near the top of the list
parent . scrollTop += ( scrollTop + 80 ) ;
} else {
// we're near the bottom of the list
parent . scrollTop += ( scrollBot + 80 ) ;
}
}
}
window . getItem = async function ( options ) {
return $ . ajax ( {
url : api _origin + "/getItem" ,
type : 'POST' ,
data : JSON . stringify ( {
key : options . key ,
app : options . app _uid ,
} ) ,
async : true ,
contentType : "application/json" ,
headers : {
"Authorization" : "Bearer " + auth _token
} ,
statusCode : {
401 : function ( ) {
logout ( ) ;
} ,
} ,
success : function ( result ) {
if ( options . success && typeof ( options . success ) === "function" )
options . success ( result ) ;
}
} )
}
window . setItem = async function ( options ) {
return $ . ajax ( {
url : api _origin + "/setItem" ,
type : 'POST' ,
data : JSON . stringify ( {
app : options . app _uid ,
key : options . key ,
value : options . value ,
} ) ,
async : true ,
contentType : "application/json" ,
headers : {
"Authorization" : "Bearer " + auth _token
} ,
statusCode : {
401 : function ( ) {
logout ( ) ;
} ,
} ,
success : function ( fsentry ) {
if ( options . success && typeof ( options . success ) === "function" )
options . success ( fsentry )
}
} )
}
/ * *
* Converts a glob pattern to a regular expression , with optional extended or globstar matching .
*
* @ param { string } glob - The glob pattern to convert .
* @ param { Object } [ opts ] - Optional options for the conversion .
* @ param { boolean } [ opts . extended = false ] - If true , enables extended matching with single character matching , character ranges , group matching , etc .
* @ param { boolean } [ opts . globstar = false ] - If true , uses globstar matching , where '*' matches zero or more path segments .
* @ param { string } [ opts . flags ] - Regular expression flags to include ( e . g . , 'i' for case - insensitive ) .
* @ returns { RegExp } The generated regular expression .
* @ throws { TypeError } If the provided glob pattern is not a string .
* /
window . globToRegExp = function ( glob , opts ) {
if ( typeof glob !== 'string' ) {
throw new TypeError ( 'Expected a string' ) ;
}
var str = String ( glob ) ;
// The regexp we are building, as a string.
var reStr = "" ;
// Whether we are matching so called "extended" globs (like bash) and should
// support single character matching, matching ranges of characters, group
// matching, etc.
var extended = opts ? ! ! opts . extended : false ;
// When globstar is _false_ (default), '/foo/*' is translated a regexp like
// '^\/foo\/.*$' which will match any string beginning with '/foo/'
// When globstar is _true_, '/foo/*' is translated to regexp like
// '^\/foo\/[^/]*$' which will match any string beginning with '/foo/' BUT
// which does not have a '/' to the right of it.
// E.g. with '/foo/*' these will match: '/foo/bar', '/foo/bar.txt' but
// these will not '/foo/bar/baz', '/foo/bar/baz.txt'
// Lastely, when globstar is _true_, '/foo/**' is equivelant to '/foo/*' when
// globstar is _false_
var globstar = opts ? ! ! opts . globstar : false ;
// If we are doing extended matching, this boolean is true when we are inside
// a group (eg {*.html,*.js}), and false otherwise.
var inGroup = false ;
// RegExp flags (eg "i" ) to pass in to RegExp constructor.
var flags = opts && typeof ( opts . flags ) === "string" ? opts . flags : "" ;
var c ;
for ( var i = 0 , len = str . length ; i < len ; i ++ ) {
c = str [ i ] ;
switch ( c ) {
case "/" :
case "$" :
case "^" :
case "+" :
case "." :
case "(" :
case ")" :
case "=" :
case "!" :
case "|" :
reStr += "\\" + c ;
break ;
case "?" :
if ( extended ) {
reStr += "." ;
break ;
}
case "[" :
case "]" :
if ( extended ) {
reStr += c ;
break ;
}
case "{" :
if ( extended ) {
inGroup = true ;
reStr += "(" ;
break ;
}
case "}" :
if ( extended ) {
inGroup = false ;
reStr += ")" ;
break ;
}
case "," :
if ( inGroup ) {
reStr += "|" ;
break ;
}
reStr += "\\" + c ;
break ;
case "*" :
// Move over all consecutive "*"'s.
// Also store the previous and next characters
var prevChar = str [ i - 1 ] ;
var starCount = 1 ;
while ( str [ i + 1 ] === "*" ) {
starCount ++ ;
i ++ ;
}
var nextChar = str [ i + 1 ] ;
if ( ! globstar ) {
// globstar is disabled, so treat any number of "*" as one
reStr += ".*" ;
} else {
// globstar is enabled, so determine if this is a globstar segment
var isGlobstar = starCount > 1 // multiple "*"'s
&& ( prevChar === "/" || prevChar === undefined ) // from the start of the segment
&& ( nextChar === "/" || nextChar === undefined ) // to the end of the segment
if ( isGlobstar ) {
// it's a globstar, so match zero or more path segments
reStr += "((?:[^/]*(?:\/|$))*)" ;
i ++ ; // move over the "/"
} else {
// it's not a globstar, so only match one path segment
reStr += "([^/]*)" ;
}
}
break ;
default :
reStr += c ;
}
}
// When regexp 'g' flag is specified don't
// constrain the regular expression with ^ & $
if ( ! flags || ! ~ flags . indexOf ( 'g' ) ) {
reStr = "^" + reStr + "$" ;
}
return new RegExp ( reStr , flags ) ;
} ;
/ * *
* Validates the provided file system entry name .
*
* @ function validate _fsentry _name
* @ memberof window
* @ param { string } name - The name of the file system entry to validate .
* @ returns { boolean } Returns true if the name is valid .
* @ throws { Object } Throws an object with a ` message ` property indicating the specific validation error .
*
* @ description
* This function checks the provided name against a set of rules to determine its validity as a file system entry name :
* 1. Name cannot be empty .
* 2. Name must be a string .
* 3. Name cannot contain the '/' character .
* 4. Name cannot be the '.' character .
* 5. Name cannot be the '..' character .
* 6. Name cannot exceed the maximum allowed length ( as defined in window . max _item _name _length ) .
* /
window . validate _fsentry _name = function ( name ) {
if ( ! name )
2024-03-18 07:01:06 +08:00
throw { message : i18n ( 'name_cannot_be_empty' ) }
2024-03-03 10:39:14 +08:00
else if ( ! isString ( name ) )
2024-03-18 07:01:06 +08:00
throw { message : i18n ( 'name_must_be_string' ) }
2024-03-03 10:39:14 +08:00
else if ( name . includes ( '/' ) )
2024-03-18 07:01:06 +08:00
throw { message : i18n ( 'name_cannot_contain_slash' ) }
2024-03-03 10:39:14 +08:00
else if ( name === '.' )
2024-03-18 07:01:06 +08:00
throw { message : i18n ( 'name_cannot_contain_period' ) } ;
2024-03-03 10:39:14 +08:00
else if ( name === '..' )
2024-03-18 07:01:06 +08:00
throw { message : i18n ( 'name_cannot_contain_double_period' ) } ;
2024-03-03 10:39:14 +08:00
else if ( name . length > window . max _item _name _length )
2024-03-18 07:01:06 +08:00
throw { message : i18n ( 'name_too_long' , config . max _item _name _length ) }
2024-03-03 10:39:14 +08:00
else
return true
}
/ * *
* A function that generates a unique identifier by combining a random adjective , a random noun , and a random number ( between 0 and 9999 ) .
* The result is returned as a string with components separated by hyphens .
* It is useful when you need to create unique identifiers that are also human - friendly .
*
* @ returns { string } A unique , hyphen - separated string comprising of an adjective , a noun , and a number .
*
* @ example
*
* let identifier = window . generate _identifier ( ) ;
* // identifier would be something like 'clever-idea-123'
*
* /
window . generate _identifier = function ( ) {
const first _adj = [ 'helpful' , 'sensible' , 'loyal' , 'honest' , 'clever' , 'capable' , 'calm' , 'smart' , 'genius' , 'bright' , 'charming' , 'creative' , 'diligent' , 'elegant' , 'fancy' ,
'colorful' , 'avid' , 'active' , 'gentle' , 'happy' , 'intelligent' , 'jolly' , 'kind' , 'lively' , 'merry' , 'nice' , 'optimistic' , 'polite' ,
'quiet' , 'relaxed' , 'silly' , 'victorious' , 'witty' , 'young' , 'zealous' , 'strong' , 'brave' , 'agile' , 'bold' ] ;
const nouns = [ 'street' , 'roof' , 'floor' , 'tv' , 'idea' , 'morning' , 'game' , 'wheel' , 'shoe' , 'bag' , 'clock' , 'pencil' , 'pen' ,
'magnet' , 'chair' , 'table' , 'house' , 'dog' , 'room' , 'book' , 'car' , 'cat' , 'tree' ,
'flower' , 'bird' , 'fish' , 'sun' , 'moon' , 'star' , 'cloud' , 'rain' , 'snow' , 'wind' , 'mountain' ,
'river' , 'lake' , 'sea' , 'ocean' , 'island' , 'bridge' , 'road' , 'train' , 'plane' , 'ship' , 'bicycle' ,
'horse' , 'elephant' , 'lion' , 'tiger' , 'bear' , 'zebra' , 'giraffe' , 'monkey' , 'snake' , 'rabbit' , 'duck' ,
'goose' , 'penguin' , 'frog' , 'crab' , 'shrimp' , 'whale' , 'octopus' , 'spider' , 'ant' , 'bee' , 'butterfly' , 'dragonfly' ,
'ladybug' , 'snail' , 'camel' , 'kangaroo' , 'koala' , 'panda' , 'piglet' , 'sheep' , 'wolf' , 'fox' , 'deer' , 'mouse' , 'seal' ,
'chicken' , 'cow' , 'dinosaur' , 'puppy' , 'kitten' , 'circle' , 'square' , 'garden' , 'otter' , 'bunny' , 'meerkat' , 'harp' ]
// return a random combination of first_adj + noun + number (between 0 and 9999)
// e.g. clever-idea-123
return first _adj [ Math . floor ( Math . random ( ) * first _adj . length ) ] + '-' + nouns [ Math . floor ( Math . random ( ) * nouns . length ) ] + '-' + Math . floor ( Math . random ( ) * 10000 ) ;
}
/ * *
* Checks if the provided variable is a string or an instance of the String object .
*
* @ param { * } variable - The variable to check .
* @ returns { boolean } True if the variable is a string or an instance of the String object , false otherwise .
* /
window . isString = function ( variable ) {
return typeof variable === 'string' || variable instanceof String ;
}
/ * *
* A function that checks whether a file system entry ( fsentry ) matches a list of allowed file types .
* It handles both file extensions ( like '.jpg' ) and MIME types ( like 'text/plain' ) .
* If the allowed file types string is empty or not provided , the function always returns true .
* It checks the file types only if the fsentry is a file , not a directory .
*
* @ param { Object } fsentry - The file system entry to check . It must be an object with properties : 'is_dir' , 'name' , 'type' .
* @ param { string } allowed _file _types _string - The list of allowed file types , separated by commas . Can include extensions and MIME types .
*
* @ returns { boolean } True if the fsentry matches one of the allowed file types , or if the allowed _file _types _string is empty or not provided . False otherwise .
*
* @ example
*
* let fsentry = { is _dir : false , name : 'example.jpg' , type : 'image/jpeg' } ;
* let allowedTypes = '.jpg, text/plain, image/*' ;
* let result = window . check _fsentry _against _allowed _file _types _string ( fsentry , allowedTypes ) ;
* // result would be true, as 'example.jpg' matches the '.jpg' in allowedTypes
*
* /
window . check _fsentry _against _allowed _file _types _string = function ( fsentry , allowed _file _types _string ) {
// simple cases that are always a pass
if ( ! allowed _file _types _string || allowed _file _types _string . trim ( ) === '' )
return true ;
// parse allowed_file_types into an array of extensions and types
let allowed _file _types = allowed _file _types _string . split ( ',' ) ;
if ( allowed _file _types . length > 0 ) {
// trim every entry
for ( let index = 0 ; index < allowed _file _types . length ; index ++ ) {
allowed _file _types [ index ] = allowed _file _types [ index ] . trim ( ) ;
}
}
let passes _allowed _file _type _filter = true ;
// check types, only if this fsentry is a file and not a directory
if ( ! fsentry . is _dir && allowed _file _types . length > 0 ) {
passes _allowed _file _type _filter = false ;
for ( let index = 0 ; index < allowed _file _types . length ; index ++ ) {
const allowed _file _type = allowed _file _types [ index ] . toLowerCase ( ) ;
// if type is not already set, try to set it based on the file name
if ( ! fsentry . type )
fsentry . type = mime . getType ( fsentry . name ) ;
// extensions (e.g. .jpg)
if ( allowed _file _type . startsWith ( '.' ) && fsentry . name . toLowerCase ( ) . endsWith ( allowed _file _type ) ) {
passes _allowed _file _type _filter = true ;
break ;
}
// MIME types (e.g. text/plain)
else if ( globToRegExp ( allowed _file _type ) . test ( fsentry . type ? . toLowerCase ( ) ) ) {
passes _allowed _file _type _filter = true ;
break ;
}
}
}
return passes _allowed _file _type _filter ;
}
// @author Rich Adams <rich@richadams.me>
// Implements a tap and hold functionality. If you click/tap and release, it will trigger a normal
// click event. But if you click/tap and hold for 1s (default), it will trigger a taphold event instead.
; ( function ( $ )
{
// Default options
var defaults = {
duration : 500 , // ms
clickHandler : null
}
// When start of a taphold event is triggered.
function startHandler ( event )
{
var $elem = jQuery ( this ) ;
// Merge the defaults and any user defined settings.
let settings = jQuery . extend ( { } , defaults , event . data ) ;
// If object also has click handler, store it and unbind. Taphold will trigger the
// click itself, rather than normal propagation.
if ( typeof $elem . data ( "events" ) != "undefined"
&& typeof $elem . data ( "events" ) . click != "undefined" )
{
// Find the one without a namespace defined.
for ( var c in $elem . data ( "events" ) . click )
{
if ( $elem . data ( "events" ) . click [ c ] . namespace == "" )
{
var handler = $elem . data ( "events" ) . click [ c ] . handler
$elem . data ( "taphold_click_handler" , handler ) ;
$elem . unbind ( "click" , handler ) ;
break ;
}
}
}
// Otherwise, if a custom click handler was explicitly defined, then store it instead.
else if ( typeof settings . clickHandler == "function" )
{
$elem . data ( "taphold_click_handler" , settings . clickHandler ) ;
}
// Reset the flags
$elem . data ( "taphold_triggered" , false ) ; // If a hold was triggered
$elem . data ( "taphold_clicked" , false ) ; // If a click was triggered
$elem . data ( "taphold_cancelled" , false ) ; // If event has been cancelled.
// Set the timer for the hold event.
$elem . data ( "taphold_timer" ,
setTimeout ( function ( )
{
// If event hasn't been cancelled/clicked already, then go ahead and trigger the hold.
if ( ! $elem . data ( "taphold_cancelled" )
&& ! $elem . data ( "taphold_clicked" ) )
{
// Trigger the hold event, and set the flag to say it's been triggered.
$elem . trigger ( jQuery . extend ( event , jQuery . Event ( "taphold" ) ) ) ;
$elem . data ( "taphold_triggered" , true ) ;
}
} , settings . duration ) ) ;
}
// When user ends a tap or click, decide what we should do.
function stopHandler ( event )
{
var $elem = jQuery ( this ) ;
// If taphold has been cancelled, then we're done.
if ( $elem . data ( "taphold_cancelled" ) ) { return ; }
// Clear the hold timer. If it hasn't already triggered, then it's too late anyway.
clearTimeout ( $elem . data ( "taphold_timer" ) ) ;
// If hold wasn't triggered and not already clicked, then was a click event.
if ( ! $elem . data ( "taphold_triggered" )
&& ! $elem . data ( "taphold_clicked" ) )
{
// If click handler, trigger it.
if ( typeof $elem . data ( "taphold_click_handler" ) == "function" )
{
$elem . data ( "taphold_click_handler" ) ( jQuery . extend ( event , jQuery . Event ( "click" ) ) ) ;
}
// Set flag to say we've triggered the click event.
$elem . data ( "taphold_clicked" , true ) ;
}
}
// If a user prematurely leaves the boundary of the object we're working on.
function leaveHandler ( event )
{
// Cancel the event.
$ ( this ) . data ( "taphold_cancelled" , true ) ;
}
// Determine if touch events are supported.
var touchSupported = ( "ontouchstart" in window ) // Most browsers
|| ( "onmsgesturechange" in window ) ; // Microsoft
var taphold = $ . event . special . taphold =
{
setup : function ( data )
{
$ ( this ) . bind ( ( touchSupported ? "touchstart" : "mousedown" ) , data , startHandler )
. bind ( ( touchSupported ? "touchend" : "mouseup" ) , stopHandler )
. bind ( ( touchSupported ? "touchmove touchcancel" : "mouseleave" ) , leaveHandler ) ;
} ,
teardown : function ( namespaces )
{
$ ( this ) . unbind ( ( touchSupported ? "touchstart" : "mousedown" ) , startHandler )
. unbind ( ( touchSupported ? "touchend" : "mouseup" ) , stopHandler )
. unbind ( ( touchSupported ? "touchmove touchcancel" : "mouseleave" ) , leaveHandler ) ;
}
} ;
} ) ( jQuery ) ;
window . refresh _user _data = async ( auth _token ) => {
let whoami
try {
whoami = await puter . os . user ( ) ;
} catch ( e ) {
}
// update local user data
if ( whoami ) {
update _auth _data ( auth _token , whoami )
2024-03-09 11:06:14 +08:00
}
2024-03-03 10:39:14 +08:00
}
window . update _auth _data = ( auth _token , user ) => {
window . auth _token = auth _token ;
localStorage . setItem ( 'auth_token' , auth _token ) ;
// Has username changed?
if ( window . user ? . username !== user . username )
update _username _in _gui ( user . username ) ;
2024-04-15 12:09:41 +08:00
// Has email changed?
if ( window . user ? . email !== user . email && user . email ) {
$ ( '.user-email' ) . html ( user . email ) ;
}
2024-03-03 10:39:14 +08:00
// update this session's user data
window . user = user ;
localStorage . setItem ( 'user' , JSON . stringify ( window . user ) ) ;
// re-initialize the Puter.js objects with the new auth token
puter . setAuthToken ( auth _token , api _origin )
//update the logged_in_users array entry for this user
if ( window . user ) {
let logged _in _users _updated = false ;
for ( let i = 0 ; i < window . logged _in _users . length && ! logged _in _users _updated ; i ++ ) {
if ( window . logged _in _users [ i ] . uuid === window . user . uuid ) {
window . logged _in _users [ i ] = window . user ;
window . logged _in _users [ i ] . auth _token = window . auth _token ;
logged _in _users _updated = true ;
}
}
// no matching array elements, add one
if ( ! logged _in _users _updated ) {
let userobj = window . user ;
userobj . auth _token = window . auth _token ;
window . logged _in _users . push ( userobj ) ;
}
// update local storage
localStorage . setItem ( 'logged_in_users' , JSON . stringify ( window . logged _in _users ) ) ;
}
window . desktop _path = '/' + window . user . username + '/Desktop' ;
window . trash _path = '/' + window . user . username + '/Trash' ;
window . appdata _path = '/' + window . user . username + '/AppData' ;
window . docs _path = '/' + window . user . username + '/Documents' ;
window . pictures _path = '/' + window . user . username + '/Pictures' ;
window . videos _path = '/' + window . user . username + '/Videos' ;
window . desktop _path = '/' + window . user . username + '/Desktop' ;
window . home _path = '/' + window . user . username ;
if ( window . user !== null && ! window . user . is _temp ) {
$ ( '.user-options-login-btn, .user-options-create-account-btn' ) . hide ( ) ;
$ ( '.user-options-menu-btn' ) . show ( ) ;
}
}
2024-03-09 11:06:14 +08:00
window . mutate _user _preferences = function ( user _preferences _delta ) {
for ( const [ key , value ] of Object . entries ( user _preferences _delta ) ) {
// Don't wait for set to be done for better efficiency
2024-03-22 20:46:55 +08:00
puter . kv . set ( ` user_preferences. ${ key } ` , value ) ;
2024-03-09 11:06:14 +08:00
}
// There may be syncing issues across multiple devices
update _user _preferences ( { ... window . user _preferences , ... user _preferences _delta } ) ;
}
window . update _user _preferences = function ( user _preferences ) {
window . user _preferences = user _preferences ;
localStorage . setItem ( 'user_preferences' , JSON . stringify ( user _preferences ) ) ;
2024-04-08 21:04:44 +08:00
const language = user _preferences . language ? ? 'en' ;
window . locale = language ;
// Broadcast locale change to apps
const broadcastService = globalThis . services . get ( 'broadcast' ) ;
broadcastService . sendBroadcast ( 'localeChanged' , {
language : language ,
} , { sendToNewAppInstances : true } ) ;
2024-03-09 11:06:14 +08:00
}
2024-03-03 10:39:14 +08:00
window . sendWindowWillCloseMsg = function ( iframe _element ) {
return new Promise ( function ( resolve ) {
const msg _id = uuidv4 ( ) ;
iframe _element . contentWindow . postMessage ( {
msg : "windowWillClose" ,
msg _id : msg _id
} , '*' ) ;
//register callback
appCallbackFunctions [ msg _id ] = resolve ;
} )
}
window . logout = ( ) => {
2024-04-02 16:19:12 +08:00
console . log ( 'DISP LOGOUT EVENT' ) ;
$ ( document ) . trigger ( 'logout' ) ;
// document.dispatchEvent(new Event("logout", { bubbles: true}));
2024-03-03 10:39:14 +08:00
}
/ * *
* Checks if the current document is in fullscreen mode .
*
* @ function is _fullscreen
* @ memberof window
* @ returns { boolean } Returns true if the document is in fullscreen mode , otherwise false .
*
* @ example
* // Checks if the document is currently in fullscreen mode
* const inFullscreen = window . is _fullscreen ( ) ;
*
* @ description
* This function checks various browser - specific properties to determine if the document
* is currently being displayed in fullscreen mode . It covers standard as well as
* some vendor - prefixed properties to ensure compatibility across different browsers .
* /
window . is _fullscreen = ( ) => {
return ( document . fullscreenElement && document . fullscreenElement !== null ) ||
( document . webkitIsFullScreen && document . webkitIsFullScreen !== null ) ||
( document . webkitFullscreenElement && document . webkitFullscreenElement !== null ) ||
( document . mozFullScreenElement && document . mozFullScreenElement !== null ) ||
( document . msFullscreenElement && document . msFullscreenElement !== null ) ;
}
window . get _apps = async ( app _names , callback ) => {
if ( Array . isArray ( app _names ) )
app _names = app _names . join ( '|' ) ;
// 'explorer' is a special app, no metadata should be returned
if ( app _names === 'explorer' )
return [ ] ;
let res = await $ . ajax ( {
url : api _origin + "/apps/" + app _names ,
type : 'GET' ,
async : true ,
contentType : "application/json" ,
headers : {
"Authorization" : "Bearer " + auth _token
} ,
success : function ( res ) {
}
} ) ;
if ( res . length === 1 )
res = res [ 0 ] ;
if ( callback && typeof callback === 'function' )
callback ( res ) ;
else
return res ;
}
/ * *
* Sends an "itemChanged" event to all watching applications associated with a specific item .
*
* @ function sendItemChangeEventToWatchingApps
* @ memberof window
* @ param { string } item _uid - Unique identifier of the item that experienced the change .
* @ param { Object } event _data - Additional data about the event to be passed to the watching applications .
*
* @ description
* This function sends an "itemChanged" message to all applications that are currently watching
* the specified item . If an application ' s iframe is not found or no longer valid ,
* it is removed from the list of watchers .
*
* The function expects that ` window.watchItems ` contains a mapping of item UIDs to arrays of app instance IDs .
*
* @ example
* // Example usage to send a change event to watching applications of an item with UID "item123".
* window . sendItemChangeEventToWatchingApps ( 'item123' , { property : 'value' } ) ;
* /
window . sendItemChangeEventToWatchingApps = function ( item _uid , event _data ) {
if ( window . watchItems [ item _uid ] ) {
window . watchItems [ item _uid ] . forEach ( app _instance _id => {
const iframe = $ ( ` .window[data-element_uuid=" ${ app _instance _id } "] ` ) . find ( '.window-app-iframe' )
if ( iframe && iframe . length > 0 ) {
iframe . get ( 0 ) ? . contentWindow
. postMessage ( {
msg : 'itemChanged' ,
data : event _data ,
} , '*' ) ;
} else {
window . watchItems [ item _uid ] . splice ( window . watchItems [ item _uid ] . indexOf ( app _instance _id ) , 1 ) ;
}
} ) ;
}
}
/ * *
* Assigns an icon to a filesystem entry based on its properties such as name , type ,
* and whether it ' s a directory , app , trashed , or specific file type .
*
* @ function item _icon
* @ global
* @ async
* @ param { Object } fsentry - A filesystem entry object . It may contain various properties
* like name , type , path , associated _app , thumbnail , is _dir , and metadata , depending on
* the type of filesystem entry .
* /
window . item _icon = async ( fsentry ) => {
// --------------------------------------------------
// If this file is Trashed then set the name to the original name of the file before it was trashed
// --------------------------------------------------
if ( fsentry . path ? . startsWith ( trash _path + '/' ) ) {
if ( fsentry . metadata ) {
try {
let metadata = JSON . parse ( fsentry . metadata ) ;
fsentry . name = ( metadata && metadata . original _name ) ? metadata . original _name : fsentry . name
}
catch ( e ) {
}
}
}
// --------------------------------------------------
// thumbnail
// --------------------------------------------------
if ( fsentry . thumbnail ) {
return { image : fsentry . thumbnail , type : 'thumb' } ;
}
// --------------------------------------------------
// app icon
// --------------------------------------------------
else if ( fsentry . associated _app && fsentry . associated _app ? . name ) {
if ( fsentry . associated _app . icon )
return { image : fsentry . associated _app . icon , type : 'icon' } ;
else
return { image : window . icons [ 'app.svg' ] , type : 'icon' } ;
}
// --------------------------------------------------
// Trash
// --------------------------------------------------
else if ( fsentry . shortcut _to _path && fsentry . shortcut _to _path === trash _path ) {
// get trash image, this is needed to get the correct empty vs full trash icon
let trash _img = $ ( ` .item[data-path=" ${ html _encode ( trash _path ) } " i] .item-icon-icon ` ) . attr ( 'src' )
// if trash_img is undefined that's probably because trash wasn't added anywhere, do a direct lookup to see if trash is empty or no
if ( ! trash _img ) {
let trashstat = await puter . fs . stat ( trash _path ) ;
if ( trashstat . is _empty !== undefined && trashstat . is _empty === true )
trash _img = window . icons [ 'trash.svg' ] ;
else
trash _img = window . icons [ 'trash-full.svg' ] ;
}
return { image : trash _img , type : 'icon' } ;
}
// --------------------------------------------------
// Directories
// --------------------------------------------------
else if ( fsentry . is _dir ) {
// System Directories
if ( fsentry . path === docs _path )
return { image : window . icons [ 'folder-documents.svg' ] , type : 'icon' } ;
else if ( fsentry . path === pictures _path )
return { image : window . icons [ 'folder-pictures.svg' ] , type : 'icon' } ;
else if ( fsentry . path === home _path )
return { image : window . icons [ 'folder-home.svg' ] , type : 'icon' } ;
else if ( fsentry . path === videos _path )
return { image : window . icons [ 'folder-videos.svg' ] , type : 'icon' } ;
else if ( fsentry . path === desktop _path )
return { image : window . icons [ 'folder-desktop.svg' ] , type : 'icon' } ;
// regular directories
else
return { image : window . icons [ 'folder.svg' ] , type : 'icon' } ;
}
// --------------------------------------------------
// Match icon by file extension
// --------------------------------------------------
// *.doc
else if ( fsentry . name . toLowerCase ( ) . endsWith ( '.doc' ) ) {
return { image : window . icons [ 'file-doc.svg' ] , type : 'icon' } ;
}
// *.docx
else if ( fsentry . name . toLowerCase ( ) . endsWith ( '.docx' ) ) {
return { image : window . icons [ 'file-docx.svg' ] , type : 'icon' } ;
}
// *.exe
else if ( fsentry . name . toLowerCase ( ) . endsWith ( '.exe' ) ) {
return { image : window . icons [ 'file-exe.svg' ] , type : 'icon' } ;
}
// *.gz
else if ( fsentry . name . toLowerCase ( ) . endsWith ( '.gz' ) ) {
return { image : window . icons [ 'file-gzip.svg' ] , type : 'icon' } ;
}
// *.jar
else if ( fsentry . name . toLowerCase ( ) . endsWith ( '.jar' ) ) {
return { image : window . icons [ 'file-jar.svg' ] , type : 'icon' } ;
}
// *.java
else if ( fsentry . name . toLowerCase ( ) . endsWith ( '.java' ) ) {
return { image : window . icons [ 'file-java.svg' ] , type : 'icon' } ;
}
// *.jsp
else if ( fsentry . name . toLowerCase ( ) . endsWith ( '.jsp' ) ) {
return { image : window . icons [ 'file-jsp.svg' ] , type : 'icon' } ;
}
// *.log
else if ( fsentry . name . toLowerCase ( ) . endsWith ( '.log' ) ) {
return { image : window . icons [ 'file-log.svg' ] , type : 'icon' } ;
}
// *.mp3
else if ( fsentry . name . toLowerCase ( ) . endsWith ( '.mp3' ) ) {
return { image : window . icons [ 'file-mp3.svg' ] , type : 'icon' } ;
}
// *.rb
else if ( fsentry . name . toLowerCase ( ) . endsWith ( '.rb' ) ) {
return { image : window . icons [ 'file-ruby.svg' ] , type : 'icon' } ;
}
// *.rss
else if ( fsentry . name . toLowerCase ( ) . endsWith ( '.rss' ) ) {
return { image : window . icons [ 'file-rss.svg' ] , type : 'icon' } ;
}
// *.rtf
else if ( fsentry . name . toLowerCase ( ) . endsWith ( '.rtf' ) ) {
return { image : window . icons [ 'file-rtf.svg' ] , type : 'icon' } ;
}
// *.sketch
else if ( fsentry . name . toLowerCase ( ) . endsWith ( '.sketch' ) ) {
return { image : window . icons [ 'file-sketch.svg' ] , type : 'icon' } ;
}
// *.sql
else if ( fsentry . name . toLowerCase ( ) . endsWith ( '.sql' ) ) {
return { image : window . icons [ 'file-sql.svg' ] , type : 'icon' } ;
}
// *.tif
else if ( fsentry . name . toLowerCase ( ) . endsWith ( '.tif' ) ) {
return { image : window . icons [ 'file-tif.svg' ] , type : 'icon' } ;
}
// *.tiff
else if ( fsentry . name . toLowerCase ( ) . endsWith ( '.tiff' ) ) {
return { image : window . icons [ 'file-tiff.svg' ] , type : 'icon' } ;
}
// *.wav
else if ( fsentry . name . toLowerCase ( ) . endsWith ( '.wav' ) ) {
return { image : window . icons [ 'file-wav.svg' ] , type : 'icon' } ;
}
// *.cpp
else if ( fsentry . name . toLowerCase ( ) . endsWith ( '.cpp' ) ) {
return { image : window . icons [ 'file-cpp.svg' ] , type : 'icon' } ;
}
// *.pptx
else if ( fsentry . name . toLowerCase ( ) . endsWith ( '.pptx' ) ) {
return { image : window . icons [ 'file-pptx.svg' ] , type : 'icon' } ;
}
// *.psd
else if ( fsentry . name . toLowerCase ( ) . endsWith ( '.psd' ) ) {
return { image : window . icons [ 'file-psd.svg' ] , type : 'icon' } ;
}
2024-03-23 02:49:57 +08:00
// *.py
else if ( fsentry . name . toLowerCase ( ) . endsWith ( '.py' ) ) {
return { image : window . icons [ 'file-py.svg' ] , type : 'icon' } ;
}
2024-03-03 10:39:14 +08:00
// *.xlsx
else if ( fsentry . name . toLowerCase ( ) . endsWith ( '.xlsx' ) ) {
return { image : window . icons [ 'file-xlsx.svg' ] , type : 'icon' } ;
}
// --------------------------------------------------
// Determine icon by set or derived mime type
// --------------------------------------------------
else if ( fsentry . type ) {
return { image : content _type _to _icon ( fsentry . type ) , type : 'icon' } ;
}
else {
return { image : content _type _to _icon ( mime . getType ( fsentry . name ) ) , type : 'icon' } ;
}
}
/ * *
* Asynchronously checks if a save account notice should be shown to the user , and if needed , displays the notice .
*
* This function first retrieves a key value pair from the cloud key - value storage to determine if the notice has been shown before .
* If the notice hasn ' t been shown and the user is using a temporary session , the notice is then displayed . After the notice is shown ,
* the function updates the key - value storage indicating that the notice has been shown . The user can choose to save the session ,
* remind later or log in to an existing account .
*
* @ param { string } [ message ] - The custom message to be displayed in the notice . If not provided , a default message will be used .
* @ global
* @ function window . show _save _account _notice _if _needed
* /
window . show _save _account _notice _if _needed = function ( message ) {
getItem ( {
key : "save_account_notice_shown" ,
success : async function ( value ) {
if ( ! value && window . user ? . is _temp ) {
setItem ( { key : "save_account_notice_shown" , value : true } ) ;
// Show the notice
setTimeout ( async ( ) => {
const alert _resp = await UIAlert ( {
message : message ? ? ` <strong>Congrats on storing data!</strong><p>Don't forget to save your session! You are in a temporary session. Save session to avoid accidentally losing your work.</p> ` ,
body _icon : window . icons [ 'reminder.svg' ] ,
buttons : [
{
2024-03-19 04:07:29 +08:00
label : i18n ( 'save_session' ) ,
2024-03-03 10:39:14 +08:00
value : 'save-session' ,
type : 'primary' ,
} ,
// {
// label: 'Log into an existing account',
// value: 'login',
// },
{
label : ` I'll do it later ` ,
value : 'remind-later' ,
} ,
] ,
window _options : {
backdrop : true ,
close _on _backdrop _click : false ,
}
} )
if ( alert _resp === 'remind-later' ) {
}
if ( alert _resp === 'save-session' ) {
let saved = await UIWindowSaveAccount ( {
send _confirmation _code : false ,
} ) ;
} else if ( alert _resp === 'login' ) {
let login _result = await UIWindowLogin ( {
show _signup _button : false ,
reload _on _success : true ,
send _confirmation _code : false ,
window _options : {
show _in _taskbar : false ,
backdrop : true ,
close _on _backdrop _click : false ,
}
} ) ;
if ( ! login _result )
$ ( '.toolbar' ) . prepend ( ht ) ;
}
} , desktop _loading _fade _delay + 1000 ) ;
}
}
} )
}
window . onpopstate = ( event ) => {
if ( event . state !== null && event . state . window _id !== null ) {
$ ( ` .window[data-id=" ${ event . state . window _id } "] ` ) . focusWindow ( ) ;
}
}
window . sort _items = ( item _container , sort _by , sort _order ) => {
if ( sort _order !== 'asc' && sort _order !== 'desc' )
sort _order = 'asc' ;
$ ( item _container ) . find ( ` .item[data-sortable="true"] ` ) . detach ( ) . sort ( function ( a , b ) {
// Name
if ( ! sort _by || sort _by === 'name' ) {
if ( a . dataset . name . toLowerCase ( ) < b . dataset . name . toLowerCase ( ) ) { return ( sort _order === 'asc' ? - 1 : 1 ) ; }
if ( a . dataset . name . toLowerCase ( ) > b . dataset . name . toLowerCase ( ) ) { return ( sort _order === 'asc' ? 1 : - 1 ) ; }
return 0 ;
}
// Size
else if ( sort _by === 'size' ) {
if ( parseInt ( a . dataset . size ) < parseInt ( b . dataset . size ) ) { return ( sort _order === 'asc' ? - 1 : 1 ) ; }
if ( parseInt ( a . dataset . size ) > parseInt ( b . dataset . size ) ) { return ( sort _order === 'asc' ? 1 : - 1 ) ; }
return 0 ;
}
// Modified
else if ( sort _by === 'modified' ) {
if ( parseInt ( a . dataset . modified ) < parseInt ( b . dataset . modified ) ) { return ( sort _order === 'asc' ? - 1 : 1 ) ; }
if ( parseInt ( a . dataset . modified ) > parseInt ( b . dataset . modified ) ) { return ( sort _order === 'asc' ? 1 : - 1 ) ; }
return 0 ;
}
// Type
else if ( sort _by === 'type' ) {
if ( path . extname ( a . dataset . name . toLowerCase ( ) ) < path . extname ( b . dataset . name . toLowerCase ( ) ) ) { return ( sort _order === 'asc' ? - 1 : 1 ) ; }
if ( path . extname ( a . dataset . name . toLowerCase ( ) ) > path . extname ( b . dataset . name . toLowerCase ( ) ) ) { return ( sort _order === 'asc' ? 1 : - 1 ) ; }
return 0 ;
}
} ) . appendTo ( item _container ) ;
}
2024-03-09 11:06:14 +08:00
window . show _or _hide _files = ( item _containers ) => {
const show _hidden _files = window . user _preferences . show _hidden _files ;
const class _to _add = show _hidden _files ? 'item-revealed' : 'item-hidden' ;
const class _to _remove = show _hidden _files ? 'item-hidden' : 'item-revealed' ;
$ ( item _containers )
. find ( '.item' )
. filter ( ( _ , item ) => item . dataset . name . startsWith ( '.' ) )
. removeClass ( class _to _remove ) . addClass ( class _to _add ) ;
}
2024-03-03 10:39:14 +08:00
window . create _folder = async ( basedir , appendto _element ) => {
let dirname = basedir ;
let folder _name = 'New Folder' ;
let newfolder _op _id = operation _id ++ ;
operation _cancelled [ newfolder _op _id ] = false ;
let newfolder _progress _window _init _ts = Date . now ( ) ;
let progwin ;
// only show progress window if it takes longer than 500ms to create folder
let progwin _timeout = setTimeout ( async ( ) => {
progwin = await UIWindowNewFolderProgress ( { operation _id : newfolder _op _id } ) ;
} , 500 ) ;
// create folder
try {
await puter . fs . mkdir ( {
path : dirname + '/' + folder _name ,
rename : true ,
overwrite : false ,
success : function ( data ) {
const el _created _dir = $ ( appendto _element ) . find ( '.item[data-path="' + html _encode ( dirname ) + '/' + html _encode ( data . name ) + '"]' ) ;
2024-03-10 22:18:17 +08:00
if ( el _created _dir . length > 0 ) {
2024-03-03 10:39:14 +08:00
activate _item _name _editor ( el _created _dir ) ;
2024-03-10 22:18:17 +08:00
// Add action to actions_history for undo ability
actions _history . push ( {
operation : 'create_folder' ,
data : el _created _dir
} ) ;
}
2024-03-03 10:39:14 +08:00
clearTimeout ( progwin _timeout ) ;
// done
let newfolder _duration = ( Date . now ( ) - newfolder _progress _window _init _ts ) ;
if ( progwin && newfolder _duration >= copy _progress _hide _delay ) {
$ ( progwin ) . close ( ) ;
} else if ( progwin ) {
setTimeout ( ( ) => {
setTimeout ( ( ) => {
$ ( progwin ) . close ( ) ;
} , Math . abs ( copy _progress _hide _delay - newfolder _duration ) ) ;
} )
}
}
} ) ;
} catch ( err ) {
clearTimeout ( progwin _timeout ) ;
}
}
window . create _file = async ( options ) => {
// args
let dirname = options . dirname ;
let appendto _element = options . append _to _element ;
let filename = options . name ;
let content = options . content ? [ options . content ] : [ ] ;
// create file
try {
puter . fs . upload ( new File ( content , filename ) , dirname ,
{
success : async function ( data ) {
const created _file = $ ( appendto _element ) . find ( '.item[data-path="' + html _encode ( dirname ) + '/' + html _encode ( data . name ) + '"]' ) ;
if ( created _file . length > 0 ) {
activate _item _name _editor ( created _file ) ;
2024-03-10 22:18:17 +08:00
// Add action to actions_history for undo ability
actions _history . push ( {
operation : 'create_file' ,
data : created _file
} ) ;
2024-03-03 10:39:14 +08:00
}
}
} ) ;
} catch ( err ) {
console . log ( err ) ;
}
}
window . create _shortcut = async ( filename , is _dir , basedir , appendto _element , shortcut _to , shortcut _to _path ) => {
let dirname = basedir ;
const extname = path . extname ( filename ) ;
const basename = path . basename ( filename , extname ) + ' - Shortcut' ;
filename = basename + extname ;
// create file shortcut
try {
await puter . fs . upload ( new File ( [ ] , filename ) , dirname , {
overwrite : false ,
shortcutTo : shortcut _to _path ? ? shortcut _to ,
dedupeName : true ,
} ) ;
} catch ( err ) {
console . log ( err )
}
}
window . copy _clipboard _items = async function ( dest _path , dest _container _element ) {
let copy _op _id = operation _id ++ ;
operation _cancelled [ copy _op _id ] = false ;
// unselect previously selected items in the target container
$ ( dest _container _element ) . children ( '.item-selected' ) . removeClass ( 'item-selected' ) ;
update _explorer _footer _selected _items _count ( $ ( dest _container _element ) . closest ( '.window' ) ) ;
let overwrite _all = false ;
( async ( ) => {
let copy _progress _window _init _ts = Date . now ( ) ;
2024-03-10 13:26:49 +08:00
// only show progress window if it takes longer than 2s to copy
let progwin ;
let progwin _timeout = setTimeout ( async ( ) => {
progwin = await UIWindowCopyProgress ( { operation _id : copy _op _id } ) ;
} , 2000 ) ;
2024-03-13 02:18:15 +08:00
const copied _item _paths = [ ]
2024-03-03 10:39:14 +08:00
for ( let i = 0 ; i < clipboard . length ; i ++ ) {
let copy _path = clipboard [ i ] . path ;
let item _with _same _name _already _exists = true ;
let overwrite = overwrite _all ;
$ ( progwin ) . find ( '.copy-from' ) . html ( copy _path ) ;
do {
if ( overwrite )
item _with _same _name _already _exists = false ;
2024-03-13 02:18:15 +08:00
2024-03-03 10:39:14 +08:00
// cancelled?
if ( operation _cancelled [ copy _op _id ] )
return ;
// perform copy
try {
2024-03-13 02:18:15 +08:00
let resp = await puter . fs . copy ( {
2024-03-03 10:39:14 +08:00
source : copy _path ,
destination : dest _path ,
overwrite : overwrite || overwrite _all ,
// if user is copying an item to where its source is, change the name so there is no conflict
dedupeName : dest _path === path . dirname ( copy _path ) ,
} ) ;
2024-03-13 02:18:15 +08:00
2024-04-05 11:41:39 +08:00
// remove overwritten item from the DOM
if ( resp [ 0 ] . overwritten ? . id ) {
$ ( ` .item[data-uid= ${ resp [ 0 ] . overwritten . id } ] ` ) . removeItems ( ) ;
}
2024-03-13 02:18:15 +08:00
// copy new path for undo copy
2024-04-05 11:41:39 +08:00
copied _item _paths . push ( resp [ 0 ] . copied . path ) ;
2024-03-13 02:18:15 +08:00
2024-03-03 10:39:14 +08:00
// skips next loop iteration
break ;
} catch ( err ) {
if ( err . code === 'item_with_same_name_exists' ) {
const alert _resp = await UIAlert ( {
message : ` <strong> ${ html _encode ( err . entry _name ) } </strong> already exists. ` ,
buttons : [
2024-03-19 04:16:33 +08:00
{ label : i18n ( 'replace' ) , type : 'primary' , value : 'replace' } ,
... ( clipboard . length > 1 ) ? [ { label : i18n ( 'replace_all' ) , value : 'replace_all' } ] : [ ] ,
... ( clipboard . length > 1 ) ? [ { label : i18n ( 'skip' ) , value : 'skip' } ] : [ { label : i18n ( 'cancel' ) , value : 'cancel' } ] ,
2024-03-03 10:39:14 +08:00
]
} )
2024-03-19 04:16:33 +08:00
if ( alert _resp === 'replace' ) {
2024-03-03 10:39:14 +08:00
overwrite = true ;
2024-03-19 04:16:33 +08:00
} else if ( alert _resp === 'replace_all' ) {
2024-03-03 10:39:14 +08:00
overwrite = true ;
overwrite _all = true ;
2024-03-19 04:16:33 +08:00
} else if ( alert _resp === 'skip' || alert _resp === 'cancel' ) {
2024-03-03 10:39:14 +08:00
item _with _same _name _already _exists = false ;
}
}
else {
if ( err . message ) {
UIAlert ( err . message )
}
item _with _same _name _already _exists = false ;
}
}
} while ( item _with _same _name _already _exists )
}
// done
2024-03-13 02:18:15 +08:00
// Add action to actions_history for undo ability
actions _history . push ( {
operation : 'copy' ,
data : copied _item _paths
} ) ;
2024-03-10 13:26:49 +08:00
clearTimeout ( progwin _timeout ) ;
2024-03-03 10:39:14 +08:00
let copy _duration = ( Date . now ( ) - copy _progress _window _init _ts ) ;
2024-03-10 13:26:49 +08:00
if ( progwin && copy _duration >= copy _progress _hide _delay ) {
2024-03-03 10:39:14 +08:00
$ ( progwin ) . close ( ) ;
2024-03-10 13:26:49 +08:00
} else if ( progwin ) {
2024-03-03 10:39:14 +08:00
setTimeout ( ( ) => {
setTimeout ( ( ) => {
$ ( progwin ) . close ( ) ;
} , Math . abs ( copy _progress _hide _delay - copy _duration ) ) ;
} )
}
} ) ( ) ;
}
/ * *
* Copies the given items to the destination path .
*
* @ param { HTMLElement [ ] } el _items - HTML elements representing the items to copy
* @ param { string } dest _path - Destination path to copy items to
* /
window . copy _items = function ( el _items , dest _path ) {
let copy _op _id = operation _id ++ ;
let overwrite _all = false ;
( async ( ) => {
let copy _progress _window _init _ts = Date . now ( ) ;
2024-03-10 13:26:49 +08:00
// only show progress window if it takes longer than 2s to copy
let progwin ;
let progwin _timeout = setTimeout ( async ( ) => {
progwin = await UIWindowCopyProgress ( { operation _id : copy _op _id } ) ;
} , 2000 ) ;
2024-03-13 02:18:15 +08:00
const copied _item _paths = [ ]
2024-03-03 10:39:14 +08:00
for ( let i = 0 ; i < el _items . length ; i ++ ) {
let copy _path = $ ( el _items [ i ] ) . attr ( 'data-path' ) ;
let item _with _same _name _already _exists = true ;
let overwrite = overwrite _all ;
$ ( progwin ) . find ( '.copy-from' ) . html ( copy _path ) ;
do {
if ( overwrite )
item _with _same _name _already _exists = false ;
// cancelled?
if ( operation _cancelled [ copy _op _id ] )
return ;
try {
2024-03-13 02:18:15 +08:00
let resp = await puter . fs . copy ( {
2024-03-03 10:39:14 +08:00
source : copy _path ,
destination : dest _path ,
overwrite : overwrite || overwrite _all ,
// if user is copying an item to where the source is, automatically change the name so there is no conflict
dedupeName : dest _path === path . dirname ( copy _path ) ,
} )
2024-04-05 11:41:39 +08:00
// remove overwritten item from the DOM
if ( resp [ 0 ] . overwritten ? . id ) {
$ ( ` .item[data-uid= ${ resp . overwritten . id } ] ` ) . removeItems ( ) ;
}
2024-03-13 02:18:15 +08:00
// copy new path for undo copy
2024-04-05 11:41:39 +08:00
copied _item _paths . push ( resp [ 0 ] . copied . path ) ;
2024-03-13 02:18:15 +08:00
2024-03-03 10:39:14 +08:00
// skips next loop iteration
item _with _same _name _already _exists = false ;
} catch ( err ) {
if ( err . code === 'item_with_same_name_exists' ) {
const alert _resp = await UIAlert ( {
message : ` <strong> ${ html _encode ( err . entry _name ) } </strong> already exists. ` ,
buttons : [
2024-03-19 04:16:33 +08:00
{ label : i18n ( 'replace' ) , type : 'primary' , value : 'replace' } ,
... ( el _items . length > 1 ) ? [ { label : i18n ( 'replace_all' ) , value : 'replace_all' } ] : [ ] ,
... ( el _items . length > 1 ) ? [ { label : i18n ( 'skip' ) , value : 'skip' } ] : [ { label : i18n ( 'cancel' ) , value : 'cancel' } ] ,
2024-03-03 10:39:14 +08:00
]
} )
2024-03-19 04:16:33 +08:00
if ( alert _resp === 'replace' ) {
2024-03-03 10:39:14 +08:00
overwrite = true ;
2024-03-19 04:16:33 +08:00
} else if ( alert _resp === 'replace_all' ) {
2024-03-03 10:39:14 +08:00
overwrite = true ;
overwrite _all = true ;
2024-03-19 04:16:33 +08:00
} else if ( alert _resp === 'skip' || alert _resp === 'cancel' ) {
2024-03-03 10:39:14 +08:00
item _with _same _name _already _exists = false ;
}
}
else {
if ( err . message ) {
UIAlert ( err . message )
}
else if ( err ) {
UIAlert ( err )
}
item _with _same _name _already _exists = false ;
}
}
} while ( item _with _same _name _already _exists )
}
// done
2024-03-13 02:18:15 +08:00
// Add action to actions_history for undo ability
actions _history . push ( {
operation : 'copy' ,
data : copied _item _paths
} ) ;
2024-03-10 13:26:49 +08:00
clearTimeout ( progwin _timeout ) ;
2024-03-03 10:39:14 +08:00
let copy _duration = ( Date . now ( ) - copy _progress _window _init _ts ) ;
2024-03-10 13:26:49 +08:00
if ( progwin && copy _duration >= copy _progress _hide _delay ) {
2024-03-03 10:39:14 +08:00
$ ( progwin ) . close ( ) ;
2024-03-10 13:26:49 +08:00
} else if ( progwin ) {
2024-03-03 10:39:14 +08:00
setTimeout ( ( ) => {
setTimeout ( ( ) => {
$ ( progwin ) . close ( ) ;
} , Math . abs ( copy _progress _hide _delay - copy _duration ) ) ;
} )
}
} ) ( )
}
/ * *
* Deletes the given item .
*
* @ param { HTMLElement } el _item - HTML element representing the item to delete
* @ param { boolean } [ descendants _only = false ] - If true , only deletes descendant items under the given item
* @ returns { Promise < void > }
* /
window . delete _item = async function ( el _item , descendants _only = false ) {
if ( $ ( el _item ) . attr ( 'data-immutable' ) === '1' )
return ;
// hide all UIItems with matching uids
$ ( ` .item[data-uid=' ${ $ ( el _item ) . attr ( 'data-uid' ) } '] ` ) . fadeOut ( 150 , function ( ) {
// close all windows with matching uids
$ ( '.window-' + $ ( el _item ) . attr ( 'data-uid' ) ) . close ( ) ;
// close all windows that belong to a descendant of this item
// todo this has to be case-insensitive but the `i` selector doesn't work on ^=
$ ( ` .window[data-path^=" ${ $ ( el _item ) . attr ( 'data-path' ) } /"] ` ) . close ( ) ;
} ) ;
try {
await puter . fs . delete ( {
paths : $ ( el _item ) . attr ( 'data-path' ) ,
descendantsOnly : descendants _only ,
recursive : true ,
} ) ;
// fade out item
$ ( ` .item[data-uid=' ${ $ ( el _item ) . attr ( 'data-uid' ) } '] ` ) . fadeOut ( 150 , function ( ) {
// find all parent windows that contain this item
let parent _windows = $ ( ` .item[data-uid=' ${ $ ( el _item ) . attr ( 'data-uid' ) } '] ` ) . closest ( '.window' ) ;
// remove item from DOM
$ ( ` .item[data-uid=' ${ $ ( el _item ) . attr ( 'data-uid' ) } '] ` ) . removeItems ( ) ;
// update parent windows' item counts
$ ( parent _windows ) . each ( function ( index ) {
update _explorer _footer _item _count ( this ) ;
update _explorer _footer _selected _items _count ( this ) ;
} ) ;
// update all shortcuts to this item
$ ( ` .item[data-shortcut_to_path=" ${ html _encode ( $ ( el _item ) . attr ( 'data-path' ) ) } " i] ` ) . attr ( ` data-shortcut_to_path ` , '' ) ;
} ) ;
} catch ( err ) {
UIAlert ( err . responseText ) ;
}
}
window . move _clipboard _items = function ( el _target _container , target _path ) {
let dest _path = target _path === undefined ? $ ( el _target _container ) . attr ( 'data-path' ) : target _path ;
let el _items = [ ] ;
if ( clipboard . length > 0 ) {
for ( let i = 0 ; i < clipboard . length ; i ++ ) {
el _items . push ( $ ( ` .item[data-path=" ${ html _encode ( clipboard [ i ] ) } " i] ` ) ) ;
}
if ( el _items . length > 0 )
move _items ( el _items , dest _path ) ;
}
clipboard = [ ] ;
}
/ * *
* Initiates a download for multiple files provided as an array of paths .
*
* This function triggers the download of files from given paths . It constructs the
* download URLs using an API base URL and the given paths , along with an authentication token .
* Each file is then fetched and prompted to the user for download using the ` saveAs ` function .
*
* Global dependencies :
* - ` api_origin ` : The base URL for the download API endpoint .
* - ` auth_token ` : The authentication token required for the download API .
* - ` saveAs ` : Function to save the fetched blob as a file .
* - ` path.basename() ` : Function to extract the filename from the provided path .
*
* @ global
* @ function trigger _download
* @ param { string [ ] } paths - An array of file paths that are to be downloaded .
*
* @ example
* let filePaths = [ '/path/to/file1.txt' , '/path/to/file2.png' ] ;
* window . trigger _download ( filePaths ) ;
* /
window . trigger _download = ( paths ) => {
let urls = [ ] ;
for ( let index = 0 ; index < paths . length ; index ++ ) {
urls . push ( {
download : api _origin + "/down?path=" + paths [ index ] + "&auth_token=" + auth _token ,
filename : path . basename ( paths [ index ] ) ,
} ) ;
}
urls . forEach ( function ( e ) {
fetch ( e . download )
. then ( res => res . blob ( ) )
. then ( blob => {
saveAs ( blob , e . filename ) ;
} ) ;
} ) ;
}
/ * *
*
* @ param { * } options
* /
window . launch _app = async ( options ) => {
2024-04-05 23:55:49 +08:00
const uuid = options . uuid ? ? uuidv4 ( ) ;
2024-03-03 10:39:14 +08:00
let icon , title , file _signature ;
const window _options = options . window _options ? ? { } ;
2024-04-09 22:08:36 +08:00
if ( options . parent _instance _id ) {
window _options . parent _instance _id = options . parent _instance _id ;
}
2024-03-03 10:39:14 +08:00
// try to get 3rd-party app info
let app _info = options . app _obj ? ? await get _apps ( options . name ) ;
//-----------------------------------
// icon
//-----------------------------------
if ( app _info . icon )
icon = app _info . icon ;
else if ( app _info . icon )
icon = window . icons [ 'app.svg' ] ;
else if ( options . name === 'explorer' )
icon = window . icons [ 'folder.svg' ] ;
else
icon = window . icons [ 'app-icon-' + options . name + '.svg' ]
//-----------------------------------
// title
//-----------------------------------
if ( app _info . title )
title = app _info . title ;
else if ( options . window _title )
title = options . window _title ;
else if ( options . name )
title = options . name ;
//-----------------------------------
// maximize on start
//-----------------------------------
if ( app _info . maximize _on _start && app _info . maximize _on _start === 1 )
options . maximized = 1 ;
//-----------------------------------
// if opened a file, sign it
//-----------------------------------
if ( options . file _signature )
file _signature = options . file _signature ;
else if ( options . file _uid ) {
file _signature = await puter . fs . sign ( app _info . uuid , { uid : options . file _uid , action : 'write' } ) ;
// add token to options
options . token = file _signature . token ;
// add file_signature to options
file _signature = file _signature . items ;
}
2024-04-15 08:39:26 +08:00
// -----------------------------------
// Create entry to track the "portal"
// (portals are processese in Puter's GUI)
// -----------------------------------
let el _win ;
2024-04-15 11:44:52 +08:00
let process ;
2024-04-15 08:39:26 +08:00
2024-03-03 10:39:14 +08:00
//------------------------------------
// Explorer
//------------------------------------
if ( options . name === 'explorer' ) {
2024-04-15 11:44:52 +08:00
process = new PseudoProcess ( {
2024-04-15 11:23:33 +08:00
uuid ,
name : 'explorer' ,
parent : options . parent _instance _id ,
meta : {
launch _options : options ,
app _info : app _info ,
}
} ) ;
const svc _process = globalThis . services . get ( 'process' ) ;
svc _process . register ( process ) ;
2024-03-03 10:39:14 +08:00
if ( options . path === window . home _path ) {
title = 'Home' ;
icon = window . icons [ 'folder-home.svg' ] ;
}
else if ( options . path === window . trash _path ) {
title = 'Trash' ;
}
else if ( ! options . path )
title = root _dirname ;
else
title = path . dirname ( options . path ) ;
// open window
2024-04-15 08:39:26 +08:00
el _win = UIWindow ( {
2024-03-03 10:39:14 +08:00
element _uuid : uuid ,
icon : icon ,
path : options . path ? ? window . home _path ,
title : title ,
uid : null ,
is _dir : true ,
app : 'explorer' ,
... window _options ,
is _maximized : options . maximized ,
} ) ;
}
//------------------------------------
// All other apps
//------------------------------------
else {
2024-04-15 11:44:52 +08:00
process = new PortalProcess ( {
2024-04-15 11:23:33 +08:00
uuid ,
name : app _info . name ,
parent : options . parent _instance _id ,
meta : {
launch _options : options ,
app _info : app _info ,
}
} ) ;
const svc _process = globalThis . services . get ( 'process' ) ;
2024-04-15 11:44:52 +08:00
svc _process . register ( process ) ;
2024-04-15 11:23:33 +08:00
2024-03-03 10:39:14 +08:00
//-----------------------------------
// iframe_url
//-----------------------------------
let iframe _url ;
2024-04-13 08:51:50 +08:00
// This can be any trusted URL that won't be used for other apps
const BUILTIN _PREFIX = 'https://builtins.namespaces.puter.com/' ;
2024-03-03 10:39:14 +08:00
if ( ! app _info . index _url ) {
iframe _url = new URL ( 'https://' + options . name + '.' + window . app _domain + ` /index.html ` ) ;
2024-04-13 08:51:50 +08:00
} else if ( app _info . index _url . startsWith ( BUILTIN _PREFIX ) ) {
const name = app _info . index _url . slice ( BUILTIN _PREFIX . length ) ;
iframe _url = new URL ( ` ${ gui _origin } /builtin/ ${ name } ` ) ;
} else {
2024-03-03 10:39:14 +08:00
iframe _url = new URL ( app _info . index _url ) ;
}
// add app_instance_id to URL
iframe _url . searchParams . append ( 'puter.app_instance_id' , uuid ) ;
2024-03-17 10:57:33 +08:00
2024-03-03 10:39:14 +08:00
// add app_id to URL
iframe _url . searchParams . append ( 'puter.app.id' , app _info . uuid ) ;
2024-04-05 23:55:49 +08:00
// add parent_app_instance_id to URL
if ( options . parent _instance _id ) {
iframe _url . searchParams . append ( 'puter.parent_instance_id' , options . parent _instance _id ) ;
}
2024-03-03 10:39:14 +08:00
if ( file _signature ) {
iframe _url . searchParams . append ( 'puter.item.uid' , file _signature . uid ) ;
iframe _url . searchParams . append ( 'puter.item.path' , options . file _path ? ` ~/ ` + options . file _path . split ( '/' ) . slice ( 1 ) . join ( '/' ) : file _signature . path ) ;
iframe _url . searchParams . append ( 'puter.item.name' , file _signature . fsentry _name ) ;
iframe _url . searchParams . append ( 'puter.item.read_url' , file _signature . read _url ) ;
iframe _url . searchParams . append ( 'puter.item.write_url' , file _signature . write _url ) ;
iframe _url . searchParams . append ( 'puter.item.metadata_url' , file _signature . metadata _url ) ;
iframe _url . searchParams . append ( 'puter.item.size' , file _signature . fsentry _size ) ;
iframe _url . searchParams . append ( 'puter.item.accessed' , file _signature . fsentry _accessed ) ;
iframe _url . searchParams . append ( 'puter.item.modified' , file _signature . fsentry _modified ) ;
iframe _url . searchParams . append ( 'puter.item.created' , file _signature . fsentry _created ) ;
iframe _url . searchParams . append ( 'puter.domain' , app _domain ) ;
}
else if ( options . readURL ) {
iframe _url . searchParams . append ( 'puter.item.name' , options . filename ) ;
iframe _url . searchParams . append ( 'puter.item.path' , options . file _path ? ` ~/ ` + options . file _path . split ( '/' ) . slice ( 1 ) . join ( '/' ) : undefined ) ;
iframe _url . searchParams . append ( 'puter.item.read_url' , options . readURL ) ;
iframe _url . searchParams . append ( 'puter.domain' , window . app _domain ) ;
}
2024-04-05 05:44:34 +08:00
if ( app _info . godmode && app _info . godmode === 1 ) {
// Add auth_token to GODMODE apps
2024-03-03 10:39:14 +08:00
iframe _url . searchParams . append ( 'puter.auth.token' , auth _token ) ;
iframe _url . searchParams . append ( 'puter.auth.username' , window . user . username ) ;
iframe _url . searchParams . append ( 'puter.domain' , window . app _domain ) ;
2024-04-05 05:44:34 +08:00
} else if ( options . token ) {
// App token. Only add token if it's not a GODMODE app since GODMODE apps already have the super token
// that has access to everything.
2024-03-17 10:57:33 +08:00
2024-04-05 05:44:34 +08:00
iframe _url . searchParams . append ( 'puter.auth.token' , options . token ) ;
} else {
// Try to acquire app token from the server
2024-03-29 10:10:03 +08:00
2024-03-03 10:39:14 +08:00
let response = await fetch ( window . api _origin + "/auth/get-user-app-token" , {
"headers" : {
"Content-Type" : "application/json" ,
"Authorization" : "Bearer " + auth _token ,
} ,
"body" : JSON . stringify ( { app _uid : app _info . uid ? ? app _info . uuid } ) ,
"method" : "POST" ,
} ) ;
let res = await response . json ( ) ;
if ( res . token ) {
iframe _url . searchParams . append ( 'puter.auth.token' , res . token ) ;
}
}
2024-04-05 05:44:34 +08:00
if ( api _origin )
iframe _url . searchParams . append ( 'puter.api_origin' , api _origin ) ;
2024-03-03 10:39:14 +08:00
// Add options.params to URL
if ( options . params ) {
iframe _url . searchParams . append ( 'puter.domain' , window . app _domain ) ;
for ( const property in options . params ) {
iframe _url . searchParams . append ( property , options . params [ property ] ) ;
}
}
// Add options.args to URL
iframe _url . searchParams . append ( 'puter.args' , JSON . stringify ( options . args ? ? { } ) ) ;
2024-04-23 04:13:49 +08:00
// ...and finally append utm_source=puter.com to the URL
iframe _url . searchParams . append ( 'utm_source' , 'puter.com' ) ;
2024-03-03 10:39:14 +08:00
2024-04-15 08:39:26 +08:00
el _win = UIWindow ( {
2024-03-03 10:39:14 +08:00
element _uuid : uuid ,
title : title ,
iframe _url : iframe _url . href ,
2024-04-17 19:45:01 +08:00
params : options . params ? ? undefined ,
2024-03-03 10:39:14 +08:00
icon : icon ,
window _class : 'window-app' ,
update _window _url : true ,
app _uuid : app _info . uuid ? ? app _info . uid ,
top : options . maximized ? 0 : undefined ,
left : options . maximized ? 0 : undefined ,
height : options . maximized ? ` calc(100% - ${ window . taskbar _height + window . toolbar _height + 1 } px) ` : undefined ,
width : options . maximized ? ` 100% ` : undefined ,
app : options . name ,
2024-04-13 08:51:50 +08:00
is _visible : ! app _info . background ,
2024-03-03 10:39:14 +08:00
is _maximized : options . maximized ,
is _fullpage : options . is _fullpage ,
... window _options ,
2024-04-14 03:28:28 +08:00
show _in _taskbar : app _info . background ? false : window _options ? . show _in _taskbar ,
2024-04-15 15:37:04 +08:00
} ) ;
2024-03-03 10:39:14 +08:00
2024-04-13 08:51:50 +08:00
if ( ! app _info . background ) {
$ ( el _win ) . show ( ) ;
}
2024-03-03 10:39:14 +08:00
// send post request to /rao to record app open
if ( options . name !== 'explorer' ) {
// add the app to the beginning of the array
launch _apps . recent . unshift ( app _info ) ;
// dedupe the array by uuid, uid, and id
launch _apps . recent = _ . uniqBy ( launch _apps . recent , 'name' ) ;
// limit to window.launch_recent_apps_count
launch _apps . recent = launch _apps . recent . slice ( 0 , window . launch _recent _apps _count ) ;
// send post request to /rao to record app open
$ . ajax ( {
url : api _origin + "/rao" ,
type : 'POST' ,
data : JSON . stringify ( {
original _client _socket _id : window . socket ? . id ,
app _uid : app _info . uid ? ? app _info . uuid ,
} ) ,
async : true ,
contentType : "application/json" ,
headers : {
"Authorization" : "Bearer " + auth _token
} ,
} )
}
}
2024-04-15 08:39:26 +08:00
2024-04-15 11:44:52 +08:00
( async ( ) => {
const el = await el _win ;
$ ( el ) . on ( 'remove' , ( ) => {
const svc _process = globalThis . services . get ( 'process' ) ;
svc _process . unregister ( process . uuid ) ;
2024-04-17 18:47:30 +08:00
// If it's a non-sdk app, report that it launched and closed.
// FIXME: This is awkward. Really, we want some way of knowing when it's launched and reporting that immediately instead.
const $app _iframe = $ ( el ) . find ( '.window-app-iframe' ) ;
if ( $app _iframe . attr ( 'data-appUsesSdk' ) !== 'true' ) {
window . report _app _launched ( process . uuid , { uses _sdk : false } ) ;
// We also have to report an extra close event because the real one was sent already
window . report _app _closed ( process . uuid ) ;
}
2024-04-15 11:44:52 +08:00
} ) ;
2024-04-15 15:37:04 +08:00
process . references . el _win = el ;
process . chstatus ( PROCESS _RUNNING ) ;
2024-04-15 11:44:52 +08:00
} ) ( ) ;
2024-03-03 10:39:14 +08:00
}
window . open _item = async function ( options ) {
let el _item = options . item ;
const $el _parent _window = $ ( el _item ) . closest ( '.window' ) ;
const parent _win _id = $ ( $el _parent _window ) . attr ( 'data-id' ) ;
const is _dir = $ ( el _item ) . attr ( 'data-is_dir' ) === '1' ? true : false ;
const uid = $ ( el _item ) . attr ( 'data-shortcut_to' ) === '' ? $ ( el _item ) . attr ( 'data-uid' ) : $ ( el _item ) . attr ( 'data-shortcut_to' ) ;
const item _path = $ ( el _item ) . attr ( 'data-shortcut_to_path' ) === '' ? $ ( el _item ) . attr ( 'data-path' ) : $ ( el _item ) . attr ( 'data-shortcut_to_path' ) ;
const is _shortcut = $ ( el _item ) . attr ( 'data-is_shortcut' ) === '1' ;
const shortcut _to _path = $ ( el _item ) . attr ( 'data-shortcut_to_path' ) ;
const associated _app _name = $ ( el _item ) . attr ( 'data-associated_app_name' ) ;
2024-03-22 20:46:55 +08:00
const file _uid = $ ( el _item ) . attr ( 'data-uid' )
2024-03-03 10:39:14 +08:00
//----------------------------------------------------------------
// Is this a shortcut whose source is perma-deleted?
//----------------------------------------------------------------
if ( is _shortcut && shortcut _to _path === '' ) {
UIAlert ( ` This shortcut can't be opened because its source has been deleted. ` )
}
//----------------------------------------------------------------
// Is this a shortcut whose source is trashed?
//----------------------------------------------------------------
else if ( is _shortcut && shortcut _to _path . startsWith ( trash _path + '/' ) ) {
UIAlert ( ` This shortcut can't be opened because its source has been deleted. ` )
}
//----------------------------------------------------------------
// Is this a trashed file?
//----------------------------------------------------------------
else if ( item _path . startsWith ( trash _path + '/' ) ) {
UIAlert ( ` This item can't be opened because it's in the trash. To use this item, first drag it out of the Trash. ` )
}
//----------------------------------------------------------------
// Is this a file (no dir) on a SaveFileDialog?
//----------------------------------------------------------------
else if ( $el _parent _window . attr ( 'data-is_saveFileDialog' ) === 'true' && ! is _dir ) {
$el _parent _window . find ( '.savefiledialog-filename' ) . val ( $ ( el _item ) . attr ( 'data-name' ) ) ;
$el _parent _window . find ( '.savefiledialog-save-btn' ) . trigger ( 'click' ) ;
}
//----------------------------------------------------------------
// Is this a file (no dir) on an OpenFileDialog?
//----------------------------------------------------------------
else if ( $el _parent _window . attr ( 'data-is_openFileDialog' ) === 'true' && ! is _dir ) {
$el _parent _window . find ( '.window-disable-mask, .busy-indicator' ) . show ( ) ;
let busy _init _ts = Date . now ( ) ;
try {
let filedialog _parent _uid = $el _parent _window . attr ( 'data-parent_uuid' ) ;
let $filedialog _parent _app _window = $ ( ` .window[data-element_uuid=" ${ filedialog _parent _uid } "] ` ) ;
let parent _window _app _uid = $filedialog _parent _app _window . attr ( 'data-app_uuid' ) ;
const initiating _app _uuid = $el _parent _window . attr ( 'data-initiating_app_uuid' ) ;
let res = await puter . fs . sign ( window . host _app _uid ? ? parent _window _app _uid , { uid : uid , action : 'write' } ) ;
res = res . items ;
// todo split is buggy because there might be a slash in the filename
res . path = ` ~/ ` + item _path . split ( '/' ) . slice ( 2 ) . join ( '/' ) ;
const parent _uuid = $el _parent _window . attr ( 'data-parent_uuid' ) ;
const return _to _parent _window = $el _parent _window . attr ( 'data-return_to_parent_window' ) === 'true' ;
if ( return _to _parent _window ) {
window . opener . postMessage ( {
msg : "fileOpenPicked" ,
original _msg _id : $el _parent _window . attr ( 'data-iframe_msg_uid' ) ,
items : Array . isArray ( res ) ? [ ... res ] : [ res ] ,
// LEGACY SUPPORT, remove this in the future when Polotno uses the new SDK
// this is literally put in here to support Polotno's legacy code
... ( ! Array . isArray ( res ) && res )
} , '*' ) ;
window . close ( ) ;
}
else if ( parent _uuid ) {
// send event to iframe
const target _iframe = $ ( ` .window[data-element_uuid=" ${ parent _uuid } "] ` ) . find ( '.window-app-iframe' ) . get ( 0 ) ;
if ( target _iframe ) {
let retobj = {
msg : "fileOpenPicked" ,
original _msg _id : $el _parent _window . attr ( 'data-iframe_msg_uid' ) ,
items : Array . isArray ( res ) ? [ ... res ] : [ res ] ,
// LEGACY SUPPORT, remove this in the future when Polotno uses the new SDK
// this is literally put in here to support Polotno's legacy code
... ( ! Array . isArray ( res ) && res )
} ;
target _iframe . contentWindow . postMessage ( retobj , '*' ) ;
}
// focus iframe
$ ( target _iframe ) . get ( 0 ) ? . focus ( { preventScroll : true } ) ;
// send file_opened event
const file _opened _event = new CustomEvent ( 'file_opened' , { detail : res } ) ;
// dispatch event to parent window
$ ( ` .window[data-element_uuid=" ${ parent _uuid } "] ` ) . get ( 0 ) ? . dispatchEvent ( file _opened _event ) ;
}
} catch ( e ) {
console . log ( e ) ;
}
// done
let busy _duration = ( Date . now ( ) - busy _init _ts ) ;
if ( busy _duration >= busy _indicator _hide _delay ) {
$el _parent _window . close ( ) ;
} else {
setTimeout ( ( ) => {
// close this dialog
$el _parent _window . close ( ) ;
} , Math . abs ( busy _indicator _hide _delay - busy _duration ) ) ;
}
}
//----------------------------------------------------------------
2024-03-23 02:49:57 +08:00
// Does the user have a preference for this file type?
2024-03-22 20:46:55 +08:00
//----------------------------------------------------------------
else if ( user _preferences [ ` default_apps ${ path . extname ( item _path ) . toLowerCase ( ) } ` ] ) {
launch _app ( {
name : user _preferences [ ` default_apps ${ path . extname ( item _path ) . toLowerCase ( ) } ` ] ,
file _path : item _path ,
window _title : path . basename ( item _path ) ,
maximized : options . maximized ,
file _uid : file _uid ,
} ) ;
}
//----------------------------------------------------------------
2024-03-03 10:39:14 +08:00
// Is there an app associated with this item?
//----------------------------------------------------------------
else if ( associated _app _name !== '' ) {
launch _app ( {
name : associated _app _name ,
} )
}
//----------------------------------------------------------------
// Dir with no open windows: create a new window
//----------------------------------------------------------------
else if ( is _dir && ( $el _parent _window . length === 0 || options . new _window ) ) {
UIWindow ( {
path : item _path ,
title : path . basename ( item _path ) ,
icon : await item _icon ( { is _dir : true , path : item _path } ) ,
uid : $ ( el _item ) . attr ( 'data-uid' ) ,
is _dir : is _dir ,
app : 'explorer' ,
top : options . maximized ? 0 : undefined ,
left : options . maximized ? 0 : undefined ,
height : options . maximized ? ` calc(100% - ${ window . taskbar _height + window . toolbar _height + 1 } px) ` : undefined ,
width : options . maximized ? ` 100% ` : undefined ,
} ) ;
}
//----------------------------------------------------------------
// Dir with an open window: change the path of the open window
//----------------------------------------------------------------
else if ( $el _parent _window . length > 0 && is _dir ) {
window _nav _history [ parent _win _id ] = window _nav _history [ parent _win _id ] . slice ( 0 , window _nav _history _current _position [ parent _win _id ] + 1 ) ;
window _nav _history [ parent _win _id ] . push ( item _path ) ;
window _nav _history _current _position [ parent _win _id ] ++ ;
update _window _path ( $el _parent _window , item _path ) ;
}
//----------------------------------------------------------------
// all other cases: try to open using an app
//----------------------------------------------------------------
else {
const fspath = item _path . toLowerCase ( ) ;
const fsuid = uid . toLowerCase ( ) ;
let open _item _meta ;
// get all info needed to open an item
try {
open _item _meta = await $ . ajax ( {
url : api _origin + "/open_item" ,
type : 'POST' ,
contentType : "application/json" ,
data : JSON . stringify ( {
uid : fsuid ? ? undefined ,
path : fspath ? ? undefined ,
} ) ,
headers : {
"Authorization" : "Bearer " + auth _token
} ,
statusCode : {
401 : function ( ) {
logout ( ) ;
} ,
} ,
} ) ;
} catch ( err ) {
}
// get a list of suggested apps for this file type.
let suggested _apps = open _item _meta ? . suggested _apps ? ? await suggest _apps _for _fsentry ( { uid : fsuid , path : fspath } ) ;
//---------------------------------------------
// No suitable apps, ask if user would like to
// download
//---------------------------------------------
if ( suggested _apps . length === 0 ) {
//---------------------------------------------
// If .zip file, unzip it
//---------------------------------------------
if ( path . extname ( item _path ) === '.zip' ) {
unzipItem ( item _path ) ;
return ;
}
const alert _resp = await UIAlert (
'Found no suitable apps to open this file with. Would you like to download it instead?' ,
[
{
2024-03-19 04:07:29 +08:00
label : i18n ( 'download_file' ) ,
2024-03-19 04:16:33 +08:00
value : 'download_file' ,
2024-03-03 10:39:14 +08:00
type : 'primary' ,
} ,
{
2024-03-19 04:07:29 +08:00
label : i18n ( 'cancel' )
2024-03-03 10:39:14 +08:00
}
] )
2024-03-19 04:16:33 +08:00
if ( alert _resp === 'download_file' ) {
2024-03-03 10:39:14 +08:00
trigger _download ( [ item _path ] ) ;
}
return ;
}
//---------------------------------------------
// First suggested app is default app to open this item
//---------------------------------------------
else {
launch _app ( {
name : suggested _apps [ 0 ] . name ,
token : open _item _meta . token ,
file _path : item _path ,
app _obj : suggested _apps [ 0 ] ,
window _title : path . basename ( item _path ) ,
file _uid : fsuid ,
maximized : options . maximized ,
file _signature : open _item _meta . signature ,
} ) ;
}
}
}
/ * *
* Moves the given items to the destination path .
*
* @ param { HTMLElement [ ] } el _items - jQuery elements representing the items to move
* @ param { string } dest _path - The destination path to move the items to
* @ returns { Promise < void > }
* /
2024-03-14 04:15:28 +08:00
window . move _items = async function ( el _items , dest _path , is _undo = false ) {
2024-03-03 10:39:14 +08:00
let move _op _id = operation _id ++ ;
operation _cancelled [ move _op _id ] = false ;
// --------------------------------------------------------
// Optimization: in case all items being moved
// are immutable do not proceed
// --------------------------------------------------------
let all _items _are _immutable = true ;
for ( let i = 0 ; i < el _items . length ; i ++ ) {
if ( $ ( el _items [ i ] ) . attr ( 'data-immutable' ) === '0' ) {
all _items _are _immutable = false ;
break ;
}
}
if ( all _items _are _immutable )
return ;
// --------------------------------------------------------
// good to go, proceed
// --------------------------------------------------------
// overwrite all items? default is false unless in a conflict case user asks for it
let overwrite _all = false ;
// when did this operation start
let move _init _ts = Date . now ( ) ;
2024-03-10 13:26:49 +08:00
// only show progress window if it takes longer than 2s to move
let progwin ;
let progwin _timeout = setTimeout ( async ( ) => {
progwin = await UIWindowMoveProgress ( { operation _id : move _op _id } ) ;
} , 2000 ) ;
2024-03-03 10:39:14 +08:00
2024-03-14 04:15:28 +08:00
// storing moved items for undo ability
const moved _items = [ ]
2024-03-03 10:39:14 +08:00
// Go through each item and try to move it
for ( let i = 0 ; i < el _items . length ; i ++ ) {
// get current item
let el _item = el _items [ i ] ;
// if operation cancelled by user, stop
if ( operation _cancelled [ move _op _id ] )
return ;
// cannot move an immutable item, skip it
if ( $ ( el _item ) . attr ( 'data-immutable' ) === '1' )
continue ;
// cannot move item to its own path, skip it
if ( path . dirname ( $ ( el _item ) . attr ( 'data-path' ) ) === dest _path ) {
await UIAlert ( ` <p>Moving <strong> ${ html _encode ( $ ( el _item ) . attr ( 'data-name' ) ) } </strong></p>Cannot move item to its current location. ` )
continue ;
}
// if an item with the same name already exists in the destination path
let item _with _same _name _already _exists = false ;
let overwrite = overwrite _all ;
let untrashed _at _least _one _item = false ;
// --------------------------------------------------------
// Keep trying to move the item until it succeeds or is cancelled
// or user decides to overwrite or skip
// --------------------------------------------------------
do {
try {
let path _to _show _on _progwin = $ ( el _item ) . attr ( 'data-path' ) ;
// parse metadata if any
let metadata = $ ( el _item ) . attr ( 'data-metadata' ) ;
// no metadata?
if ( metadata === '' || metadata === 'null' || metadata === null )
metadata = { }
// try to parse metadata as JSON
else {
try {
metadata = JSON . parse ( metadata )
} catch ( e ) {
}
}
let new _name ;
// user cancelled?
if ( operation _cancelled [ move _op _id ] )
return ;
// indicates whether this is a recycling operation
let recycling = false ;
// --------------------------------------------------------
// Trashing
// --------------------------------------------------------
if ( dest _path === trash _path ) {
new _name = $ ( el _item ) . attr ( 'data-uid' ) ;
metadata = {
original _name : $ ( el _item ) . attr ( 'data-name' ) ,
original _path : $ ( el _item ) . attr ( 'data-path' ) ,
trashed _ts : Math . round ( Date . now ( ) / 1000 ) ,
} ;
// update other clients
if ( window . socket )
window . socket . emit ( 'trash.is_empty' , { is _empty : false } ) ;
// change trash icons to 'trash-full.svg'
$ ( ` [data-app="trash"] ` ) . find ( '.taskbar-icon > img' ) . attr ( 'src' , window . icons [ 'trash-full.svg' ] ) ;
$ ( ` .item[data-path=" ${ html _encode ( trash _path ) } " i], .item[data-shortcut_to_path=" ${ html _encode ( trash _path ) } " i] ` ) . find ( '.item-icon > img' ) . attr ( 'src' , window . icons [ 'trash-full.svg' ] ) ;
$ ( ` .window[data-path=" ${ html _encode ( trash _path ) } " i] ` ) . find ( '.window-head-icon' ) . attr ( 'src' , window . icons [ 'trash-full.svg' ] ) ;
}
// moving an item into a trashed directory? deny.
else if ( dest _path . startsWith ( trash _path ) ) {
$ ( progwin ) . close ( ) ;
UIAlert ( 'Cannot move items into a deleted folder.' ) ;
return ;
}
// --------------------------------------------------------
// If recycling an item, restore its original name
// --------------------------------------------------------
else if ( metadata . trashed _ts !== undefined ) {
recycling = true ;
new _name = metadata . original _name ;
metadata = { } ;
untrashed _at _least _one _item = true ;
path _to _show _on _progwin = trash _path + '/' + new _name ;
}
// --------------------------------------------------------
// update progress window with current item being moved
// --------------------------------------------------------
$ ( progwin ) . find ( '.move-from' ) . html ( path _to _show _on _progwin ) ;
// execute move
let resp = await puter . fs . move ( {
source : $ ( el _item ) . attr ( 'data-uid' ) ,
destination : dest _path ,
overwrite : overwrite || overwrite _all ,
newName : new _name ,
// recycling requires making all missing dirs
createMissingParents : recycling ,
newMetadata : metadata ,
excludeSocketID : window . socket ? . id ,
} ) ;
let fsentry = resp . moved ;
// path must use the real name from DB
fsentry . path = path . join ( dest _path , fsentry . name ) ;
// skip next loop iteration because this iteration was successful
item _with _same _name _already _exists = false ;
// update all shortcut_to_path
$ ( ` .item[data-shortcut_to_path=" ${ html _encode ( $ ( el _item ) . attr ( 'data-path' ) ) } " i] ` ) . attr ( ` data-shortcut_to_path ` , fsentry . path ) ;
// Remove all items with matching uids
$ ( ` .item[data-uid=' ${ $ ( el _item ) . attr ( 'data-uid' ) } '] ` ) . fadeOut ( 150 , function ( ) {
// find all parent windows that contain this item
let parent _windows = $ ( ` .item[data-uid=' ${ $ ( el _item ) . attr ( 'data-uid' ) } '] ` ) . closest ( '.window' ) ;
// remove this item
$ ( this ) . removeItems ( ) ;
// update parent windows' item counts and selected item counts in their footers
$ ( parent _windows ) . each ( function ( ) {
update _explorer _footer _item _count ( this ) ;
update _explorer _footer _selected _items _count ( this )
} ) ;
} )
// if trashing, close windows of trashed items and its descendants
if ( dest _path === trash _path ) {
$ ( ` .window[data-path=" ${ html _encode ( $ ( el _item ) . attr ( 'data-path' ) ) } " i] ` ) . close ( ) ;
// todo this has to be case-insensitive but the `i` selector doesn't work on ^=
$ ( ` .window[data-path^=" ${ html _encode ( $ ( el _item ) . attr ( 'data-path' ) ) } /"] ` ) . close ( ) ;
}
// update all paths of its and its descendants' open windows
else {
// todo this has to be case-insensitive but the `i` selector doesn't work on ^=
$ ( ` .window[data-path^=" ${ html _encode ( $ ( el _item ) . attr ( 'data-path' ) ) } /"], .window[data-path=" ${ html _encode ( $ ( el _item ) . attr ( 'data-path' ) ) } " i] ` ) . each ( function ( ) {
update _window _path ( this , $ ( this ) . attr ( 'data-path' ) . replace ( $ ( el _item ) . attr ( 'data-path' ) , path . join ( dest _path , fsentry . name ) ) ) ;
} )
}
if ( dest _path === trash _path ) {
// if trashing dir...
if ( $ ( el _item ) . attr ( 'data-is_dir' ) === '1' ) {
// disassociate all its websites
// todo, some client-side check to see if this dir has at least one associated website before sending ajax request
puter . hosting . delete ( dir _uuid )
$ ( ` .mywebsites-dir-path[data-uuid=" ${ $ ( el _item ) . attr ( 'data-uid' ) } "] ` ) . remove ( ) ;
// remove the website badge from all instances of the dir
$ ( ` .item[data-uid=" ${ $ ( el _item ) . attr ( 'data-uid' ) } "] ` ) . find ( '.item-has-website-badge' ) . fadeOut ( 300 ) ;
}
}
// if replacing an existing item, remove the old item that was just replaced
if ( resp . overwritten ? . id ) {
$ ( ` .item[data-uid= ${ resp . overwritten . id } ] ` ) . removeItems ( ) ;
}
// if this is trash, get original name from item metadata
fsentry . name = metadata ? . original _name || fsentry . name ;
// create new item on matching containers
2024-03-14 04:15:28 +08:00
const options = {
2024-03-03 10:39:14 +08:00
appendTo : $ ( ` .item-container[data-path=" ${ html _encode ( dest _path ) } " i] ` ) ,
immutable : fsentry . immutable ,
associated _app _name : fsentry . associated _app ? . name ,
uid : fsentry . uid ,
path : fsentry . path ,
icon : await item _icon ( fsentry ) ,
name : ( dest _path === trash _path ) ? $ ( el _item ) . attr ( 'data-name' ) : fsentry . name ,
is _dir : fsentry . is _dir ,
size : fsentry . size ,
type : fsentry . type ,
modified : fsentry . modified ,
is _selected : false ,
is _shared : ( dest _path === trash _path ) ? false : fsentry . is _shared ,
is _shortcut : fsentry . is _shortcut ,
shortcut _to : fsentry . shortcut _to ,
shortcut _to _path : fsentry . shortcut _to _path ,
has _website : $ ( el _item ) . attr ( 'data-has_website' ) === '1' ,
metadata : fsentry . metadata ? ? '' ,
suggested _apps : fsentry . suggested _apps ,
2024-03-14 04:15:28 +08:00
}
UIItem ( options ) ;
moved _items . push ( { 'options' : options , 'original_path' : $ ( el _item ) . attr ( 'data-path' ) } ) ;
2024-03-03 10:39:14 +08:00
// this operation may have created some missing directories,
// see if any of the directories in the path of this file is new AND
// if these new path have any open parents that need to be updated
resp . parent _dirs _created ? . forEach ( async dir => {
let item _container = $ ( ` .item-container[data-path=" ${ html _encode ( path . dirname ( dir . path ) ) } " i] ` ) ;
if ( item _container . length > 0 && $ ( ` .item[data-path=" ${ html _encode ( dir . path ) } " i] ` ) . length === 0 ) {
UIItem ( {
appendTo : item _container ,
immutable : false ,
uid : dir . uid ,
path : dir . path ,
icon : await item _icon ( dir ) ,
name : dir . name ,
size : dir . size ,
type : dir . type ,
modified : dir . modified ,
is _dir : true ,
is _selected : false ,
is _shared : dir . is _shared ,
has _website : false ,
suggested _apps : dir . suggested _apps ,
} ) ;
}
sort _items ( item _container ) ;
} ) ;
//sort each container
$ ( ` .item-container[data-path=" ${ html _encode ( dest _path ) } " i] ` ) . each ( function ( ) {
sort _items ( this , $ ( this ) . attr ( 'data-sort_by' ) , $ ( this ) . attr ( 'data-sort_order' ) )
} )
} catch ( err ) {
// -----------------------------------------------------------------------
// if item with same name already exists, ask user if they want to overwrite
// -----------------------------------------------------------------------
if ( err . code === 'item_with_same_name_exists' ) {
item _with _same _name _already _exists = true ;
const alert _resp = await UIAlert ( {
message : ` <strong> ${ html _encode ( err . entry _name ) } </strong> already exists. ` ,
buttons : [
2024-03-19 04:16:33 +08:00
{ label : i18n ( 'replace' ) , type : 'primary' , value : 'replace' } ,
... ( el _items . length > 1 ) ? [ { label : i18n ( 'replace_all' ) , value : 'replace_all' } ] : [ ] ,
... ( el _items . length > 1 ) ? [ { label : i18n ( 'skip' ) , value : 'skip' } ] : [ { label : i18n ( 'cancel' ) , value : 'cancel' } ] ,
2024-03-03 10:39:14 +08:00
]
} )
2024-03-19 04:16:33 +08:00
if ( alert _resp === 'replace' ) {
2024-03-03 10:39:14 +08:00
overwrite = true ;
2024-03-19 04:16:33 +08:00
} else if ( alert _resp === 'replace_all' ) {
2024-03-03 10:39:14 +08:00
overwrite = true ;
overwrite _all = true ;
2024-03-19 04:16:33 +08:00
} else if ( alert _resp === 'skip' || alert _resp === 'cancel' ) {
2024-03-03 10:39:14 +08:00
item _with _same _name _already _exists = false ;
}
}
// -----------------------------------------------------------------------
// all other errors
// -----------------------------------------------------------------------
else {
item _with _same _name _already _exists = false ;
// error message after source item has reappeared
$ ( el _item ) . show ( 0 , function ( ) {
UIAlert ( ` <p>Moving <strong> ${ html _encode ( $ ( el _item ) . attr ( 'data-name' ) ) } </strong></p> ${ err . message ? ? '' } ` )
} ) ;
break ;
}
}
} while ( item _with _same _name _already _exists ) ;
// check if trash is empty
if ( untrashed _at _least _one _item ) {
const trash = await puter . fs . stat ( trash _path ) ;
if ( window . socket ) {
window . socket . emit ( 'trash.is_empty' , { is _empty : trash . is _empty } ) ;
}
if ( trash . is _empty ) {
$ ( ` [data-app="trash"] ` ) . find ( '.taskbar-icon > img' ) . attr ( 'src' , window . icons [ 'trash.svg' ] ) ;
$ ( ` .item[data-path=" ${ html _encode ( trash _path ) } " i] ` ) . find ( '.item-icon > img' ) . attr ( 'src' , window . icons [ 'trash.svg' ] ) ;
$ ( ` .window[data-path=" ${ html _encode ( trash _path ) } " i] ` ) . find ( '.window-head-icon' ) . attr ( 'src' , window . icons [ 'trash.svg' ] ) ;
}
}
}
2024-03-10 13:26:49 +08:00
clearTimeout ( progwin _timeout ) ;
2024-03-03 10:39:14 +08:00
// log stats to console
let move _duration = ( Date . now ( ) - move _init _ts ) ;
console . log ( ` moved ${ el _items . length } item ${ el _items . length > 1 ? 's' : '' } in ${ move _duration } ms ` ) ;
// -----------------------------------------------------------------------
// DONE! close progress window with delay to allow user to see 100% progress
// -----------------------------------------------------------------------
2024-03-14 04:15:28 +08:00
// Add action to actions_history for undo ability
if ( ! is _undo && dest _path !== trash _path ) {
actions _history . push ( {
operation : 'move' ,
data : moved _items ,
} ) ;
} else if ( ! is _undo && dest _path === trash _path ) {
actions _history . push ( {
operation : 'delete' ,
data : moved _items ,
} ) ;
}
2024-03-10 13:26:49 +08:00
if ( progwin ) {
setTimeout ( ( ) => {
$ ( progwin ) . close ( ) ;
} , copy _progress _hide _delay ) ;
}
2024-03-03 10:39:14 +08:00
}
/ * *
* Generates sharing URLs for various social media platforms and services based on the provided arguments .
*
* @ global
* @ function
* @ param { Object } args - Configuration object for generating share URLs .
* @ param { string } [ args . url ] - The URL to share .
* @ param { string } [ args . title ] - The title or headline of the content to share .
* @ param { string } [ args . image ] - Image URL associated with the content .
* @ param { string } [ args . desc ] - A description of the content .
* @ param { string } [ args . appid ] - App ID for certain platforms that require it .
* @ param { string } [ args . redirecturl ] - Redirect URL for certain platforms .
* @ param { string } [ args . via ] - Attribution source , e . g . , a Twitter username .
* @ param { string } [ args . hashtags ] - Comma - separated list of hashtags without '#' .
* @ param { string } [ args . provider ] - Content provider .
* @ param { string } [ args . language ] - Content ' s language .
* @ param { string } [ args . userid ] - User ID for certain platforms .
* @ param { string } [ args . category ] - Content ' s category .
* @ param { string } [ args . phonenumber ] - Phone number for platforms like SMS or Telegram .
* @ param { string } [ args . emailaddress ] - Email address to share content to .
* @ param { string } [ args . ccemailaddress ] - CC email address for sharing content .
* @ param { string } [ args . bccemailaddress ] - BCC email address for sharing content .
* @ returns { Object } - An object containing key - value pairs where keys are platform names and values are constructed sharing URLs .
*
* @ example
* const shareConfig = {
* url : 'https://example.com' ,
* title : 'Check this out!' ,
* desc : 'This is an amazing article on example.com' ,
* via : 'exampleUser'
* } ;
* const shareLinks = window . socialLink ( shareConfig ) ;
* console . log ( shareLinks . twitter ) ; // Outputs the constructed Twitter share link
* /
window . socialLink = function ( args ) {
const validargs = [
'url' ,
'title' ,
'image' ,
'desc' ,
'appid' ,
'redirecturl' ,
'via' ,
'hashtags' ,
'provider' ,
'language' ,
'userid' ,
'category' ,
'phonenumber' ,
'emailaddress' ,
'cemailaddress' ,
'bccemailaddress' ,
] ;
for ( var i = 0 ; i < validargs . length ; i ++ ) {
const validarg = validargs [ i ] ;
if ( ! args [ validarg ] ) {
args [ validarg ] = '' ;
}
}
const url = fixedEncodeURIComponent ( args . url ) ;
const title = fixedEncodeURIComponent ( args . title ) ;
const image = fixedEncodeURIComponent ( args . image ) ;
const desc = fixedEncodeURIComponent ( args . desc ) ;
const via = fixedEncodeURIComponent ( args . via ) ;
const hash _tags = fixedEncodeURIComponent ( args . hashtags ) ;
const language = fixedEncodeURIComponent ( args . language ) ;
const user _id = fixedEncodeURIComponent ( args . userid ) ;
const category = fixedEncodeURIComponent ( args . category ) ;
const phone _number = fixedEncodeURIComponent ( args . phonenumber ) ;
const email _address = fixedEncodeURIComponent ( args . emailaddress ) ;
const cc _email _address = fixedEncodeURIComponent ( args . ccemailaddress ) ;
const bcc _email _address = fixedEncodeURIComponent ( args . bccemailaddress ) ;
var text = title ;
if ( desc ) {
text += '%20%3A%20' ; // This is just this, " : "
text += desc ;
}
return {
'add.this' : 'http://www.addthis.com/bookmark.php?url=' + url ,
'blogger' : 'https://www.blogger.com/blog-this.g?u=' + url + '&n=' + title + '&t=' + desc ,
'buffer' : 'https://buffer.com/add?text=' + text + '&url=' + url ,
'diaspora' : 'https://share.diasporafoundation.org/?title=' + title + '&url=' + url ,
'douban' : 'http://www.douban.com/recommend/?url=' + url + '&title=' + text ,
'email' : 'mailto:' + email _address + '?subject=' + title + '&body=' + desc ,
'evernote' : 'https://www.evernote.com/clip.action?url=' + url + '&title=' + text ,
'getpocket' : 'https://getpocket.com/edit?url=' + url ,
'facebook' : 'http://www.facebook.com/sharer.php?u=' + url ,
'flattr' : 'https://flattr.com/submit/auto?user_id=' + user _id + '&url=' + url + '&title=' + title + '&description=' + text + '&language=' + language + '&tags=' + hash _tags + '&hidden=HIDDEN&category=' + category ,
'flipboard' : 'https://share.flipboard.com/bookmarklet/popout?v=2&title=' + text + '&url=' + url ,
'gmail' : 'https://mail.google.com/mail/?view=cm&to=' + email _address + '&su=' + title + '&body=' + url + '&bcc=' + bcc _email _address + '&cc=' + cc _email _address ,
'google.bookmarks' : 'https://www.google.com/bookmarks/mark?op=edit&bkmk=' + url + '&title=' + title + '&annotation=' + text + '&labels=' + hash _tags + '' ,
'instapaper' : 'http://www.instapaper.com/edit?url=' + url + '&title=' + title + '&description=' + desc ,
'line.me' : 'https://lineit.line.me/share/ui?url=' + url + '&text=' + text ,
'linkedin' : 'https://www.linkedin.com/sharing/share-offsite/?url=' + url ,
'livejournal' : 'http://www.livejournal.com/update.bml?subject=' + text + '&event=' + url ,
'hacker.news' : 'https://news.ycombinator.com/submitlink?u=' + url + '&t=' + title ,
'ok.ru' : 'https://connect.ok.ru/dk?st.cmd=WidgetSharePreview&st.shareUrl=' + url ,
'pinterest' : 'http://pinterest.com/pin/create/button/?url=' + url ,
'qzone' : 'http://sns.qzone.qq.com/cgi-bin/qzshare/cgi_qzshare_onekey?url=' + url ,
'reddit' : 'https://reddit.com/submit?url=' + url + '&title=' + title ,
'renren' : 'http://widget.renren.com/dialog/share?resourceUrl=' + url + '&srcUrl=' + url + '&title=' + text + '&description=' + desc ,
'skype' : 'https://web.skype.com/share?url=' + url + '&text=' + text ,
'sms' : 'sms:' + phone _number + '?body=' + text ,
'surfingbird.ru' : 'http://surfingbird.ru/share?url=' + url + '&description=' + desc + '&screenshot=' + image + '&title=' + title ,
'telegram.me' : 'https://t.me/share/url?url=' + url + '&text=' + text + '&to=' + phone _number ,
'threema' : 'threema://compose?text=' + text + '&id=' + user _id ,
'tumblr' : 'https://www.tumblr.com/widgets/share/tool?canonicalUrl=' + url + '&title=' + title + '&caption=' + desc + '&tags=' + hash _tags ,
'twitter' : 'https://twitter.com/intent/tweet?url=' + url + '&text=' + text + '&via=' + via + '&hashtags=' + hash _tags ,
'vk' : 'http://vk.com/share.php?url=' + url + '&title=' + title + '&comment=' + desc ,
'weibo' : 'http://service.weibo.com/share/share.php?url=' + url + '&appkey=&title=' + title + '&pic=&ralateUid=' ,
'whatsapp' : 'https://api.whatsapp.com/send?text=' + text + '%20' + url ,
'xing' : 'https://www.xing.com/spi/shares/new?url=' + url ,
'yahoo' : 'http://compose.mail.yahoo.com/?to=' + email _address + '&subject=' + title + '&body=' + text ,
} ;
}
/ * *
* Encodes a URI component with enhanced safety by replacing characters
* that are not typically encoded by the standard encodeURIComponent .
*
* @ param { string } str - The string to be URI encoded .
* @ returns { string } - Returns the URI encoded string .
*
* @ example
* const str = "Hello, world!" ;
* const encodedStr = fixedEncodeURIComponent ( str ) ;
* console . log ( encodedStr ) ; // Expected output: "Hello%2C%20world%21"
* /
function fixedEncodeURIComponent ( str ) {
return encodeURIComponent ( str ) . replace ( /[!'()*]/g , function ( c ) {
return '%' + c . charCodeAt ( 0 ) . toString ( 16 ) ;
} ) ;
}
/ * *
* Refreshes the desktop background based on the user ' s settings .
* If the user has set a custom desktop background URL or color , it will use that .
* If not , it defaults to a specific wallpaper image .
*
* @ global
* @ function
* @ fires set _desktop _background - Calls this global function to set the desktop background .
*
* @ example
* // This will refresh the desktop background according to the user's preference or defaults.
* window . refresh _desktop _background ( ) ;
* /
window . refresh _desktop _background = function ( ) {
if ( window . user && ( window . user . desktop _bg _url !== null || window . user . desktop _bg _color !== null ) ) {
window . set _desktop _background ( {
url : window . user . desktop _bg _url ,
fit : window . user . desktop _bg _fit ,
color : window . user . desktop _bg _color ,
} )
}
// default background
else {
let wallpaper = ( window . gui _env === 'prod' ) ? '/dist/images/wallpaper.webp' : '/images/wallpaper.webp' ;
window . set _desktop _background ( {
url : wallpaper ,
fit : 'cover' ,
} ) ;
}
}
window . determine _website _url = function ( fsentry _path ) {
// search window.sites and if any site has `dir_path` set and the fsentry_path starts with that dir_path + '/', return the site's url + path
for ( let i = 0 ; i < window . sites . length ; i ++ ) {
if ( window . sites [ i ] . dir _path && fsentry _path . startsWith ( window . sites [ i ] . dir _path + '/' ) ) {
return window . sites [ i ] . address + fsentry _path . replace ( window . sites [ i ] . dir _path , '' ) ;
}
}
return null ;
}
window . update _sites _cache = function ( ) {
return puter . hosting . list ( ( sites ) => {
if ( sites && sites . length > 0 ) {
window . sites = sites ;
} else {
window . sites = [ ] ;
}
} )
}
/ * *
*
* @ param { * } el _target _container
* @ param { * } target _path
* /
window . init _upload _using _dialog = function ( el _target _container , target _path = null ) {
$ ( "#upload-file-dialog" ) . unbind ( 'onchange' ) ;
$ ( "#upload-file-dialog" ) . unbind ( 'change' ) ;
$ ( "#upload-file-dialog" ) . unbind ( 'onChange' ) ;
target _path = target _path === null ? $ ( el _target _container ) . attr ( 'data-path' ) : path . resolve ( target _path ) ;
$ ( '#upload-file-dialog' ) . trigger ( 'click' ) ;
$ ( "#upload-file-dialog" ) . on ( 'change' , async function ( e ) {
if ( $ ( "#upload-file-dialog" ) . val ( ) !== '' ) {
const files = $ ( '#upload-file-dialog' ) [ 0 ] . files ;
if ( files . length > 0 ) {
try {
upload _items ( files , target _path ) ;
}
catch ( err ) {
UIAlert ( err . message ? ? err )
}
$ ( '#upload-file-dialog' ) . val ( '' ) ;
}
}
else {
return
}
} )
}
window . upload _items = async function ( items , dest _path ) {
let upload _progress _window ;
let opid ;
2024-03-14 04:41:12 +08:00
if ( dest _path == trash _path ) {
UIAlert ( 'Uploading to trash is not allowed!' ) ;
return ;
}
2024-03-03 10:39:14 +08:00
puter . fs . upload (
// what to upload
items ,
// where to upload
dest _path ,
// options
{
// init
init : async ( operation _id , xhr ) => {
opid = operation _id ;
// create upload progress window
upload _progress _window = await UIWindowUploadProgress ( { operation _id : operation _id } ) ;
// cancel btn
$ ( upload _progress _window ) . find ( '.upload-cancel-btn' ) . on ( 'click' , function ( e ) {
$ ( upload _progress _window ) . close ( ) ;
show _save _account _notice _if _needed ( ) ;
xhr . abort ( ) ;
} )
// add to active_uploads
active _uploads [ opid ] = 0 ;
} ,
// start
start : async function ( ) {
// change upload progress window message to uploading
$ ( upload _progress _window ) . find ( '.upload-progress-msg' ) . html ( ` Uploading (<span class="upload-progress-percent">0%</span>) ` ) ;
} ,
// progress
progress : async function ( operation _id , op _progress ) {
$ ( ` [data-upload-operation-id=" ${ operation _id } "] ` ) . find ( '.upload-progress-bar' ) . css ( 'width' , op _progress + '%' ) ;
$ ( ` [data-upload-operation-id=" ${ operation _id } "] ` ) . find ( '.upload-progress-percent' ) . html ( op _progress + '%' ) ;
// update active_uploads
active _uploads [ opid ] = op _progress ;
// update title if window is not visible
if ( document . visibilityState !== "visible" ) {
update _title _based _on _uploads ( ) ;
}
} ,
// success
success : async function ( items ) {
// DONE
2024-03-11 17:07:53 +08:00
// Add action to actions_history for undo ability
const files = [ ]
if ( typeof items [ Symbol . iterator ] === 'function' ) {
for ( const item of items ) {
files . push ( item . path )
}
} else {
files . push ( items . path )
}
actions _history . push ( {
operation : 'upload' ,
data : files
} ) ;
2024-03-03 10:39:14 +08:00
// close progress window after a bit of delay for a better UX
setTimeout ( ( ) => {
setTimeout ( ( ) => {
$ ( upload _progress _window ) . close ( ) ;
show _save _account _notice _if _needed ( ) ;
} , Math . abs ( upload _progress _hide _delay ) ) ;
} )
// remove from active_uploads
delete active _uploads [ opid ] ;
} ,
// error
error : async function ( err ) {
$ ( upload _progress _window ) . close ( ) ;
// UIAlert(err?.message ?? 'An error occurred while uploading.');
// remove from active_uploads
delete active _uploads [ opid ] ;
} ,
// abort
abort : async function ( operation _id ) {
// console.log('upload aborted');
// remove from active_uploads
delete active _uploads [ opid ] ;
}
}
) ;
}
window . empty _trash = async function ( ) {
const alert _resp = await UIAlert ( {
2024-03-19 04:07:29 +08:00
message : i18n ( 'empty_trash_confirmation' ) ,
2024-03-03 10:39:14 +08:00
buttons : [
{
2024-03-19 04:07:29 +08:00
label : i18n ( 'yes' ) ,
2024-03-03 10:39:14 +08:00
value : 'yes' ,
type : 'primary' ,
} ,
{
2024-03-19 04:07:29 +08:00
label : i18n ( 'no' ) ,
2024-03-03 10:39:14 +08:00
value : 'no' ,
} ,
]
} )
if ( alert _resp === 'no' )
return ;
// only show progress window if it takes longer than 500ms to create folder
let init _ts = Date . now ( ) ;
let progwin ;
let op _id = uuidv4 ( ) ;
let progwin _timeout = setTimeout ( async ( ) => {
progwin = await UIWindowProgressEmptyTrash ( { operation _id : op _id } ) ;
} , 500 ) ;
await puter . fs . delete ( {
paths : trash _path ,
descendantsOnly : true ,
recursive : true ,
success : async function ( resp ) {
// update other clients
if ( window . socket ) {
window . socket . emit ( 'trash.is_empty' , { is _empty : true } ) ;
}
// use the 'empty trash' icon for Trash
$ ( ` [data-app="trash"] ` ) . find ( '.taskbar-icon > img' ) . attr ( 'src' , window . icons [ 'trash.svg' ] ) ;
$ ( ` .item[data-path=" ${ html _encode ( trash _path ) } " i], .item[data-shortcut_to_path=" ${ html _encode ( trash _path ) } " i] ` ) . find ( '.item-icon > img' ) . attr ( 'src' , window . icons [ 'trash.svg' ] ) ;
$ ( ` .window[data-path=" ${ trash _path } "] ` ) . find ( '.window-head-icon' ) . attr ( 'src' , window . icons [ 'trash.svg' ] ) ;
// remove all items with trash paths
// todo this has to be case-insensitive but the `i` selector doesn't work on ^=
$ ( ` .item[data-path^=" ${ trash _path } /"] ` ) . removeItems ( ) ;
// update the footer item count for Trash
update _explorer _footer _item _count ( $ ( ` .window[data-path=" ${ trash _path } "] ` ) )
// close progress window
clearTimeout ( progwin _timeout ) ;
setTimeout ( ( ) => {
$ ( progwin ) . close ( ) ;
} , Math . max ( 0 , copy _progress _hide _delay - ( Date . now ( ) - init _ts ) ) ) ;
} ,
error : async function ( err ) {
clearTimeout ( progwin _timeout ) ;
setTimeout ( ( ) => {
$ ( progwin ) . close ( ) ;
} , Math . max ( 0 , copy _progress _hide _delay - ( Date . now ( ) - init _ts ) ) ) ;
}
} ) ;
}
window . copy _to _clipboard = async function ( text ) {
if ( navigator . clipboard ) {
// copy text to clipboard
await navigator . clipboard . writeText ( text ) ;
}
else {
document . execCommand ( 'copy' ) ;
}
}
window . getUsage = ( ) => {
return fetch ( api _origin + "/drivers/usage" , {
headers : {
"Content-Type" : "application/json" ,
"Authorization" : "Bearer " + auth _token
} ,
method : "GET"
} )
. then ( response => {
// Check if the response is ok (status code in the range 200-299)
if ( ! response . ok ) {
throw new Error ( 'Network response was not ok' ) ;
}
return response . json ( ) ; // Parse the response as JSON
} )
. then ( data => {
// Handle the JSON data
return data ;
} )
. catch ( error => {
// Handle any errors
console . error ( 'There has been a problem with your fetch operation:' , error ) ;
} ) ;
}
window . getAppUIDFromOrigin = async function ( origin ) {
try {
const response = await fetch ( window . api _origin + "/auth/app-uid-from-origin" , {
headers : {
"Content-Type" : "application/json" ,
"Authorization" : "Bearer " + window . auth _token ,
} ,
body : JSON . stringify ( { origin : origin } ) ,
method : "POST" ,
} ) ;
const data = await response . json ( ) ;
// Assuming the app_uid is in the data object, return it
return data . uid ;
} catch ( err ) {
// Handle any errors here
console . error ( err ) ;
// You may choose to return something specific here in case of an error
return null ;
}
}
window . getUserAppToken = async function ( origin ) {
try {
const response = await fetch ( window . api _origin + "/auth/get-user-app-token" , {
headers : {
"Content-Type" : "application/json" ,
"Authorization" : "Bearer " + window . auth _token ,
} ,
body : JSON . stringify ( { origin : origin } ) ,
method : "POST" ,
} ) ;
const data = await response . json ( ) ;
// return
return data ;
} catch ( err ) {
// Handle any errors here
console . error ( err ) ;
// You may choose to return something specific here in case of an error
return null ;
}
}
window . checkUserSiteRelationship = async function ( origin ) {
try {
const response = await fetch ( window . api _origin + "/auth/check-app " , {
headers : {
"Content-Type" : "application/json" ,
"Authorization" : "Bearer " + window . auth _token ,
} ,
body : JSON . stringify ( { origin : origin } ) ,
method : "POST" ,
} ) ;
const data = await response . json ( ) ;
// return
return data ;
} catch ( err ) {
// Handle any errors here
console . error ( err ) ;
// You may choose to return something specific here in case of an error
return null ;
}
}
window . zipItems = async function ( el _items , targetDirPath , download = true ) {
const zip = new JSZip ( ) ;
// if single item, convert to array
el _items = Array . isArray ( el _items ) ? el _items : [ el _items ] ;
// create progress window
let start _ts = Date . now ( ) ;
let progwin , progwin _timeout ;
// only show progress window if it takes longer than 500ms to download
progwin _timeout = setTimeout ( async ( ) => {
progwin = await UIWindowDownloadDirProg ( ) ;
} , 500 ) ;
for ( const el _item of el _items ) {
let targetPath = $ ( el _item ) . attr ( 'data-path' ) ;
// if directory, zip the directory
if ( $ ( el _item ) . attr ( 'data-is_dir' ) === '1' ) {
$ ( progwin ) . find ( '.dir-dl-status' ) . html ( ` Reading <strong> ${ html _encode ( targetPath ) } </strong> ` ) ;
// Recursively read the directory
let children = await readDirectoryRecursive ( targetPath ) ;
// Add files to the zip
for ( const child of children ) {
let relativePath ;
if ( el _items . length === 1 )
relativePath = child . relativePath ;
else
relativePath = path . basename ( targetPath ) + '/' + child . relativePath ;
// update progress window
$ ( progwin ) . find ( '.dir-dl-status' ) . html ( ` Zipping <strong> ${ html _encode ( relativePath ) } </strong> ` ) ;
// read file content
let content = await puter . fs . read ( child . path ) ;
try {
zip . file ( relativePath , content , { binary : true } ) ;
} catch ( e ) {
console . error ( e ) ;
}
}
}
// if item is a file, zip the file
else {
let content = await puter . fs . read ( targetPath ) ;
zip . file ( path . basename ( targetPath ) , content , { binary : true } ) ;
}
}
// determine name of zip file
let zipName ;
if ( el _items . length === 1 )
zipName = path . basename ( $ ( el _items [ 0 ] ) . attr ( 'data-path' ) ) ;
else
zipName = 'Archive' ;
// Generate the zip file
zip . generateAsync ( { type : "blob" } )
. then ( async function ( content ) {
// Trigger the download
if ( download ) {
const url = URL . createObjectURL ( content ) ;
const a = document . createElement ( "a" ) ;
a . href = url ;
a . download = zipName ;
document . body . appendChild ( a ) ;
a . click ( ) ;
// Cleanup
document . body . removeChild ( a ) ;
URL . revokeObjectURL ( url ) ;
}
// save
else
await puter . fs . write ( targetDirPath + '/' + zipName + ".zip" , content , { overwrite : false , dedupeName : true } )
// close progress window
clearTimeout ( progwin _timeout ) ;
setTimeout ( ( ) => {
$ ( progwin ) . close ( ) ;
} , Math . max ( 0 , copy _progress _hide _delay - ( Date . now ( ) - start _ts ) ) ) ;
} )
. catch ( function ( err ) {
// close progress window
clearTimeout ( progwin _timeout ) ;
setTimeout ( ( ) => {
$ ( progwin ) . close ( ) ;
} , Math . max ( 0 , copy _progress _hide _delay - ( Date . now ( ) - start _ts ) ) ) ;
// handle errors
console . error ( "Error in zipping files: " , err ) ;
} ) ;
}
async function readDirectoryRecursive ( path , baseDir = '' ) {
let allFiles = [ ] ;
// Read the directory
const entries = await puter . fs . readdir ( path ) ;
// Process each entry
for ( const entry of entries ) {
const fullPath = ` ${ path } / ${ entry . name } ` ;
if ( entry . is _dir ) {
// If entry is a directory, recursively read it
const subDirFiles = await readDirectoryRecursive ( fullPath , ` ${ baseDir } ${ entry . name } / ` ) ;
allFiles = allFiles . concat ( subDirFiles ) ;
} else {
// If entry is a file, add it to the list
allFiles . push ( { path : fullPath , relativePath : ` ${ baseDir } ${ entry . name } ` } ) ;
}
}
return allFiles ;
}
window . extractSubdomain = function ( url ) {
var subdomain = url . split ( '://' ) [ 1 ] . split ( '.' ) [ 0 ] ;
return subdomain ;
}
window . sleep = function ( ms ) {
return new Promise ( resolve => setTimeout ( resolve , ms ) ) ;
}
window . unzipItem = async function ( itemPath ) {
// create progress window
let start _ts = Date . now ( ) ;
let progwin , progwin _timeout ;
// only show progress window if it takes longer than 500ms to download
progwin _timeout = setTimeout ( async ( ) => {
progwin = await UIWindowDownloadDirProg ( ) ;
} , 500 ) ;
const zip = new JSZip ( ) ;
let filPath = itemPath ;
let file = puter . fs . read ( filPath ) ;
zip . loadAsync ( file ) . then ( async function ( zip ) {
const rootdir = await puter . fs . mkdir ( path . dirname ( filPath ) + '/' + path . basename ( filPath , '.zip' ) , { dedupeName : true } ) ;
Object . keys ( zip . files ) . forEach ( async function ( filename ) {
console . log ( filename ) ;
if ( filename . endsWith ( '/' ) )
await puter . fs . mkdir ( rootdir . path + '/' + filename , { createMissingParents : true } ) ;
zip . files [ filename ] . async ( 'blob' ) . then ( async function ( fileData ) {
await puter . fs . write ( rootdir . path + '/' + filename , fileData ) ;
} ) . catch ( function ( e ) {
// UIAlert(e.message);
} )
} )
// close progress window
clearTimeout ( progwin _timeout ) ;
setTimeout ( ( ) => {
$ ( progwin ) . close ( ) ;
} , Math . max ( 0 , copy _progress _hide _delay - ( Date . now ( ) - start _ts ) ) ) ;
} ) . catch ( function ( e ) {
// UIAlert(e.message);
// close progress window
clearTimeout ( progwin _timeout ) ;
setTimeout ( ( ) => {
$ ( progwin ) . close ( ) ;
} , Math . max ( 0 , copy _progress _hide _delay - ( Date . now ( ) - start _ts ) ) ) ;
} )
}
2024-03-10 22:18:17 +08:00
2024-03-11 17:07:53 +08:00
window . rename _file = async ( options , new _name , old _name , old _path , el _item , el _item _name , el _item _icon , el _item _name _editor , website _url , is _undo = false ) => {
2024-03-10 22:35:58 +08:00
puter . fs . rename ( {
uid : options . uid === 'null' ? null : options . uid ,
new _name : new _name ,
excludeSocketID : window . socket . id ,
success : async ( fsentry ) => {
// Add action to actions_history for undo ability
if ( ! is _undo )
actions _history . push ( {
operation : 'rename' ,
2024-03-11 17:07:53 +08:00
data : { options , new _name , old _name , old _path , el _item , el _item _name , el _item _icon , el _item _name _editor , website _url }
2024-03-10 22:35:58 +08:00
} ) ;
// Has the extension changed? in that case update options.sugggested_apps
const old _extension = path . extname ( old _name ) ;
const new _extension = path . extname ( new _name ) ;
if ( old _extension !== new _extension ) {
suggest _apps _for _fsentry ( {
uid : options . uid ,
onSuccess : function ( suggested _apps ) {
options . suggested _apps = suggested _apps ;
}
} ) ;
}
// Set new item name
$ ( ` .item[data-uid=' ${ $ ( el _item ) . attr ( 'data-uid' ) } '] .item-name ` ) . html ( html _encode ( truncate _filename ( new _name , TRUNCATE _LENGTH ) ) . replaceAll ( ' ' , ' ' ) ) ;
$ ( el _item _name ) . show ( ) ;
// Hide item name editor
$ ( el _item _name _editor ) . hide ( ) ;
// Set new icon
const new _icon = ( options . is _dir ? window . icons [ 'folder.svg' ] : ( await item _icon ( fsentry ) ) . image ) ;
$ ( el _item _icon ) . find ( '.item-icon-icon' ) . attr ( 'src' , new _icon ) ;
// Set new data-name
options . name = new _name ;
$ ( el _item ) . attr ( 'data-name' , html _encode ( new _name ) ) ;
$ ( ` .item[data-uid=' ${ $ ( el _item ) . attr ( 'data-uid' ) } '] ` ) . attr ( 'data-name' , html _encode ( new _name ) ) ;
$ ( ` .window- ${ options . uid } ` ) . attr ( 'data-name' , html _encode ( new _name ) ) ;
// Set new title attribute
$ ( ` .item[data-uid=' ${ $ ( el _item ) . attr ( 'data-uid' ) } '] ` ) . attr ( 'title' , html _encode ( new _name ) ) ;
$ ( ` .window- ${ options . uid } ` ) . attr ( 'title' , html _encode ( new _name ) ) ;
// Set new value for item-name-editor
$ ( ` .item[data-uid=' ${ $ ( el _item ) . attr ( 'data-uid' ) } '] .item-name-editor ` ) . val ( html _encode ( new _name ) ) ;
$ ( ` .item[data-uid=' ${ $ ( el _item ) . attr ( 'data-uid' ) } '] .item-name ` ) . attr ( 'title' , html _encode ( new _name ) ) ;
// Set new data-path
options . path = path . join ( path . dirname ( options . path ) , options . name ) ;
const new _path = options . path ;
$ ( el _item ) . attr ( 'data-path' , new _path ) ;
$ ( ` .item[data-uid=' ${ $ ( el _item ) . attr ( 'data-uid' ) } '] ` ) . attr ( 'data-path' , new _path ) ;
$ ( ` .window- ${ options . uid } ` ) . attr ( 'data-path' , new _path ) ;
// Update all elements that have matching paths
$ ( ` [data-path=" ${ html _encode ( old _path ) } " i] ` ) . each ( function ( ) {
$ ( this ) . attr ( 'data-path' , new _path )
if ( $ ( this ) . hasClass ( 'window-navbar-path-dirname' ) )
$ ( this ) . text ( new _name ) ;
} ) ;
// Update the paths of all elements whose paths start with old_path
$ ( ` [data-path^=" ${ html _encode ( old _path ) + '/' } "] ` ) . each ( function ( ) {
const new _el _path = _ . replace ( $ ( this ) . attr ( 'data-path' ) , old _path + '/' , new _path + '/' ) ;
$ ( this ) . attr ( 'data-path' , new _el _path ) ;
} ) ;
// Update the 'Sites Cache'
if ( $ ( el _item ) . attr ( 'data-has_website' ) === '1' )
await update _sites _cache ( ) ;
// Update website_url
website _url = determine _website _url ( new _path ) ;
$ ( el _item ) . attr ( 'data-website_url' , website _url ) ;
// Update all exact-matching windows
$ ( ` .window- ${ options . uid } ` ) . each ( function ( ) {
update _window _path ( this , options . path ) ;
} )
// Set new name for corresponding open windows
$ ( ` .window- ${ options . uid } .window-head-title ` ) . text ( new _name ) ;
// Re-sort all matching item containers
$ ( ` .item[data-uid=' ${ $ ( el _item ) . attr ( 'data-uid' ) } '] ` ) . parent ( '.item-container' ) . each ( function ( ) {
sort _items ( this , $ ( el _item ) . closest ( '.item-container' ) . attr ( 'data-sort_by' ) , $ ( el _item ) . closest ( '.item-container' ) . attr ( 'data-sort_order' ) ) ;
} )
} ,
error : function ( err ) {
// reset to old name
$ ( el _item _name ) . text ( truncate _filename ( options . name , TRUNCATE _LENGTH ) ) ;
$ ( el _item _name ) . show ( ) ;
// hide item name editor
$ ( el _item _name _editor ) . hide ( ) ;
$ ( el _item _name _editor ) . val ( html _encode ( $ ( el _item ) . attr ( 'data-name' ) ) ) ;
//show error
if ( err . message ) {
UIAlert ( err . message )
}
} ,
} ) ;
}
2024-03-11 17:07:53 +08:00
/ * *
* Deletes the given item with path .
*
* @ param { string } path - path of the item to delete
* @ returns { Promise < void > }
* /
window . delete _item _with _path = async function ( path ) {
try {
await puter . fs . delete ( {
paths : path ,
descendantsOnly : false ,
recursive : true ,
} ) ;
} catch ( err ) {
UIAlert ( err . responseText ) ;
}
}
2024-03-10 22:18:17 +08:00
window . undo _last _action = async ( ) => {
if ( actions _history . length > 0 ) {
const last _action = actions _history . pop ( ) ;
// Undo the create file action
if ( last _action . operation === 'create_file' || last _action . operation === 'create_folder' ) {
const lastCreatedItem = last _action . data ;
undo _create _file _or _folder ( lastCreatedItem ) ;
2024-03-10 22:35:58 +08:00
} else if ( last _action . operation === 'rename' ) {
2024-03-11 17:07:53 +08:00
const { options , new _name , old _name , old _path , el _item , el _item _name , el _item _icon , el _item _name _editor , website _url } = last _action . data ;
rename _file ( options , old _name , new _name , old _path , el _item , el _item _name , el _item _icon , el _item _name _editor , website _url , true ) ;
} else if ( last _action . operation === 'upload' ) {
const files = last _action . data ;
undo _upload ( files ) ;
2024-03-13 02:18:15 +08:00
} else if ( last _action . operation === 'copy' ) {
const files = last _action . data ;
undo _copy ( files ) ;
2024-03-14 04:15:28 +08:00
} else if ( last _action . operation === 'move' ) {
const items = last _action . data ;
undo _move ( items ) ;
} else if ( last _action . operation === 'delete' ) {
const items = last _action . data ;
undo _delete ( items ) ;
2024-03-10 22:18:17 +08:00
}
}
}
window . undo _create _file _or _folder = async ( item ) => {
await window . delete _item ( item ) ;
}
2024-03-11 17:07:53 +08:00
window . undo _upload = async ( files ) => {
for ( const file of files ) {
await window . delete _item _with _path ( file ) ;
}
}
2024-03-13 02:18:15 +08:00
window . undo _copy = async ( files ) => {
for ( const file of files ) {
await window . delete _item _with _path ( file ) ;
}
2024-03-14 04:15:28 +08:00
}
window . undo _move = async ( items ) => {
for ( const item of items ) {
2024-03-14 10:43:56 +08:00
const el = await get _html _element _from _options ( item . options ) ;
2024-03-14 04:15:28 +08:00
console . log ( item . original _path )
move _items ( [ el ] , path . dirname ( item . original _path ) , true ) ;
}
}
window . undo _delete = async ( items ) => {
for ( const item of items ) {
2024-03-14 10:43:56 +08:00
const el = await get _html _element _from _options ( item . options ) ;
2024-03-14 04:15:28 +08:00
let metadata = $ ( el ) . attr ( 'data-metadata' ) === '' ? { } : JSON . parse ( $ ( el ) . attr ( 'data-metadata' ) )
move _items ( [ el ] , path . dirname ( metadata . original _path ) , true ) ;
}
}
window . get _html _element _from _options = async function ( options ) {
const item _id = global _element _id ++ ;
options . disabled = options . disabled ? ? false ;
options . visible = options . visible ? ? 'visible' ; // one of 'visible', 'revealed', 'hidden'
options . is _dir = options . is _dir ? ? false ;
options . is _selected = options . is _selected ? ? false ;
options . is _shared = options . is _shared ? ? false ;
options . is _shortcut = options . is _shortcut ? ? 0 ;
options . is _trash = options . is _trash ? ? false ;
options . metadata = options . metadata ? ? '' ;
options . multiselectable = options . multiselectable ? ? true ;
options . shortcut _to = options . shortcut _to ? ? '' ;
options . shortcut _to _path = options . shortcut _to _path ? ? '' ;
options . immutable = ( options . immutable === false || options . immutable === 0 || options . immutable === undefined ? 0 : 1 ) ;
options . sort _container _after _append = ( options . sort _container _after _append !== undefined ? options . sort _container _after _append : false ) ;
const is _shared _with _me = ( options . path !== '/' + window . user . username && ! options . path . startsWith ( '/' + window . user . username + '/' ) ) ;
let website _url = determine _website _url ( options . path ) ;
// do a quick check to see if the target parent has any file type restrictions
const appendto _allowed _file _types = $ ( options . appendTo ) . attr ( 'data-allowed_file_types' )
if ( ! window . check _fsentry _against _allowed _file _types _string ( { is _dir : options . is _dir , name : options . name , type : options . type } , appendto _allowed _file _types ) )
options . disabled = true ;
// --------------------------------------------------------
// HTML for Item
// --------------------------------------------------------
let h = '' ;
h += ` <div id="item- ${ item _id } "
class = "item${options.is_selected ? ' item-selected':''} ${options.disabled ? 'item-disabled':''} item-${options.visible}"
data - id = "${item_id}"
data - name = "${html_encode(options.name)}"
data - metadata = "${html_encode(options.metadata)}"
data - uid = "${options.uid}"
data - is _dir = "${options.is_dir ? 1 : 0}"
data - is _trash = "${options.is_trash ? 1 : 0}"
data - has _website = "${options.has_website ? 1 : 0 }"
data - website _url = "${website_url ? html_encode(website_url) : ''}"
data - immutable = "${options.immutable}"
data - is _shortcut = "${options.is_shortcut}"
data - shortcut _to = "${html_encode(options.shortcut_to)}"
data - shortcut _to _path = "${html_encode(options.shortcut_to_path)}"
data - sortable = "${options.sortable ?? 'true'}"
data - sort _by = "${html_encode(options.sort_by) ?? 'name'}"
data - size = "${options.size ?? ''}"
data - type = "${html_encode(options.type) ?? ''}"
data - modified = "${options.modified ?? ''}"
data - associated _app _name = "${html_encode(options.associated_app_name) ?? ''}"
data - path = "${html_encode(options.path)}" > ` ;
// spinner
h += ` <div class="item-spinner"> ` ;
h += ` </div> ` ;
// modified
h += ` <div class="item-attr item-attr--modified"> ` ;
h += ` <span> ${ options . modified === 0 ? '-' : timeago . format ( options . modified * 1000 ) } </span> ` ;
h += ` </div> ` ;
// size
h += ` <div class="item-attr item-attr--size"> ` ;
h += ` <span> ${ options . size ? byte _format ( options . size ) : '-' } </span> ` ;
h += ` </div> ` ;
// type
h += ` <div class="item-attr item-attr--type"> ` ;
if ( options . is _dir )
h += ` <span>Folder</span> ` ;
else
h += ` <span> ${ options . type ? html _encode ( options . type ) : '-' } </span> ` ;
h += ` </div> ` ;
// icon
h += ` <div class="item-icon"> ` ;
h += ` <img src=" ${ html _encode ( options . icon . image ) } " class="item-icon- ${ options . icon . type } " data-item-id=" ${ item _id } "> ` ;
h += ` </div> ` ;
// badges
h += ` <div class="item-badges"> ` ;
// website badge
h += ` <img class="item-badge item-has-website-badge long-hover"
style = "${options.has_website ? 'display:block;' : ''}"
src = "${html_encode(window.icons['world.svg'])}"
data - item - id = "${item_id}"
> ` ;
// link badge
h += ` <img class="item-badge item-has-website-url-badge"
style = "${website_url ? 'display:block;' : ''}"
src = "${html_encode(window.icons['link.svg'])}"
data - item - id = "${item_id}"
> ` ;
// shared badge
h += ` <img class="item-badge item-badge-has-permission"
style = " display : $ { is _shared _with _me ? 'block' : 'none' } ;
background - color : # ffffff ;
padding : 2 px ; " src=" $ { html _encode ( window . icons [ 'shared.svg' ] ) } "
data - item - id = "${item_id}"
title = "A user has shared this item with you." > ` ;
// owner-shared badge
h += ` <img class="item-badge item-is-shared"
style = "background-color: #ffffff; padding: 2px; ${!is_shared_with_me && options.is_shared ? 'display:block;' : ''}"
src = "${html_encode(window.icons['owner-shared.svg'])}"
data - item - id = "${item_id}"
data - item - uid = "${options.uid}"
data - item - path = "${html_encode(options.path)}"
title = "You have shared this item with at least one other user."
> ` ;
// shortcut badge
h += ` <img class="item-badge item-shortcut"
style = "background-color: #ffffff; padding: 2px; ${options.is_shortcut !== 0 ? 'display:block;' : ''}"
src = "${html_encode(window.icons['shortcut.svg'])}"
data - item - id = "${item_id}"
title = "Shortcut"
> ` ;
h += ` </div> ` ;
// name
h += ` <span class="item-name" data-item-id=" ${ item _id } " title=" ${ html _encode ( options . name ) } "> ${ html _encode ( truncate _filename ( options . name , TRUNCATE _LENGTH ) ) . replaceAll ( ' ' , ' ' ) } </span> `
// name editor
h += ` <textarea class="item-name-editor hide-scrollbar" spellcheck="false" autocomplete="off" autocorrect="off" autocapitalize="off" data-gramm_editor="false"> ${ html _encode ( options . name ) } </textarea> `
h += ` </div> ` ;
return h ;
2024-03-15 23:02:47 +08:00
}
window . store _auto _arrange _preference = ( preference ) => {
2024-03-16 14:02:11 +08:00
puter . kv . set ( 'user_preferences.auto_arrange_desktop' , preference ) ;
2024-03-15 23:02:47 +08:00
localStorage . setItem ( 'auto_arrange' , preference ) ;
}
2024-03-16 14:02:11 +08:00
window . get _auto _arrange _data = async ( ) => {
const preferenceValue = await puter . kv . get ( 'user_preferences.auto_arrange_desktop' ) ;
is _auto _arrange _enabled = preferenceValue === null ? true : preferenceValue ;
2024-03-17 12:22:02 +08:00
const positions = await puter . kv . get ( 'desktop_item_positions' )
2024-03-18 12:16:15 +08:00
desktop _item _positions = ( ! positions || typeof positions !== 'object' || Array . isArray ( positions ) ) ? { } : positions ;
2024-03-15 23:02:47 +08:00
}
window . clear _desktop _item _positions = async ( el _desktop ) => {
$ ( el _desktop ) . find ( '.item' ) . each ( function ( ) {
const el _item = $ ( this ) [ 0 ] ;
$ ( el _item ) . css ( 'position' , '' ) ;
$ ( el _item ) . css ( 'left' , '' ) ;
$ ( el _item ) . css ( 'top' , '' ) ;
} ) ;
2024-03-18 11:44:51 +08:00
if ( reset _item _positions ) {
delete _desktop _item _positions ( )
}
2024-03-15 23:02:47 +08:00
}
window . set _desktop _item _positions = async ( el _desktop ) => {
$ ( el _desktop ) . find ( '.item' ) . each ( async function ( ) {
2024-03-16 14:02:11 +08:00
const position = desktop _item _positions [ $ ( this ) . attr ( 'data-uid' ) ]
2024-03-15 23:02:47 +08:00
const el _item = $ ( this ) [ 0 ] ;
if ( position ) {
$ ( el _item ) . css ( 'position' , 'absolute' ) ;
$ ( el _item ) . css ( 'left' , position . left + 'px' ) ;
$ ( el _item ) . css ( 'top' , position . top + 'px' ) ;
}
} ) ;
}
2024-03-16 14:02:11 +08:00
window . save _desktop _item _positions = ( ) => {
puter . kv . set ( 'desktop_item_positions' , desktop _item _positions ) ;
2024-03-15 23:02:47 +08:00
}
2024-03-18 11:44:51 +08:00
window . delete _desktop _item _positions = ( ) => {
desktop _item _positions = { }
puter . kv . del ( 'desktop_item_positions' ) ;
2024-03-30 10:53:39 +08:00
}
2024-03-30 11:59:50 +08:00
window . change _clock _visible = ( clock _visible ) => {
let newValue = clock _visible || window . user _preferences . clock _visible ;
2024-03-30 10:53:39 +08:00
2024-03-30 12:25:03 +08:00
newValue === 'auto' && is _fullscreen ( ) ? $ ( '#clock' ) . show ( ) : $ ( '#clock' ) . hide ( ) ;
2024-03-30 10:53:39 +08:00
newValue === 'show' && $ ( '#clock' ) . show ( ) ;
newValue === 'hide' && $ ( '#clock' ) . hide ( ) ;
2024-03-30 11:59:50 +08:00
if ( clock _visible ) {
// save clock_visible to user preferences
2024-03-30 10:53:39 +08:00
window . mutate _user _preferences ( {
2024-03-30 11:59:50 +08:00
clock _visible : newValue
2024-03-30 10:53:39 +08:00
} ) ;
return ;
}
2024-03-30 11:59:50 +08:00
$ ( 'select.change-clock-visible' ) . val ( window . user _preferences . clock _visible ) ;
2024-04-17 17:08:42 +08:00
}
// Finds the `.window` element for the given app instance ID
window . window _for _app _instance = ( instance _id ) => {
return $ ( ` .window[data-element_uuid=" ${ instance _id } "] ` ) . get ( 0 ) ;
} ;
// Finds the `iframe` element for the given app instance ID
window . iframe _for _app _instance = ( instance _id ) => {
return $ ( window _for _app _instance ( instance _id ) ) . find ( '.window-app-iframe' ) . get ( 0 ) ;
} ;
2024-04-17 18:14:33 +08:00
// Run any callbacks to say that the app has launched
window . report _app _launched = ( instance _id , { uses _sdk = true } ) => {
const child _launch _callback = window . child _launch _callbacks [ instance _id ] ;
if ( child _launch _callback ) {
const parent _iframe = iframe _for _app _instance ( child _launch _callback . parent _instance _id ) ;
// send confirmation to requester window
parent _iframe . contentWindow . postMessage ( {
msg : 'childAppLaunched' ,
original _msg _id : child _launch _callback . launch _msg _id ,
child _instance _id : instance _id ,
uses _sdk : uses _sdk ,
} , '*' ) ;
delete window . child _launch _callbacks [ instance _id ] ;
}
2024-04-17 18:47:30 +08:00
} ;
// Run any callbacks to say that the app has closed
window . report _app _closed = ( instance _id ) => {
const el _window = window _for _app _instance ( instance _id ) ;
// notify parent app, if we have one, that we're closing
const parent _id = el _window . dataset [ 'parent_instance_id' ] ;
const parent = $ ( ` .window[data-element_uuid=" ${ parent _id } "] .window-app-iframe ` ) . get ( 0 ) ;
if ( parent ) {
parent . contentWindow . postMessage ( {
msg : 'appClosed' ,
appInstanceID : instance _id ,
} , '*' ) ;
}
// notify child apps, if we have them, that we're closing
const children = $ ( ` .window[data-parent_instance_id=" ${ instance _id } "] .window-app-iframe ` ) ;
children . each ( ( _ , child ) => {
child . contentWindow . postMessage ( {
msg : 'appClosed' ,
appInstanceID : instance _id ,
} , '*' ) ;
} ) ;
// TODO: Once other AppConnections exist, those will need notifying too.
} ;