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 UIAlert from './UIAlert.js' ;
import UIContextMenu from './UIContextMenu.js' ;
import path from '../lib/path.js' ;
import UITaskbarItem from './UITaskbarItem.js' ;
import UIWindowLogin from './UIWindowLogin.js' ;
import UIWindowPublishWebsite from './UIWindowPublishWebsite.js' ;
import UIWindowItemProperties from './UIWindowItemProperties.js' ;
2024-03-17 11:13:48 +08:00
import new _context _menu _item from '../helpers/new_context_menu_item.js' ;
2024-03-03 10:39:14 +08:00
const el _body = document . getElementsByTagName ( 'body' ) [ 0 ] ;
async function UIWindow ( options ) {
const win _id = global _element _id ++ ;
last _window _zindex ++ ;
// options.dominant places the window in center close to top.
options . dominant = options . dominant ? ? false ;
// in case of file dialogs, the window is automatically dominant
if ( options . is _openFileDialog || options . is _saveFileDialog || options . is _directoryPicker )
options . dominant = true ;
// we don't want to increment window_counter for dominant windows
if ( ! options . dominant )
window . window _counter ++ ;
// add this window's id to the window_stack
window _stack . push ( win _id ) ;
// =====================================
// set options defaults
// =====================================
// indicates if sidebar is hidden, only applies to directory windows
let sidebar _hidden = false ;
const default _window _top = ( 'calc(15% + ' + ( ( window . window _counter - 1 ) % 10 * 20 ) + 'px)' ) ;
// list of file types that are allowed, other types will be disabled but still shown
options . allowed _file _types = options . allowed _file _types ? ? '' ;
options . app = options . app ? ? '' ;
options . allow _context _menu = options . allow _context _menu ? ? true ;
options . allow _native _ctxmenu = options . allow _native _ctxmenu ? ? false ;
options . allow _user _select = options . allow _user _select ? ? false ;
options . backdrop = options . backdrop ? ? false ;
options . body _css = options . body _css ? ? { } ;
options . border _radius = options . border _radius ? ? undefined ;
options . draggable _body = options . draggable _body ? ? false ;
options . element _uuid = options . element _uuid ? ? uuidv4 ( ) ;
options . center = options . center ? ? false ;
options . close _on _backdrop _click = options . close _on _backdrop _click ? ? true ;
options . disable _parent _window = options . disable _parent _window ? ? false ;
options . has _head = options . has _head ? ? true ;
options . height = options . height ? ? 380 ;
options . icon = options . icon ? ? null ;
options . iframe _msg _uid = options . iframe _msg _uid ? ? null ;
options . is _droppable = options . is _droppable ? ? true ;
options . is _draggable = options . is _draggable ? ? true ;
options . is _dir = options . is _dir ? ? false ;
options . is _minimized = options . is _minimized ? ? false ;
options . is _maximized = options . is _maximized ? ? false ;
options . is _openFileDialog = options . is _openFileDialog ? ? false ;
options . is _resizable = options . is _resizable ? ? true ;
// if this is a fullpage window, it won't be resizable
if ( options . is _fullpage )
options . is _resizable = false ;
// in the embedded/fullpage mode every window is on top since there is no taskbar to switch between windows
// if user has specifically asked for this window to NOT stay on top, honor it.
if ( ( is _embedded || window . is _fullpage _mode ) && ! options . parent _uuid && options . stay _on _top !== false )
options . stay _on _top = true ;
// Keep the window on top of all previously opened windows
options . stay _on _top = options . stay _on _top ? ? false ;
options . is _saveFileDialog = options . is _saveFileDialog ? ? false ;
options . show _minimize _button = options . show _minimize _button ? ? true ;
options . on _close = options . on _close ? ? undefined ;
options . parent _uuid = options . parent _uuid ? ? null ;
options . selectable _body = options . selectable _body ? ? true ;
options . show _in _taskbar = options . show _in _taskbar ? ? true ;
options . show _maximize _button = options . show _maximize _button ? ? true ;
options . single _instance = options . single _instance ? ? false ;
options . sort _by = options . sort _by ? ? 'name' ;
options . sort _order = options . sort _order ? ? 'asc' ;
options . title = options . title ? ? null ;
options . top = options . top ? ? default _window _top ;
options . type = options . type ? ? null ;
options . update _window _url = options . update _window _url ? ? false ;
options . layout = options . layout ? ? 'icons' ;
options . width = options . width ? ? 680 ;
options . window _css = options . window _css ? ? { } ;
options . window _class = ( options . window _class !== undefined ? ' ' + options . window _class : '' ) ;
// if only one instance is allowed, bring focus to the window that is already open
if ( options . single _instance && options . app !== '' ) {
let $already _open _window = $ ( ` .window[data-app=" ${ html _encode ( options . app ) } "] ` ) ;
if ( $already _open _window . length ) {
$ ( ` .window[data-app=" ${ html _encode ( options . app ) } "] ` ) . focusWindow ( ) ;
return ;
}
}
// left
if ( ! options . dominant && ! options . center ) {
options . left = options . left ? ? ( ( window . innerWidth / 2 - options . width / 2 ) + ( window . window _counter - 1 ) % 10 * 30 ) + 'px' ;
} else if ( ! options . dominant && options . center ) {
options . left = options . left ? ? ( ( window . innerWidth / 2 - options . width / 2 ) ) + 'px' ;
}
else if ( options . dominant ) {
options . left = ( window . innerWidth / 2 - options . width / 2 ) + 'px' ;
}
else
options . left = options . left ? ? ( ( window . innerWidth / 2 - options . width / 2 ) + 'px' ) ;
// top
if ( ! options . dominant && ! options . center ) {
options . top = options . top ? ? ( ( window . innerHeight / 2 - options . height / 2 ) + ( window . window _counter - 1 ) % 10 * 30 ) + 'px' ;
} else if ( ! options . dominant && options . center ) {
options . top = options . top ? ? ( ( window . innerHeight / 2 - options . height / 2 ) ) + 'px' ;
}
else if ( options . dominant ) {
options . top = ( window . innerHeight * 0.15 ) ;
}
else if ( isMobile . phone )
options . top = 100 ;
if ( isMobile . phone ) {
options . left = 0 ;
options . top = window . toolbar _height + 'px' ;
options . width = '100%' ;
options . height = 'calc(100% - ' + window . toolbar _height + 'px)' ;
} else {
options . width += 'px'
options . height += 'px'
}
// =====================================
// cover page
// =====================================
if ( options . cover _page ) {
options . left = 0 ;
options . top = 0 ;
options . width = '100%' ;
options . height = '100%' ;
}
// --------------------------------------------------------
// HTML for Window
// --------------------------------------------------------
let h = '' ;
// Window
let zindex = options . stay _on _top ? ( 99999999 + last _window _zindex + 1 + ' !important' ) : last _window _zindex ;
h += ` <div class="window window-active
$ { options . cover _page ? 'window-cover-page' : '' }
$ { options . uid !== undefined ? 'window-' + options . uid : '' }
$ { options . window _class }
$ { options . allow _user _select ? ' allow-user-select' : '' }
$ { options . is _openFileDialog || options . is _saveFileDialog || options . is _directoryPicker ? 'window-filedialog' : '' } "
id = "window-${win_id}"
data - allowed _file _types = "${html_encode(options.allowed_file_types)}"
data - app = "${html_encode(options.app)}"
data - app _uuid = "${html_encode(options.app_uuid ?? '')}"
data - disable _parent _window = "${html_encode(options.disable_parent_window)}"
data - name = "${html_encode(options.title)}"
data - path = "${html_encode(options.path)}"
data - uid = "${options.uid}"
data - element _uuid = "${options.element_uuid}"
data - parent _uuid = "${options.parent_uuid}"
data - id = "${win_id}"
data - iframe _msg _uid = "${options.iframe_msg_uid}"
data - is _dir = "${options.is_dir}"
data - return _to _parent _window = "${options.return_to_parent_window}"
data - initiating _app _uuid = "${options.initiating_app_uuid}"
data - is _openFileDialog = "${options.is_openFileDialog}"
data - is _saveFileDialog = "${options.is_saveFileDialog}"
data - is _directoryPicker = "${options.is_directoryPicker}"
data - is _fullpage = "${options.is_fullpage ? 1 : 0}"
data - is _minimized = "${options.is_minimized ? 1 : 0}"
data - is _maximized = "${options.is_maximized ? 1 : 0}"
data - layout = "${options.layout}"
data - stay _on _top = "${options.stay_on_top}"
data - sort _by = "${options.sort_by ?? 'name'}"
data - sort _order = "${options.sort_order ?? 'asc'}"
data - multiselectable = "${options.selectable_body}"
data - update _window _url = "${options.update_window_url}"
data - initial _zindex = "${zindex}"
style = " z - index : $ { zindex } ;
$ { options . width !== undefined ? 'width: ' + html _encode ( options . width ) + '; ' : '' }
$ { options . height !== undefined ? 'height: ' + html _encode ( options . height ) + '; ' : '' }
$ { options . border _radius !== undefined ? 'border-radius: ' + html _encode ( options . border _radius ) + '; ' : '' }
"
> ` ;
// window mask
h += ` <div class="window-disable-mask"> ` ;
//busy indicator
h += ` <div class="busy-indicator">BUSY</div> ` ;
h += ` </div> ` ;
// Head
if ( options . has _head ) {
h += ` <div class="window-head"> ` ;
// draggable handle which also contains icon and title
h += ` <div class="window-head-draggable"> ` ;
// icon
if ( options . icon )
h += ` <img class="window-head-icon" /> ` ;
// title
h += ` <span class="window-head-title" title=" ${ html _encode ( options . title ) } "></span> ` ;
h += ` </div> ` ;
// Minimize button, only if window is resizable and not embedded
if ( options . is _resizable && options . show _minimize _button && ! is _embedded )
h += ` <span class="window-action-btn window-minimize-btn" style="margin-left:0;"><img src=" ${ html _encode ( window . icons [ 'minimize.svg' ] ) } " draggable="false"></span> ` ;
// Maximize button
if ( options . is _resizable && options . show _maximize _button )
h += ` <span class="window-action-btn window-scale-btn"><img src=" ${ html _encode ( window . icons [ 'scale.svg' ] ) } " draggable="false"></span> ` ;
// Close button
h += ` <span class="window-action-btn window-close-btn"><img src=" ${ html _encode ( window . icons [ 'close.svg' ] ) } " draggable="false"></span> ` ;
h += ` </div> ` ;
}
// Sidebar
if ( options . is _dir && ! isMobile . phone ) {
h += ` <div class="window-sidebar disable-user-select hide-scrollbar"
style = "${window.window_sidebar_width ? 'width: ' + html_encode(window.window_sidebar_width) + 'px !important;' : ''}"
draggable = "false"
> ` ;
// favorites
h += ` <h2 class="window-sidebar-title disable-user-select">Favorites</h2> ` ;
h += ` <div draggable="false" title="Home" class="window-sidebar-item disable-user-select ${ options . path === window . home _path ? 'window-sidebar-item-active' : '' } " data-path=" ${ html _encode ( window . home _path ) } "><img draggable="false" class="window-sidebar-item-icon" src=" ${ html _encode ( window . icons [ 'folder-home.svg' ] ) } ">Home</div> ` ;
h += ` <div draggable="false" title="Documents" class="window-sidebar-item disable-user-select ${ options . path === window . docs _path ? 'window-sidebar-item-active' : '' } " data-path=" ${ html _encode ( window . docs _path ) } "><img draggable="false" class="window-sidebar-item-icon" src=" ${ html _encode ( window . icons [ 'folder-documents.svg' ] ) } ">Documents</div> ` ;
h += ` <div draggable="false" title="Pictures" class="window-sidebar-item disable-user-select ${ options . path === window . pictures _path ? 'window-sidebar-item-active' : '' } " data-path=" ${ html _encode ( window . pictures _path ) } "><img draggable="false" class="window-sidebar-item-icon" src=" ${ html _encode ( window . icons [ 'folder-pictures.svg' ] ) } ">Pictures</div> ` ;
h += ` <div draggable="false" title="Desktop" class="window-sidebar-item disable-user-select ${ options . path === window . desktop _path ? 'window-sidebar-item-active' : '' } " data-path=" ${ html _encode ( window . desktop _path ) } "><img draggable="false" class="window-sidebar-item-icon" src=" ${ html _encode ( window . icons [ 'folder-desktop.svg' ] ) } ">Desktop</div> ` ;
h += ` <div draggable="false" title="Videos" class="window-sidebar-item disable-user-select ${ options . path === window . videos _path ? 'window-sidebar-item-active' : '' } " data-path=" ${ html _encode ( window . videos _path ) } "><img draggable="false" class="window-sidebar-item-icon" src=" ${ html _encode ( window . icons [ 'folder-videos.svg' ] ) } ">Videos</div> ` ;
h += ` </div> ` ;
}
// Navbar
if ( options . is _dir ) {
h += ` <div class="window-navbar"> ` ;
h += ` <div style="float:left; margin-left:5px; margin-right:5px;"> ` ;
// Back
h += ` <img draggable="false" class="window-navbar-btn window-navbar-btn-back window-navbar-btn-disabled" src=" ${ html _encode ( window . icons [ 'arrow-left.svg' ] ) } " title="Click to go back."> ` ;
// Forward
h += ` <img draggable="false" class="window-navbar-btn window-navbar-btn-forward window-navbar-btn-disabled" src=" ${ html _encode ( window . icons [ 'arrow-right.svg' ] ) } " title="Click to go forward."> ` ;
// Up
h += ` <img draggable="false" class="window-navbar-btn window-navbar-btn-up ${ options . path === '/' ? 'window-navbar-btn-disabled' : '' } " src=" ${ html _encode ( window . icons [ 'arrow-up.svg' ] ) } " title="Click to go one directory up."> ` ;
h += ` </div> ` ;
// Path
h += ` <div class="window-navbar-path"> ${ navbar _path ( options . path , window . user . username ) } </div> ` ;
// Path editor
h += ` <input class="window-navbar-path-input" data-path=" ${ html _encode ( options . path ) } " value=" ${ html _encode ( options . path ) } " spellcheck="false"/> ` ;
// Layout settings
h += ` <img class="window-navbar-layout-settings" src=" ${ html _encode ( options . layout === 'icons' ? window . icons [ 'layout-icons.svg' ] : window . icons [ 'layout-list.svg' ] ) } " draggable="false"> ` ;
h += ` </div> ` ;
}
// Body
h += ` <div
class = "window-body${options.is_dir ? ' item-container' : ''}${options.iframe_url !== undefined || options.iframe_srcdoc !== undefined ? ' window-body-app' : ''}${options.is_saveFileDialog || options.is_openFileDialog || options.is_directoryPicker ? ' window-body-filedialog' : ''}"
data - allowed _file _types = "${html_encode(options.allowed_file_types)}"
data - path = "${html_encode(options.path)}"
data - multiselectable = "${options.selectable_body}"
data - sort _by = "${options.sort_by ?? 'name'}"
data - sort _order = "${options.sort_order ?? 'asc'}"
data - uid = "${options.uid}"
id = "window-body-${win_id}"
style = "${!options.has_head ? ' height: 100%;' : ''}" > ` ;
// iframe, for apps
if ( options . iframe _url || options . iframe _srcdoc ) {
// iframe
h += ` <iframe tabindex="-1"
data - app = "${html_encode(options.app)}"
class = "window-app-iframe"
allowtransparency = "true" allowpaymentrequest = "true" allowfullscreen = "true"
frameborder = "0" webkitallowfullscreen = "webkitallowfullscreen" mozallowfullscreen = "mozallowfullscreen"
$ { options . iframe _url ? 'src="' + html _encode ( options . iframe _url ) + '"' : '' }
$ { options . iframe _srcdoc ? 'srcdoc="' + html _encode ( options . iframe _srcdoc ) + '"' : '' }
2024-03-19 14:48:53 +08:00
allow = "accelerometer; camera; encrypted-media; gamepad; display-capture; geolocation; gyroscope; microphone; midi; clipboard-read; clipboard-write; web-share; fullscreen;"
2024-03-20 16:13:17 +08:00
sandbox = "allow-forms allow-modals allow-pointer-lock allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-top-navigation-by-user-activation allow-downloads allow-presentation allow-storage-access-by-user-activation" > < / i f r a m e > ` ;
2024-03-03 10:39:14 +08:00
}
// custom body
else if ( options . body _content !== undefined ) {
h += options . body _content ;
}
// Directory
if ( options . is _dir ) {
// Detail layout header
h += window . explore _table _headers ( ) ;
// Add 'This folder is empty' message by default
h += ` <div class="explorer-empty-message">This folder is empty</div> ` ;
// Loading spinner
h += ` <div class="explorer-loading-spinner"> ` ;
h += ` <svg style="display:block; margin: 0 auto; " xmlns="http://www.w3.org/2000/svg" height="24" width="24" viewBox="0 0 24 24"><title>circle anim</title><g fill="#212121" class="nc-icon-wrapper"><g class="nc-loop-circle-24-icon-f"><path d="M12 24a12 12 0 1 1 12-12 12.013 12.013 0 0 1-12 12zm0-22a10 10 0 1 0 10 10A10.011 10.011 0 0 0 12 2z" fill="#212121" opacity=".4"></path><path d="M24 12h-2A10.011 10.011 0 0 0 12 2V0a12.013 12.013 0 0 1 12 12z" data-color="color-2"></path></g><style>.nc-loop-circle-24-icon-f{--animation-duration:0.5s;transform-origin:12px 12px;animation:nc-loop-circle-anim var(--animation-duration) infinite linear}@keyframes nc-loop-circle-anim{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}</style></g></svg> ` ;
2024-03-19 04:29:27 +08:00
h += ` <p class="explorer-loading-spinner-msg"> ${ i18n ( 'loading' ) } ...</p> ` ;
2024-03-03 10:39:14 +08:00
h += ` </div> ` ;
}
h += ` </div> ` ;
// Explorer footer
if ( options . is _dir && ! options . is _saveFileDialog && ! options . is _openFileDialog && ! options . is _directoryPicker ) {
h += ` <div class="explorer-footer"> `
h += ` <span class="explorer-footer-item-count"></span> ` ;
h += ` <span class="explorer-footer-seperator">|</span> ` ;
h += ` <span class="explorer-footer-selected-items-count"></span> ` ;
h += ` </div> ` ;
}
// is_saveFileDialog
if ( options . is _saveFileDialog ) {
h += ` <div class="window-filedialog-prompt"> ` ;
h += ` <div style="display:flex;"> ` ;
h += ` <input type="text" class="savefiledialog-filename" autocorrect="off" spellcheck="false" value=" ${ html _encode ( options . saveFileDialog _default _filename ) ? ? '' } "> ` ;
h += ` <button class="button button-small filedialog-cancel-btn">Cancel</button> ` ;
h += ` <button class="button ` ;
if ( options . saveFileDialog _default _filename === undefined || options . saveFileDialog _default _filename === '' )
h += ` disabled ` ;
h += ` button-small button-primary savefiledialog-save-btn">Save</button> ` ;
h += ` </div> ` ;
h += ` </div> ` ;
}
// is_openFileDialog
else if ( options . is _openFileDialog ) {
h += ` <div class="window-filedialog-prompt"> ` ;
h += ` <div style="text-align:right;"> ` ;
h += ` <button class="button button-small filedialog-cancel-btn">Cancel</button> ` ;
h += ` <button class="button disabled button-small button-primary openfiledialog-open-btn">Open</button> ` ;
h += ` </div> ` ;
h += ` </div> ` ;
}
// is_directoryPicker
else if ( options . is _directoryPicker ) {
h += ` <div class="window-filedialog-prompt"> ` ;
h += ` <div style="text-align:right;"> ` ;
h += ` <button class="button button-small filedialog-cancel-btn">Cancel</button> ` ;
h += ` <button class="button button-small button-primary directorypicker-select-btn" style="margin-left:10px;">Select</button> ` ;
h += ` </div> ` ;
h += ` </div> ` ;
}
h += ` </div> ` ;
// backdrop
if ( options . backdrop ) {
let backdrop _zindex ;
// backdrop should also cover over taskbar
let taskbar _zindex = $ ( '.taskbar' ) . css ( 'z-index' ) ;
if ( taskbar _zindex === null || taskbar _zindex === undefined )
backdrop _zindex = zindex ;
else {
taskbar _zindex = parseInt ( taskbar _zindex ) ;
backdrop _zindex = taskbar _zindex > zindex ? taskbar _zindex : zindex ;
}
h = ` <div class="window-backdrop" style="z-index: ${ backdrop _zindex } ;"> ` + h + ` </div> ` ;
}
// Append
$ ( el _body ) . append ( h ) ;
// disable_parent_window
if ( options . disable _parent _window && options . parent _uuid !== null ) {
const $el _parent _window = $ ( ` .window[data-element_uuid=" ${ options . parent _uuid } "] ` ) ;
const $el _parent _disable _mask = $el _parent _window . find ( '.window-disable-mask' ) ;
//disable parent window
$el _parent _window . addClass ( 'window-disabled' )
$el _parent _disable _mask . show ( ) ;
$el _parent _disable _mask . css ( 'z-index' , parseInt ( $el _parent _window . css ( 'z-index' ) ) + 1 ) ;
$el _parent _window . find ( 'iframe' ) . blur ( ) ;
}
// Add Taskbar Item
if ( ! options . is _openFileDialog && ! options . is _saveFileDialog && ! options . is _directoryPicker && options . show _in _taskbar ) {
// add icon if there is no similar app already open
if ( $ ( ` .taskbar-item[data-app=" ${ options . app } "] ` ) . length === 0 ) {
UITaskbarItem ( {
icon : options . icon ,
name : options . title ,
app : options . app ,
open _windows _count : 1 ,
onClick : function ( ) {
let open _window _count = parseInt ( $ ( ` .taskbar-item[data-app=" ${ options . app } "] ` ) . attr ( 'data-open-windows' ) ) ;
if ( open _window _count === 0 ) {
launch _app ( {
name : options . app ,
} )
} else {
return false ;
}
}
} ) ;
if ( options . app )
$ ( ` .taskbar-item[data-app=" ${ options . app } "] .active-taskbar-indicator ` ) . show ( ) ;
} else {
if ( options . app ) {
$ ( ` .taskbar-item[data-app=" ${ options . app } "] ` ) . attr ( 'data-open-windows' , parseInt ( $ ( ` .taskbar-item[data-app=" ${ options . app } "] ` ) . attr ( 'data-open-windows' ) ) + 1 ) ;
$ ( ` .taskbar-item[data-app=" ${ options . app } "] .active-taskbar-indicator ` ) . show ( ) ;
}
}
}
// if directory, set window_nav_history and window_nav_history_current_position
if ( options . is _dir ) {
window _nav _history [ win _id ] = [ options . path ] ;
window _nav _history _current _position [ win _id ] = 0 ;
}
// get all the elements needed
const el _window = document . querySelector ( ` #window- ${ win _id } ` ) ;
const el _window _head = document . querySelector ( ` #window- ${ win _id } > .window-head ` ) ;
const el _window _sidebar = document . querySelector ( ` #window- ${ win _id } > .window-sidebar ` ) ;
const el _window _head _title = document . querySelector ( ` #window- ${ win _id } > .window-head .window-head-title ` ) ;
const el _window _head _icon = document . querySelector ( ` #window- ${ win _id } > .window-head .window-head-icon ` ) ;
const el _window _head _scale _btn = document . querySelector ( ` #window- ${ win _id } > .window-head > .window-scale-btn ` ) ;
const el _window _navbar _back _btn = document . querySelector ( ` #window- ${ win _id } .window-navbar-btn-back ` ) ;
const el _window _navbar _forward _btn = document . querySelector ( ` #window- ${ win _id } .window-navbar-btn-forward ` ) ;
const el _window _navbar _up _btn = document . querySelector ( ` #window- ${ win _id } .window-navbar-btn-up ` ) ;
const el _window _body = document . querySelector ( ` #window- ${ win _id } > .window-body ` ) ;
const el _window _app _iframe = document . querySelector ( ` #window- ${ win _id } > .window-body > .window-app-iframe ` ) ;
const el _savefiledialog _filename = document . querySelector ( ` #window- ${ win _id } .savefiledialog-filename ` ) ;
const el _savefiledialog _save _btn = document . querySelector ( ` #window- ${ win _id } .savefiledialog-save-btn ` ) ;
const el _filedialog _cancel _btn = document . querySelector ( ` #window- ${ win _id } .filedialog-cancel-btn ` ) ;
const el _openfiledialog _open _btn = document . querySelector ( ` #window- ${ win _id } .openfiledialog-open-btn ` ) ;
const el _directorypicker _select _btn = document . querySelector ( ` #window- ${ win _id } .directorypicker-select-btn ` ) ;
if ( options . is _maximized ) {
// save original size and position
$ ( el _window ) . attr ( {
'data-left-before-maxim' : ( ( window . innerWidth / 2 - 680 / 2 ) + ( window . window _counter - 1 ) % 10 * 30 ) + 'px' ,
'data-top-before-maxim' : default _window _top ,
'data-width-before-maxim' : '680px' ,
'data-height-before-maxim' : '350px' ,
'data-is_maximized' : '1' ,
} ) ;
// shrink icon
$ ( el _window ) . find ( '.window-scale-btn>img' ) . attr ( 'src' , window . icons [ 'scale-down-3.svg' ] ) ;
// set new size and position
$ ( el _window ) . css ( {
'top' : window . toolbar _height + 'px' ,
'left' : '0' ,
'width' : '100%' ,
'height' : ` calc(100% - ${ window . taskbar _height + window . toolbar _height + 1 } px) ` ,
'transform' : 'none' ,
} ) ;
}
// when a window is created, focus is brought to it and
// therefore it is the current active element
window . active _element = el _window ;
// set name
$ ( el _window _head _title ) . html ( html _encode ( options . title ) ) ;
// set icon
if ( options . icon )
$ ( el _window _head _icon ) . attr ( 'src' , options . icon . image ? ? options . icon ) ;
// root folder of a shared user?
if ( options . is _dir && ( options . path . split ( '/' ) . length - 1 ) === 1 && options . path !== '/' + window . user . username ) {
$ ( el _window _head _icon ) . attr ( 'src' , window . icons [ 'shared.svg' ] ) ;
}
// focus on this window and deactivate other windows
$ ( el _window ) . focusWindow ( ) ;
if ( animate _window _opening ) {
// animate window opening
$ ( el _window ) . css ( {
'opacity' : '0' ,
'transition' : 'opacity 70ms ease-in-out' ,
} ) ;
// Use requestAnimationFrame to schedule a function to run at the next repaint of the browser window
requestAnimationFrame ( ( ) => {
// Change the window's opacity to 1 and scale to 1 to create an opening effect
$ ( el _window ) . css ( {
'opacity' : '1' ,
} )
// Set a timeout to run after the transition duration (100ms)
setTimeout ( function ( ) {
// Remove the transition property, so future CSS changes won't be animated
$ ( el _window ) . css ( {
'transition' : 'none' ,
} )
} , 70 ) ;
} ) ;
}
// onAppend() - using show() is a hack to make sure window is visible AND onAppend is called when
// window is actually appended and usable.
$ ( el _window ) . show ( 0 , function ( e ) {
// if SaveFileDialog, bring focus to the el_savefiledialog_filename and select all
if ( options . is _saveFileDialog ) {
let item _name = el _savefiledialog _filename . value ;
const extname = path . extname ( '/' + item _name ) ;
if ( extname !== '' )
el _savefiledialog _filename . setSelectionRange ( 0 , item _name . length - extname . length )
else
$ ( el _savefiledialog _filename ) . select ( ) ;
$ ( el _savefiledialog _filename ) . get ( 0 ) . focus ( { preventScroll : true } ) ;
}
//set custom window css
$ ( el _window ) . css ( options . window _css ) ;
// onAppend()
if ( options . onAppend && typeof options . onAppend === 'function' ) {
options . onAppend ( el _window ) ;
}
} )
if ( options . is _saveFileDialog ) {
//------------------------------------------------
// SaveFileDialog > Save button
//------------------------------------------------
$ ( el _savefiledialog _save _btn ) . on ( 'click' , function ( e ) {
const filename = $ ( el _savefiledialog _filename ) . val ( ) ;
try {
validate _fsentry _name ( filename )
} catch ( err ) {
UIAlert ( err . message , 'error' , 'OK' )
return ;
}
const target _path = path . join ( $ ( el _window ) . attr ( 'data-path' ) , filename ) ;
if ( options . onSaveFileDialogSave && typeof options . onSaveFileDialogSave === 'function' )
options . onSaveFileDialogSave ( target _path , el _window )
} )
//------------------------------------------------
// SaveFileDialog > Enter
//------------------------------------------------
$ ( el _savefiledialog _filename ) . on ( 'keypress' , function ( event ) {
if ( event . which === 13 ) {
$ ( el _savefiledialog _save _btn ) . trigger ( 'click' ) ;
}
} )
//------------------------------------------------
// Enable/disable Save button based on input
//------------------------------------------------
$ ( el _savefiledialog _filename ) . bind ( 'keydown change input paste' , function ( ) {
if ( $ ( this ) . val ( ) !== '' )
$ ( el _savefiledialog _save _btn ) . removeClass ( 'disabled' ) ;
else
$ ( el _savefiledialog _save _btn ) . addClass ( 'disabled' ) ;
} )
$ ( el _savefiledialog _filename ) . get ( 0 ) . focus ( { preventScroll : true } ) ;
}
if ( options . is _openFileDialog ) {
//------------------------------------------------
// OpenFileDialog > Open button
//------------------------------------------------
$ ( el _openfiledialog _open _btn ) . on ( 'click' , async function ( e ) {
const selected _els = $ ( el _window ) . find ( '.item-selected[data-is_dir="0"]' ) ;
let selected _files ;
// No item selected
if ( selected _els . length === 0 )
return ;
// ------------------------------------------------
// Item(s) selected
// ------------------------------------------------
else {
selected _files = [ ]
// an array that hold the items to sign
const items _to _sign = [ ] ;
// prepare items to sign
for ( let i = 0 ; i < selected _els . length ; i ++ )
items _to _sign . push ( { uid : $ ( selected _els [ i ] ) . attr ( 'data-uid' ) , action : 'write' , path : $ ( selected _els [ i ] ) . attr ( 'data-path' ) } ) ;
// sign items
selected _files = await puter . fs . sign ( options . initiating _app _uuid , items _to _sign ) ;
selected _files = selected _files . items ;
selected _files = Array . isArray ( selected _files ) ? selected _files : [ selected _files ] ;
// change path of each item to preserve privacy
for ( let i = 0 ; i < selected _files . length ; i ++ )
selected _files [ i ] . path = ` ~/ ` + selected _files [ i ] . path . split ( '/' ) . slice ( 2 ) . join ( '/' ) ;
}
const ifram _msg _uid = $ ( el _window ) . attr ( 'data-iframe_msg_uid' ) ;
if ( options . return _to _parent _window ) {
window . opener . postMessage ( {
msg : "fileOpenPicked" ,
original _msg _id : ifram _msg _uid ,
items : Array . isArray ( selected _files ) ? [ ... selected _files ] : [ selected _files ] ,
// 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
... ( selected _files . length === 1 && selected _files [ 0 ] )
} , '*' ) ;
window . close ( ) ;
window . open ( '' , '_self' ) . close ( ) ;
}
else if ( options . parent _uuid ) {
// send event to iframe
const target _iframe = $ ( ` .window[data-element_uuid=" ${ options . parent _uuid } "] ` ) . find ( '.window-app-iframe' ) . get ( 0 ) ;
if ( target _iframe ) {
target _iframe . contentWindow . postMessage ( {
msg : "fileOpenPicked" ,
original _msg _id : ifram _msg _uid ,
items : Array . isArray ( selected _files ) ? [ ... selected _files ] : [ selected _files ] ,
// 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
... ( selected _files . length === 1 && selected _files [ 0 ] )
} , '*' ) ;
}
// focus on iframe
$ ( target _iframe ) . get ( 0 ) ? . focus ( { preventScroll : true } ) ;
// send file_opened event
const file _opened _event = new CustomEvent ( 'file_opened' , { detail : Array . isArray ( selected _files ) ? [ ... selected _files ] : [ selected _files ] } ) ;
// dispatch event to parent window
$ ( ` .window[data-element_uuid=" ${ options . parent _uuid } "] ` ) . get ( 0 ) ? . dispatchEvent ( file _opened _event ) ;
$ ( el _window ) . close ( ) ;
}
} )
}
else if ( options . is _directoryPicker ) {
//------------------------------------------------
// DirectoryPicker > Select button
//------------------------------------------------
$ ( el _directorypicker _select _btn ) . on ( 'click' , async function ( e ) {
const selected _els = $ ( el _window ) . find ( '.item-selected[data-is_dir="1"]' ) ;
let selected _dirs ;
// ------------------------------------------------
// No item selected, return current directory
// ------------------------------------------------
if ( selected _els . length === 0 ) {
selected _dirs = await puter . fs . sign ( options . initiating _app _uuid , { uid : $ ( el _window ) . attr ( 'data-uid' ) , action : 'write' } )
selected _dirs = selected _dirs . items ;
}
// ------------------------------------------------
// directorie(s) selected
// ------------------------------------------------
else {
selected _dirs = [ ]
// an array that hold the items to sign
const items _to _sign = [ ] ;
// prepare items to sign
for ( let i = 0 ; i < selected _els . length ; i ++ )
items _to _sign . push ( { uid : $ ( selected _els [ i ] ) . attr ( 'data-uid' ) , action : 'write' , path : $ ( selected _els [ i ] ) . attr ( 'data-path' ) } ) ;
// sign items
selected _dirs = await puter . fs . sign ( options . initiating _app _uuid , items _to _sign ) ;
selected _dirs = selected _dirs . items ;
selected _dirs = Array . isArray ( selected _dirs ) ? selected _dirs : [ selected _dirs ] ;
// change path of each item to preserve privacy
for ( let i = 0 ; i < selected _dirs . length ; i ++ )
selected _dirs [ i ] . path = ` ~/ ` + selected _dirs [ i ] . path . split ( '/' ) . slice ( 2 ) . join ( '/' ) ;
}
const ifram _msg _uid = $ ( el _window ) . attr ( 'data-iframe_msg_uid' ) ;
if ( options . return _to _parent _window ) {
window . opener . postMessage ( {
msg : "directoryPicked" ,
original _msg _id : ifram _msg _uid ,
items : Array . isArray ( selected _dirs ) ? [ ... selected _dirs ] : [ selected _dirs ] ,
// 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
... ( selected _dirs . length === 1 && selected _dirs [ 0 ] )
} , '*' ) ;
window . close ( ) ;
window . open ( '' , '_self' ) . close ( ) ;
}
if ( options . parent _uuid ) {
// Send directoryPicked event to iframe
const target _iframe = $ ( ` .window[data-element_uuid=" ${ options . parent _uuid } "] ` ) . find ( '.window-app-iframe' ) . get ( 0 ) ;
if ( target _iframe ) {
target _iframe . contentWindow . postMessage ( {
msg : "directoryPicked" ,
original _msg _id : ifram _msg _uid ,
items : Array . isArray ( selected _dirs ) ? [ ... selected _dirs ] : [ selected _dirs ] ,
} , '*' ) ;
}
$ ( target _iframe ) . get ( 0 ) . focus ( { preventScroll : true } ) ;
$ ( el _window ) . close ( ) ;
}
} )
}
if ( options . is _saveFileDialog || options . is _openFileDialog || options . is _directoryPicker ) {
//------------------------------------------------
// FileDialog > Cancel button
//------------------------------------------------
$ ( el _filedialog _cancel _btn ) . on ( 'click' , function ( e ) {
if ( options . return _to _parent _window ) {
window . close ( ) ;
window . open ( '' , '_self' ) . close ( ) ;
}
$ ( el _window ) . hide ( 0 , ( ) => {
// re-anable parent window
$ ( ` .window[data-element_uuid=" ${ options . parent _uuid } "] ` ) . removeClass ( 'window-disabled' ) ;
$ ( ` .window[data-element_uuid=" ${ options . parent _uuid } "] ` ) . find ( '.window-disable-mask' ) . hide ( ) ;
$ ( el _window ) . close ( ) ;
} )
} )
}
if ( options . is _dir ) {
navbar _path _droppable ( el _window ) ;
sidebar _item _droppable ( el _window ) ;
// --------------------------------------------------------
// Back button
// --------------------------------------------------------
$ ( el _window _navbar _back _btn ) . on ( 'click' , function ( e ) {
// if history menu is open don't continue
if ( $ ( el _window _navbar _back _btn ) . hasClass ( 'has-open-contextmenu' ) )
return ;
// if ctrl/cmd are pressed, open in new window
if ( e . ctrlKey || e . metaKey ) {
const dirpath = window _nav _history [ win _id ] . at ( window _nav _history _current _position [ win _id ] - 1 ) ;
UIWindow ( {
path : dirpath ,
title : dirpath === '/' ? root _dirname : path . basename ( dirpath ) ,
icon : window . icons [ 'folder.svg' ] ,
// uid: $(el_item).attr('data-uid'),
is _dir : true ,
} ) ;
}
// ... otherwise, open in same window
else {
window _nav _history _current _position [ win _id ] > 0 && window _nav _history _current _position [ win _id ] -- ;
const new _path = window _nav _history [ win _id ] . at ( window _nav _history _current _position [ win _id ] ) ;
// update window path
update _window _path ( el _window , new _path ) ;
}
} )
// --------------------------------------------------------
// Back button click-hold
// --------------------------------------------------------
$ ( el _window _navbar _back _btn ) . on ( 'taphold' , function ( ) {
let items = [ ] ;
const pos = el _window _navbar _back _btn . getBoundingClientRect ( ) ;
for ( let index = window _nav _history _current _position [ win _id ] - 1 ; index >= 0 ; index -- ) {
const history _item = window _nav _history [ win _id ] . at ( index ) ;
// build item for context menu
items . push ( {
html : ` <span> ${ history _item === window . home _path ? 'Home' : path . basename ( history _item ) } </span> ` ,
val : index ,
onClick : async function ( e ) {
let history _index = e . value ;
window _nav _history _current _position [ win _id ] = history _index ;
const new _path = window _nav _history [ win _id ] . at ( window _nav _history _current _position [ win _id ] ) ;
// if ctrl/cmd are pressed, open in new window
if ( e . ctrlKey || e . metaKey && ( new _path !== undefined && new _path !== null ) ) {
UIWindow ( {
path : new _path ,
title : new _path === '/' ? root _dirname : path . basename ( new _path ) ,
icon : window . icons [ 'folder.svg' ] ,
is _dir : true ,
} ) ;
}
// update window path
else {
update _window _path ( el _window , new _path ) ;
}
}
} )
}
// Menu
UIContextMenu ( {
position : { top : pos . top + pos . height + 3 , left : pos . left } ,
parent _element : el _window _navbar _back _btn ,
items : items ,
} )
} )
// --------------------------------------------------------
// Forward button
// --------------------------------------------------------
$ ( el _window _navbar _forward _btn ) . on ( 'click' , function ( e ) {
// if history menu is open don't continue
if ( $ ( el _window _navbar _forward _btn ) . hasClass ( 'has-open-contextmenu' ) )
return ;
// if ctrl/cmd are pressed, open in new window
if ( e . ctrlKey || e . metaKey ) {
const dirpath = window _nav _history [ win _id ] . at ( window _nav _history _current _position [ win _id ] + 1 ) ;
UIWindow ( {
path : dirpath ,
title : dirpath === '/' ? root _dirname : path . basename ( dirpath ) ,
icon : window . icons [ 'folder.svg' ] ,
// uid: $(el_item).attr('data-uid'),
is _dir : true ,
} ) ;
}
// ... otherwise, open in same window
else {
window _nav _history _current _position [ win _id ] ++ ;
// get last path in history
const target _path = window _nav _history [ win _id ] . at ( window _nav _history _current _position [ win _id ] ) ;
// update window path
if ( target _path !== undefined ) {
update _window _path ( el _window , target _path ) ;
}
}
} )
// --------------------------------------------------------
// forward button click-hold
// --------------------------------------------------------
$ ( el _window _navbar _forward _btn ) . on ( 'taphold' , function ( ) {
let items = [ ] ;
const pos = el _window _navbar _forward _btn . getBoundingClientRect ( ) ;
for ( let index = window _nav _history _current _position [ win _id ] + 1 ; index < window _nav _history [ win _id ] . length ; index ++ ) {
const history _item = window _nav _history [ win _id ] . at ( index ) ;
// build item for context menu
items . push ( {
html : ` <span> ${ history _item === window . home _path ? 'Home' : path . basename ( history _item ) } </span> ` ,
val : index ,
onClick : async function ( e ) {
let history _index = e . value ;
window _nav _history _current _position [ win _id ] = history _index ;
const new _path = window _nav _history [ win _id ] . at ( window _nav _history _current _position [ win _id ] ) ;
// if ctrl/cmd are pressed, open in new window
if ( e . ctrlKey || e . metaKey && ( new _path !== undefined && new _path !== null ) ) {
UIWindow ( {
path : new _path ,
title : new _path === '/' ? root _dirname : path . basename ( new _path ) ,
icon : window . icons [ 'folder.svg' ] ,
is _dir : true ,
} ) ;
}
// update window path
else {
update _window _path ( el _window , new _path ) ;
}
}
} )
}
// Menu
UIContextMenu ( {
parent _element : el _window _navbar _forward _btn ,
position : { top : pos . top + pos . height + 3 , left : pos . left } ,
items : items ,
} )
} )
// --------------------------------------------------------
// Up button
// --------------------------------------------------------
$ ( el _window _navbar _up _btn ) . on ( 'click' , function ( e ) {
const target _path = path . resolve ( path . join ( $ ( el _window ) . attr ( 'data-path' ) , '..' ) ) ;
// if ctrl/cmd are pressed, open in new window
if ( e . ctrlKey || e . metaKey && ( target _path !== undefined && target _path !== null ) ) {
UIWindow ( {
path : target _path ,
title : target _path === '/' ? root _dirname : path . basename ( target _path ) ,
icon : window . icons [ 'folder.svg' ] ,
// uid: $(el_item).attr('data-uid'),
is _dir : true ,
} ) ;
}
// ... otherwise, open in same window
else if ( target _path !== undefined && target _path !== null ) {
// update history
window _nav _history [ win _id ] = window _nav _history [ win _id ] . slice ( 0 , window _nav _history _current _position [ win _id ] + 1 ) ;
window _nav _history [ win _id ] . push ( target _path ) ;
window _nav _history _current _position [ win _id ] ++ ;
// update window path
update _window _path ( el _window , target _path ) ;
}
} )
const layouts = [ 'icons' , 'list' , 'details' ] ;
$ ( el _window ) . find ( '.window-navbar-layout-settings' ) . on ( 'contextmenu taphold' , function ( ) {
let cur _layout = $ ( el _window ) . attr ( 'data-layout' ) ;
let items = [ ] ;
for ( let i = 0 ; i < layouts . length ; i ++ ) {
items . push ( {
html : ` <span style="text-transform: capitalize;"> ${ layouts [ i ] } </span> ` ,
icon : cur _layout === layouts [ i ] ? '✓' : '' ,
onClick : async function ( e ) {
update _window _layout ( el _window , layouts [ i ] ) ;
window . set _layout ( $ ( el _window ) . attr ( 'data-uid' ) , layouts [ i ] ) ;
}
} )
}
UIContextMenu ( {
parent _element : this ,
items : items ,
} )
} )
$ ( el _window ) . find ( '.window-navbar-layout-settings' ) . on ( 'click' , function ( ) {
let cur _layout = $ ( el _window ) . attr ( 'data-layout' ) ;
for ( let i = 0 ; i < layouts . length ; i ++ ) {
if ( cur _layout === layouts [ i ] ) {
if ( i === layouts . length - 1 ) {
update _window _layout ( el _window , layouts [ 0 ] ) ;
window . set _layout ( $ ( el _window ) . attr ( 'data-uid' ) , layouts [ 0 ] ) ;
} else {
update _window _layout ( el _window , layouts [ i + 1 ] ) ;
window . set _layout ( $ ( el _window ) . attr ( 'data-uid' ) , layouts [ i + 1 ] ) ;
}
break ;
}
}
} )
// --------------------------------------------------------
// directory content
// --------------------------------------------------------
//auth
if ( ! is _auth ( ) && ! ( await UIWindowLogin ( ) ) )
return ;
// get directory content
refresh _item _container ( el _window _body , options ) ;
}
// set iframe url
if ( options . iframe _url ) {
$ ( el _window _app _iframe ) . attr ( 'src' , options . iframe _url )
//bring focus to iframe
el _window _app _iframe . contentWindow . focus ( ) ;
}
// set the position of window
if ( ! options . is _maximized ) {
$ ( el _window ) . css ( 'top' , options . top )
$ ( el _window ) . css ( 'left' , options . left )
}
$ ( el _window ) . css ( 'display' , 'block' ) ;
// mousedown on the window body will unselect selected items if neither ctrl nor command are pressed
$ ( el _window _body ) . on ( 'mousedown' , function ( e ) {
if ( $ ( e . target ) . hasClass ( 'window-body' ) && ! e . ctrlKey && ! e . metaKey ) {
$ ( el _window _body ) . find ( '.item-selected' ) . removeClass ( 'item-selected' ) ;
update _explorer _footer _selected _items _count ( el _window ) ;
// if this is openFileDialog, disable the Open button
if ( options . is _openFileDialog )
$ ( el _openfiledialog _open _btn ) . addClass ( 'disabled' )
}
} )
// on_close event
$ ( el _window ) . on ( 'remove' , function ( e ) {
// if on_close callback is set, call it
options . on _close ? . ( ) ;
} )
// --------------------------------------------------------
// Backdrop click
// --------------------------------------------------------
if ( options . backdrop && options . close _on _backdrop _click ) {
$ ( el _window ) . closest ( '.window-backdrop' ) . on ( 'mousedown' , function ( e ) {
if ( $ ( e . target ) . hasClass ( 'window-backdrop' ) ) {
$ ( el _window ) . close ( ) ;
}
} )
}
// --------------------------------------------------------
// Selectable
// only for Desktop screens
// --------------------------------------------------------
if ( options . is _dir && options . selectable _body && ! isMobile . phone && ! isMobile . tablet ) {
let selected _ctrl _items = [ ] ;
// init viselect
const selection = new SelectionArea ( {
selectionContainerClass : '.selection-area-container' ,
container : ` #window-body- ${ win _id } ` ,
selectables : [ ` #window-body- ${ win _id } .item ` ] ,
startareas : [ ` #window-body- ${ win _id } ` ] ,
boundaries : [ ` #window-body- ${ win _id } ` ] ,
behaviour : {
overlap : 'drop' ,
intersect : 'touch' ,
startThreshold : 10 ,
scrolling : {
speedDivider : 10 ,
manualSpeed : 750 ,
startScrollMargins : { x : 0 , y : 0 }
}
} ,
features : {
touch : true ,
range : true ,
singleTap : {
allow : true ,
intersect : 'native'
}
}
} ) ;
selection . on ( 'beforestart' , ( { store , event } ) => {
selected _ctrl _items = [ ] ;
return $ ( event . target ) . is ( ` #window-body- ${ win _id } ` )
} )
. on ( 'beforedrag' , evt => {
} )
. on ( 'start' , ( { store , event } ) => {
if ( ! event . ctrlKey && ! event . metaKey ) {
for ( const el of store . stored ) {
el . classList . remove ( 'item-selected' ) ;
}
selection . clearSelection ( ) ;
}
} )
. on ( 'move' , ( { store : { changed : { added , removed } } , event } ) => {
for ( const el of added ) {
// if ctrl or meta key is pressed and the item is already selected, then unselect it
if ( ( event . ctrlKey || event . metaKey ) && $ ( el ) . hasClass ( 'item-selected' ) ) {
el . classList . remove ( 'item-selected' ) ;
selected _ctrl _items . push ( el ) ;
}
// otherwise select it
else {
el . classList . add ( 'item-selected' ) ;
// the latest selected item is the active element
active _element = el ;
}
}
for ( const el of removed ) {
el . classList . remove ( 'item-selected' ) ;
// in case this item was selected by ctrl+click before, then reselect it again
if ( selected _ctrl _items . includes ( el ) )
$ ( el ) . addClass ( 'item-selected' ) ;
}
update _explorer _footer _selected _items _count ( el _window ) ;
// If this is openFileDialog, enable/disable the Open button accordingly
if ( options . is _openFileDialog && $ ( el _window ) . find ( '.item-selected' ) . length )
$ ( el _openfiledialog _open _btn ) . removeClass ( 'disabled' )
else
$ ( el _openfiledialog _open _btn ) . addClass ( 'disabled' )
} )
. on ( 'stop' , ( { store , event } ) => {
// If this is openFileDialog, enable/disable the Open button accordingly
if ( options . is _openFileDialog && $ ( el _window ) . find ( '.item-selected' ) . length )
$ ( el _openfiledialog _open _btn ) . removeClass ( 'disabled' )
else
$ ( el _openfiledialog _open _btn ) . addClass ( 'disabled' )
} ) ;
}
// --------------------------------------------------------
// Droppable
// --------------------------------------------------------
$ ( el _window _body ) . droppable ( {
accept : '.item' ,
greedy : true ,
tolerance : "pointer" ,
drop : async function ( e , ui ) {
// check if item was actually dropped on this window
if ( $ ( mouseover _window ) . attr ( 'data-id' ) !== $ ( el _window ) . attr ( 'data-id' ) )
return ;
// can't drop anything here but a UIItem
if ( ! $ ( ui . draggable ) . hasClass ( 'item' ) )
return ;
// --------------------------------------------------
// In case this was dropped on an App window
// --------------------------------------------------
if ( el _window _app _iframe !== null ) {
const items _to _move = [ ]
// first item
items _to _move . push ( ui . draggable ) ;
// all subsequent items
const cloned _items = document . getElementsByClassName ( 'item-selected-clone' ) ;
for ( let i = 0 ; i < cloned _items . length ; i ++ ) {
const source _item = document . getElementById ( 'item-' + $ ( cloned _items [ i ] ) . attr ( 'data-id' ) ) ;
if ( source _item !== null )
items _to _move . push ( source _item ) ;
}
// sign all items
const items _to _sign = [ ]
// prepare items to sign
for ( let i = 0 ; i < items _to _move . length ; i ++ )
items _to _sign . push ( { uid : $ ( items _to _move [ i ] ) . attr ( 'data-uid' ) , action : 'write' , path : $ ( items _to _move [ i ] ) . attr ( 'data-path' ) } ) ;
// sign items
let signatures = await puter . fs . sign ( options . app _uuid , items _to _sign ) ;
signatures = signatures . items ;
signatures = Array . isArray ( signatures ) ? signatures : [ signatures ] ;
// prepare items
let items = [ ] ;
for ( let index = 0 ; index < signatures . length ; index ++ ) {
const item = signatures [ index ] ;
items . push ( {
name : item . fsentry _name ,
readURL : item . read _url ,
writeURL : item . write _url ,
metadataURL : item . metadata _url ,
isDirectory : item . fsentry _is _dir ,
path : ` ~/ ` + item . path . split ( '/' ) . slice ( 2 ) . join ( '/' ) ,
uid : item . uid ,
} )
}
// send to app iframe
el _window _app _iframe . contentWindow . postMessage ( {
msg : "itemsOpened" ,
original _msg _id : $ ( el _window ) . attr ( 'data-iframe_msg_uid' ) ,
items : items ,
} , '*' ) ;
// if item is dragged over an app iframe, highlight the iframe
var rect = el _window _app _iframe . getBoundingClientRect ( ) ;
// if mouse is inside iframe, send drag message to iframe
el _window _app _iframe . contentWindow . postMessage ( { msg : "drop" , x : ( mouseX - rect . left ) , y : ( mouseY - rect . top ) , items : items } , '*' ) ;
// bring focus to this window
$ ( el _window ) . focusWindow ( ) ;
}
// if this window is not a directory, cancel drop.
// why not simply only launch droppable on directories? this is because
// if a window is not droppable and an item is dropped on it, the app will think
// it was dropped on desktop.
if ( ! options . is _dir ) {
return false ;
}
// If dropped on the same window, do not proceed
if ( $ ( ui . draggable ) . closest ( '.item-container' ) . attr ( 'data-path' ) === $ ( mouseover _window ) . attr ( 'data-path' ) && ! e . ctrlKey ) {
return ;
}
// If ctrl is pressed and source is Trashed, cancel whole operation
if ( e . ctrlKey && path . dirname ( $ ( ui . draggable ) . attr ( 'data-path' ) ) === window . trash _path )
return ;
// Unselect already selected items
$ ( el _window _body ) . find ( '.item-selected' ) . removeClass ( 'item-selected' )
const items _to _move = [ ]
// first item
items _to _move . push ( ui . draggable ) ;
// all subsequent items
const cloned _items = document . getElementsByClassName ( 'item-selected-clone' ) ;
for ( let i = 0 ; i < cloned _items . length ; i ++ ) {
const source _item = document . getElementById ( 'item-' + $ ( cloned _items [ i ] ) . attr ( 'data-id' ) ) ;
if ( source _item !== null ) {
items _to _move . push ( source _item ) ;
}
}
// If ctrl key is down, copy items. Except if target is Trash
if ( e . ctrlKey && $ ( mouseover _window ) . attr ( 'data-path' ) !== window . trash _path ) {
// Copy items
copy _items ( items _to _move , $ ( mouseover _window ) . attr ( 'data-path' ) )
}
// if alt key is down, create shortcut items
else if ( e . altKey ) {
items _to _move . forEach ( ( item _to _move ) => {
create _shortcut (
path . basename ( $ ( item _to _move ) . attr ( 'data-path' ) ) ,
$ ( item _to _move ) . attr ( 'data-is_dir' ) === '1' ,
$ ( mouseover _window ) . attr ( 'data-path' ) ,
null ,
$ ( item _to _move ) . attr ( 'data-shortcut_to' ) === '' ? $ ( item _to _move ) . attr ( 'data-uid' ) : $ ( item _to _move ) . attr ( 'data-shortcut_to' ) ,
$ ( item _to _move ) . attr ( 'data-shortcut_to_path' ) === '' ? $ ( item _to _move ) . attr ( 'data-path' ) : $ ( item _to _move ) . attr ( 'data-shortcut_to_path' ) ,
) ;
} ) ;
}
// otherwise, move items
else {
move _items ( items _to _move , $ ( mouseover _window ) . attr ( 'data-path' ) ) ;
}
} ,
over : function ( event , ui ) {
// Don't do anything if the dragged item is NOT a UIItem
if ( ! $ ( ui . draggable ) . hasClass ( 'item' ) )
return ;
} ,
out : function ( event , ui ) {
// Don't do anything if the dragged item is NOT a UIItem
if ( ! $ ( ui . draggable ) . hasClass ( 'item' ) )
return ;
}
} ) ;
// --------------------------------------------------------
// Double Click on Head
// double click on a window head will maximize or shrink window
// only maximize/shrink if window is marked `is_resizable`
// --------------------------------------------------------
if ( options . is _resizable ) {
$ ( el _window _head ) . dblclick ( function ( ) {
scale _window ( el _window ) ;
} )
}
$ ( el _window _head ) . mousedown ( function ( ) {
if ( window _is _snapped ) {
$ ( el _window ) . draggable ( "option" , "cursorAt" , { left : width _before _snap / 2 } ) ;
}
} )
// --------------------------------------------------------
// Click On The `Scale` Button
// (the little rectangle in the window head)
// --------------------------------------------------------
if ( options . is _resizable ) {
$ ( el _window _head _scale _btn ) . click ( function ( ) {
scale _window ( el _window ) ;
} )
}
// --------------------------------------------------------
// Dragster
// If a local item is dragged over this window, bring it to front
// --------------------------------------------------------
let drag _enter _timeout ;
$ ( el _window ) . dragster ( {
enter : function ( dragsterEvent , event ) {
// make sure to cancel any previous timeouts otherwise the window will be brought to front multiple times
clearTimeout ( drag _enter _timeout ) ;
// If items are dragged over this window long enough, bring it to front
drag _enter _timeout = setTimeout ( function ( ) {
// focus window
$ ( el _window ) . focusWindow ( ) ;
} , 1400 ) ;
} ,
leave : function ( dragsterEvent , event ) {
// cancel the timeout for 'bringing window to front'
clearTimeout ( drag _enter _timeout ) ;
} ,
drop : function ( dragsterEvent , event ) {
// cancel the timeout for 'bringing window to front'
clearTimeout ( drag _enter _timeout ) ;
} ,
over : function ( dragsterEvent , event ) {
// cancel the timeout for 'bringing window to front'
clearTimeout ( drag _enter _timeout ) ;
}
} ) ;
// --------------------------------------------------------
// Dragster
// Allow dragging of local files onto this window, if it's is_dir
// --------------------------------------------------------
$ ( el _window _body ) . dragster ( {
enter : function ( dragsterEvent , event ) {
if ( options . is _dir ) {
// remove any context menu that might be open
$ ( '.context-menu' ) . remove ( ) ;
// highlight this item container
$ ( el _window ) . find ( '.item-container' ) . addClass ( 'item-container-active' ) ;
}
} ,
leave : function ( dragsterEvent , event ) {
if ( options . is _dir ) {
$ ( el _window ) . find ( '.item-container' ) . removeClass ( 'item-container-active' ) ;
}
} ,
drop : function ( dragsterEvent , event ) {
const e = event . originalEvent ;
if ( options . is _dir ) {
// if files were dropped...
if ( e . dataTransfer ? . items ? . length > 0 ) {
upload _items ( e . dataTransfer . items , $ ( el _window ) . attr ( 'data-path' ) )
}
// de-highlight all windows
$ ( '.item-container' ) . removeClass ( 'item-container-active' ) ;
}
e . stopPropagation ( ) ;
e . preventDefault ( ) ;
return false ;
}
} ) ;
// --------------------------------------------------------
// Close button
// --------------------------------------------------------
$ ( ` #window- ${ win _id } > .window-head > .window-close-btn ` ) . click ( function ( ) {
$ ( el _window ) . close ( {
shrink _to _target : options . on _close _shrink _to _target
} ) ;
} )
// --------------------------------------------------------
// Minimize button
// --------------------------------------------------------
$ ( ` #window- ${ win _id } > .window-head > .window-minimize-btn ` ) . click ( function ( ) {
$ ( el _window ) . hideWindow ( ) ;
} )
// --------------------------------------------------------
// Draggable
// --------------------------------------------------------
let width _before _snap = 0 ;
let height _before _snap = 0 ;
let window _is _snapped = false ;
let snap _placeholder _active = false ;
let snap _trigger _timeout ;
if ( options . is _draggable ) {
let window _snap _placeholder = $ (
` <div class="window-snap-placeholder animate__animated animate__zoomIn animate__faster">
< div class = "window-snap-placeholder-inner" > < / d i v >
< / d i v > `
) ;
$ ( el _window ) . draggable ( {
start : function ( e , ui ) {
// if window is snapped, unsnap it and reset its position to where it was before snapping
if ( options . is _resizable && window _is _snapped ) {
window _is _snapped = false ;
$ ( el _window ) . css ( {
'width' : width _before _snap ,
'height' : height _before _snap + 'px' ,
} ) ;
// if at any point the window's width is "too small", hide the sidebar
if ( $ ( el _window ) . width ( ) < window _width _threshold _for _sidebar ) {
if ( width _before _snap >= window _width _threshold _for _sidebar && ! sidebar _hidden ) {
$ ( el _window _sidebar ) . hide ( ) ;
}
sidebar _hidden = true ;
}
// if at any point the window's width is "big enough", show the sidebar
else if ( $ ( el _window ) . width ( ) >= window _width _threshold _for _sidebar ) {
if ( sidebar _hidden ) {
$ ( el _window _sidebar ) . show ( ) ;
}
sidebar _hidden = false ;
}
}
$ ( el _window ) . addClass ( 'window-dragging' ) ;
2024-03-19 21:59:48 +08:00
// rm window from original_window_position
window . original _window _position [ $ ( el _window ) . attr ( 'id' ) ] = undefined ;
2024-03-03 10:39:14 +08:00
// since jquery draggable sets the z-index automatically we need this to
// bring windows to the front when they are clicked.
last _window _zindex = parseInt ( $ ( el _window ) . css ( 'z-index' ) ) ;
//transform causes draggable to start inaccurately
$ ( el _window ) . css ( 'transform' , 'none' ) ;
} ,
drag : function ( e , ui ) {
$ ( el _window _app _iframe ) . css ( 'pointer-events' , 'none' ) ;
$ ( '.window' ) . css ( 'pointer-events' , 'none' ) ;
// jqueryui changes the z-index automatically, if the stay_on_top flag is set
// make sure window stays on top
$ ( ` .window[data-stay_on_top="true"] ` ) . css ( 'z-index' , 999999999 )
if ( $ ( el _window ) . attr ( 'data-is_maximized' ) === '1' ) {
$ ( el _window ) . attr ( 'data-is_maximized' , '0' ) ;
// maximize icon
$ ( el _window _head _scale _btn ) . find ( 'img' ) . attr ( 'src' , window . icons [ 'scale.svg' ] ) ;
}
// --------------------------------------------------------
// Snap to screen edges
// --------------------------------------------------------
if ( options . is _resizable ) {
clearTimeout ( snap _trigger _timeout ) ;
// if window is not snapped, check if it should be snapped
snap _trigger _timeout = setTimeout ( function ( ) {
// if cursor is not in a snap zone, don't snap
if ( ! current _active _snap _zone ) {
return ;
}
// if dragging has stopped by now, don't snap
if ( ! $ ( el _window ) . hasClass ( 'window-dragging' ) ) {
return ;
}
// W
if ( ! window _is _snapped && current _active _snap _zone === 'w' ) {
window _snap _placeholder . css ( {
'display' : 'block' ,
'width' : '50%' ,
'height' : desktop _height ,
'top' : toolbar _height ,
'left' : 0 ,
'z-index' : last _window _zindex - 1 ,
} )
}
// NW
else if ( ! window _is _snapped && current _active _snap _zone === 'nw' ) {
window _snap _placeholder . css ( {
'display' : 'block' ,
'width' : '50%' ,
'height' : desktop _height / 2 ,
'top' : toolbar _height ,
'left' : 0 ,
'z-index' : last _window _zindex - 1 ,
} )
}
// NE
else if ( ! window _is _snapped && current _active _snap _zone === 'ne' ) {
window _snap _placeholder . css ( {
'display' : 'block' ,
'width' : '50%' ,
'height' : desktop _height / 2 ,
'top' : toolbar _height ,
'left' : desktop _width / 2 ,
'z-index' : last _window _zindex - 1 ,
} )
}
// E
else if ( ! window _is _snapped && current _active _snap _zone === 'e' ) {
window _snap _placeholder . css ( {
'display' : 'block' ,
'width' : '50%' ,
'height' : desktop _height ,
'top' : toolbar _height ,
'left' : 'initial' ,
'right' : 0 ,
'z-index' : last _window _zindex - 1 ,
} )
}
// N
else if ( ! window _is _snapped && current _active _snap _zone === 'n' ) {
window _snap _placeholder . css ( {
'display' : 'block' ,
'width' : desktop _width ,
'height' : desktop _height ,
'top' : toolbar _height ,
'left' : 0 ,
'z-index' : last _window _zindex - 1 ,
} )
}
// SW
else if ( ! window _is _snapped && current _active _snap _zone === 'sw' ) {
window _snap _placeholder . css ( {
'display' : 'block' ,
'top' : toolbar _height + desktop _height / 2 ,
'left' : 0 ,
'width' : '50%' ,
'height' : desktop _height / 2 ,
'z-index' : last _window _zindex - 1 ,
} )
}
// SE
else if ( ! window _is _snapped && current _active _snap _zone === 'se' ) {
window _snap _placeholder . css ( {
'display' : 'block' ,
'top' : toolbar _height + desktop _height / 2 ,
'left' : desktop _width / 2 ,
'width' : '50%' ,
'height' : desktop _height / 2 ,
'z-index' : last _window _zindex - 1 ,
} )
}
// If snap placeholder is not active, append it and make it active
if ( ! window _is _snapped && ! snap _placeholder _active ) {
snap _placeholder _active = true ;
$ ( el _body ) . append ( window _snap _placeholder ) ;
}
// save window size before snap
width _before _snap = $ ( el _window ) . width ( ) ;
height _before _snap = $ ( el _window ) . height ( ) ;
} , 500 ) ;
// if mouse is not in a snap zone, hide snap placeholder
if ( snap _placeholder _active && ! current _active _snap _zone ) {
snap _placeholder _active = false ;
window _snap _placeholder . fadeOut ( 80 ) ;
}
}
} ,
stop : function ( ) {
let window _will _snap = false ;
$ ( el _window ) . draggable ( "option" , "cursorAt" , false ) ;
$ ( el _window ) . removeClass ( 'window-dragging' ) ;
$ ( el _window ) . attr ( {
'data-orig-top' : $ ( el _window ) . position ( ) . top ,
'data-orig-left' : $ ( el _window ) . position ( ) . left ,
} )
$ ( el _window _app _iframe ) . css ( 'pointer-events' , 'all' ) ;
$ ( '.window' ) . css ( 'pointer-events' , 'initial' ) ;
// jqueryui changes the z-index automatically, if the stay_on_top flag is set
// make sure window stays on top with the initial zindex though
$ ( ` .window[data-stay_on_top="true"] ` ) . each ( function ( ) {
$ ( this ) . css ( 'z-index' , $ ( this ) . attr ( 'data-initial_zindex' ) )
} )
if ( options . is _resizable && snap _placeholder _active && ! window _is _snapped ) {
window _will _snap = true ;
$ ( window _snap _placeholder ) . css ( 'padding' , 0 ) ;
setTimeout ( function ( ) {
// snap to w
if ( current _active _snap _zone === 'w' ) {
$ ( el _window ) . css ( {
'top' : toolbar _height ,
'left' : 0 ,
'width' : '50%' ,
'height' : desktop _height ,
} )
}
// snap to nw
else if ( current _active _snap _zone === 'nw' ) {
$ ( el _window ) . css ( {
'top' : toolbar _height ,
'left' : 0 ,
'width' : '50%' ,
'height' : desktop _height / 2 ,
} )
}
// snap to ne
else if ( current _active _snap _zone === 'ne' ) {
$ ( el _window ) . css ( {
'top' : toolbar _height ,
'left' : '50%' ,
'width' : '50%' ,
'height' : desktop _height / 2 ,
} )
}
// snap to sw
else if ( current _active _snap _zone === 'sw' ) {
$ ( el _window ) . css ( {
'top' : toolbar _height + desktop _height / 2 ,
'left' : 0 ,
'width' : '50%' ,
'height' : desktop _height / 2 ,
} )
}
// snap to se
else if ( current _active _snap _zone === 'se' ) {
$ ( el _window ) . css ( {
'top' : toolbar _height + desktop _height / 2 ,
'left' : desktop _width / 2 ,
'width' : '50%' ,
'height' : desktop _height / 2 ,
} )
}
// snap to e
else if ( current _active _snap _zone === 'e' ) {
$ ( el _window ) . css ( {
'top' : toolbar _height ,
'left' : '50%' ,
'width' : '50%' ,
'height' : desktop _height ,
} )
}
// snap to n
else if ( current _active _snap _zone === 'n' ) {
scale _window ( el _window ) ;
}
// snap placeholder is no longer active
snap _placeholder _active = false ;
// hide snap placeholder
window _snap _placeholder . css ( 'display' , 'none' ) ;
window _snap _placeholder . css ( 'padding' , '10px' ) ;
// mark window as snapped
window _is _snapped = true ;
// if at any point the window's width is "too small", hide the sidebar
if ( $ ( el _window ) . width ( ) < window _width _threshold _for _sidebar ) {
if ( width _before _snap >= window _width _threshold _for _sidebar && ! sidebar _hidden ) {
$ ( el _window _sidebar ) . hide ( ) ;
}
sidebar _hidden = true ;
}
// if at any point the window's width is "big enough", show the sidebar
else if ( $ ( el _window ) . width ( ) >= window _width _threshold _for _sidebar ) {
if ( sidebar _hidden ) {
$ ( el _window _sidebar ) . show ( ) ;
}
sidebar _hidden = false ;
}
} , 100 ) ;
}
// if window is dropped below the taskbar, move it up
// the lst '- 30' is to account for the window head
if ( $ ( el _window ) . position ( ) . top > window . innerHeight - taskbar _height - 30 && ! window _will _snap ) {
$ ( el _window ) . animate ( {
top : window . innerHeight - taskbar _height - 60 ,
} , 100 ) ;
}
// if window is dropped too far to the right, move it left
if ( $ ( el _window ) . position ( ) . left > window . innerWidth - 50 && ! window _will _snap ) {
$ ( el _window ) . animate ( {
left : window . innerWidth - 50 ,
} , 100 ) ;
}
// if window is dropped too far to the left, move it right
if ( ( $ ( el _window ) . position ( ) . left + $ ( el _window ) . width ( ) - 150 ) < 0 && ! window _will _snap ) {
$ ( el _window ) . animate ( {
left : - 1 * ( $ ( el _window ) . width ( ) - 150 ) ,
} , 100 ) ;
}
} ,
handle : ` .window-head-draggable ` + ( options . draggable _body ? ` , .window-body ` : ` ` ) ,
stack : ` .window ` ,
scroll : false ,
containment : '.window-container' ,
} ) ;
}
// --------------------------------------------------------
// Resizable
// --------------------------------------------------------
if ( options . is _resizable ) {
if ( $ ( el _window ) . width ( ) < window _width _threshold _for _sidebar ) {
$ ( el _window _sidebar ) . hide ( ) ;
sidebar _hidden = true ;
}
$ ( el _window ) . resizable ( {
handles : "n, ne, nw, e, s, se, sw, w" ,
minWidth : 200 ,
minHeight : 200 ,
start : function ( ) {
$ ( el _window _app _iframe ) . css ( 'pointer-events' , 'none' ) ;
$ ( '.window' ) . css ( 'pointer-events' , 'none' ) ;
} ,
resize : function ( e , ui ) {
// if at any point the window's width is "too small", hide the sidebar
if ( ui . size . width < window _width _threshold _for _sidebar ) {
if ( ui . originalSize . width >= window _width _threshold _for _sidebar && ! sidebar _hidden ) {
$ ( el _window _sidebar ) . hide ( ) ;
}
sidebar _hidden = true ;
}
// if at any point the window's width is "big enough", show the sidebar
else if ( ui . size . width >= window _width _threshold _for _sidebar ) {
if ( sidebar _hidden ) {
$ ( el _window _sidebar ) . show ( ) ;
}
sidebar _hidden = false ;
}
// when resizing the top of the window, make sure the window head is not hidden behind the toolbar
if ( $ ( el _window ) . position ( ) . top < toolbar _height ) {
var difference = toolbar _height - $ ( el _window ) . position ( ) . top ;
$ ( el _window ) . css ( {
'top' : toolbar _height ,
'height' : ui . size . height - difference // Reduce the height by the difference
} ) ;
// don't resize
return false ;
}
} ,
stop : function ( ) {
$ ( el _window _app _iframe ) . css ( 'pointer-events' , 'all' ) ;
$ ( '.window' ) . css ( 'pointer-events' , 'initial' ) ;
$ ( el _window _sidebar ) . resizable ( "option" , "maxWidth" , el _window . getBoundingClientRect ( ) . width / 2 ) ;
$ ( el _window ) . attr ( {
'data-orig-width' : $ ( el _window ) . width ( ) ,
'data-orig-height' : $ ( el _window ) . height ( ) ,
} )
// maximize icon
$ ( el _window _head _scale _btn ) . find ( 'img' ) . attr ( 'src' , window . icons [ 'scale.svg' ] ) ;
$ ( el _window ) . attr ( 'data-is_maximized' , '0' ) ;
} ,
containment : 'parent' ,
} )
}
let side = $ ( el _window ) . find ( '.window-sidebar' )
side . resizable ( {
handles : "e,w" ,
minWidth : 100 ,
maxWidth : el _window . getBoundingClientRect ( ) . width / 2 ,
start : function ( ) {
$ ( el _window _app _iframe ) . css ( 'pointer-events' , 'none' ) ;
$ ( '.window' ) . css ( 'pointer-events' , 'none' ) ;
} ,
stop : function ( ) {
$ ( el _window _app _iframe ) . css ( 'pointer-events' , 'all' ) ;
$ ( '.window' ) . css ( 'pointer-events' , 'initial' ) ;
const new _width = $ ( el _window _sidebar ) . width ( ) ;
// save new width in the cloud, to user's settings
setItem ( { key : "window_sidebar_width" , value : new _width } ) ;
// save new width locally, to window object
window . window _sidebar _width = new _width ;
}
} )
// --------------------------------------------------------
// Alt/Option + Shift + click on window head will open a prompt to enter iframe url
// --------------------------------------------------------
$ ( el _window _head ) . on ( 'click' , function ( e ) {
if ( e . altKey && e . shiftKey && el _window _app _iframe !== null ) {
let url = prompt ( "Enter URL" , options . iframe _url ) ;
if ( url ) {
$ ( el _window _app _iframe ) . attr ( 'src' , url ) ;
}
}
} )
// --------------------------------------------------------
// Head Context Menu
// --------------------------------------------------------
$ ( el _window _head ) . bind ( "contextmenu taphold" , function ( event ) {
// dimiss taphold on regular devices
if ( event . type === 'taphold' && ! isMobile . phone && ! isMobile . tablet )
return ;
const $target = $ ( event . target ) ;
// Cases in which native ctx menu should be preserved
if ( options . allow _native _ctxmenu || $target . hasClass ( 'allow-native-ctxmenu' ) || $target . is ( 'input' ) || $target . is ( 'textarea' ) )
return true ;
// custom ctxmenu for all other elements
event . preventDefault ( ) ;
// If window has no head, don't show ctxmenu
if ( ! options . has _head )
return ;
let menu _items = [ ] ;
// -------------------------------------------
// Maximize/Minimize
// -------------------------------------------
if ( options . is _resizable ) {
menu _items . push ( {
html : $ ( el _window ) . attr ( 'data-is_maximized' ) === '0' ? 'Maximize' : 'Restore' ,
onClick : function ( ) {
// maximize window
scale _window ( el _window ) ;
}
} ) ;
menu _items . push ( {
html : 'Minimize' ,
onClick : function ( ) {
$ ( el _window ) . hideWindow ( ) ;
}
} ) ;
// -
menu _items . push ( '-' )
}
// -------------------------------------------
// Close
// -------------------------------------------
menu _items . push ( {
html : 'Close' ,
onClick : function ( ) {
$ ( el _window ) . close ( ) ;
}
} ) ;
UIContextMenu ( {
parent _element : el _window _head ,
items : menu _items ,
parent _id : win _id ,
} )
} )
// --------------------------------------------------------
// Body Context Menu
// --------------------------------------------------------
$ ( el _window _body ) . bind ( "contextmenu taphold" , function ( event ) {
// dimiss taphold on regular devices
if ( event . type === 'taphold' && ! isMobile . phone && ! isMobile . tablet )
return ;
const $target = $ ( event . target ) ;
// Cases in which native ctx menu should be preserved
if ( options . allow _native _ctxmenu || $target . hasClass ( 'allow-native-ctxmenu' ) || $target . is ( 'input' ) || $target . is ( 'textarea' ) )
return true
// custom ctxmenu for all other elements
event . preventDefault ( ) ;
if ( options . allow _context _menu && event . target === el _window _body ) {
// Regular directories
if ( $ ( el _window ) . attr ( 'data-path' ) !== trash _path ) {
UIContextMenu ( {
parent _element : el _window _body ,
items : [
// -------------------------------------------
// Sort by
// -------------------------------------------
{
2024-03-18 07:01:06 +08:00
html : i18n ( 'sort_by' ) ,
2024-03-03 10:39:14 +08:00
items : [
{
2024-03-18 07:01:06 +08:00
html : i18n ( 'name' ) ,
2024-03-03 10:39:14 +08:00
icon : $ ( el _window ) . attr ( 'data-sort_by' ) === 'name' ? '✓' : '' ,
onClick : async function ( ) {
sort _items ( el _window _body , 'name' , $ ( el _window ) . attr ( 'data-sort_order' ) ) ;
set _sort _by ( $ ( el _window ) . attr ( 'data-uid' ) , 'name' , $ ( el _window ) . attr ( 'data-sort_order' ) )
}
} ,
{
2024-03-18 07:01:06 +08:00
html : i18n ( 'date_modified' ) ,
2024-03-03 10:39:14 +08:00
icon : $ ( el _window ) . attr ( 'data-sort_by' ) === 'modified' ? '✓' : '' ,
onClick : async function ( ) {
sort _items ( el _window _body , 'modified' , $ ( el _window ) . attr ( 'data-sort_order' ) ) ;
set _sort _by ( $ ( el _window ) . attr ( 'data-uid' ) , 'modified' , $ ( el _window ) . attr ( 'data-sort_order' ) )
}
} ,
{
2024-03-18 07:01:06 +08:00
html : i18n ( 'type' ) ,
2024-03-03 10:39:14 +08:00
icon : $ ( el _window ) . attr ( 'data-sort_by' ) === 'type' ? '✓' : '' ,
onClick : async function ( ) {
sort _items ( el _window _body , 'type' , $ ( el _window ) . attr ( 'data-sort_order' ) ) ;
set _sort _by ( $ ( el _window ) . attr ( 'data-uid' ) , 'type' , $ ( el _window ) . attr ( 'data-sort_order' ) )
}
} ,
{
2024-03-18 07:01:06 +08:00
html : i18n ( 'size' ) ,
2024-03-03 10:39:14 +08:00
icon : $ ( el _window ) . attr ( 'data-sort_by' ) === 'size' ? '✓' : '' ,
onClick : async function ( ) {
sort _items ( el _window _body , 'size' , $ ( el _window ) . attr ( 'data-sort_order' ) ) ;
set _sort _by ( $ ( el _window ) . attr ( 'data-uid' ) , 'size' , $ ( el _window ) . attr ( 'data-sort_order' ) )
}
} ,
// -------------------------------------------
// -
// -------------------------------------------
'-' ,
{
2024-03-18 07:01:06 +08:00
html : i18n ( 'ascending' ) ,
2024-03-03 10:39:14 +08:00
icon : $ ( el _window ) . attr ( 'data-sort_order' ) === 'asc' ? '✓' : '' ,
onClick : async function ( ) {
const sort _by = $ ( el _window ) . attr ( 'data-sort_by' )
sort _items ( el _window _body , sort _by , 'asc' ) ;
set _sort _by ( $ ( el _window ) . attr ( 'data-uid' ) , sort _by , 'asc' )
}
} ,
{
2024-03-18 07:01:06 +08:00
html : i18n ( 'descending' ) ,
2024-03-03 10:39:14 +08:00
icon : $ ( el _window ) . attr ( 'data-sort_order' ) === 'desc' ? '✓' : '' ,
onClick : async function ( ) {
const sort _by = $ ( el _window ) . attr ( 'data-sort_by' )
sort _items ( el _window _body , sort _by , 'desc' ) ;
set _sort _by ( $ ( el _window ) . attr ( 'data-uid' ) , sort _by , 'desc' )
}
} ,
]
} ,
// -------------------------------------------
// Refresh
// -------------------------------------------
{
2024-03-18 07:01:06 +08:00
html : i18n ( 'refresh' ) ,
2024-03-03 10:39:14 +08:00
onClick : function ( ) {
refresh _item _container ( el _window _body , options ) ;
}
} ,
// -------------------------------------------
2024-03-09 11:06:14 +08:00
// Show/Hide hidden files
// -------------------------------------------
{
2024-03-18 07:01:06 +08:00
html : i18n ( 'show_hidden' ) ,
icon : window . user _preferences . show _hidden _files ? '✓' : '' ,
2024-03-09 11:06:14 +08:00
onClick : function ( ) {
window . mutate _user _preferences ( {
show _hidden _files : ! window . user _preferences . show _hidden _files ,
} ) ;
window . show _or _hide _files ( document . querySelectorAll ( '.item-container' ) ) ;
}
} ,
// -------------------------------------------
2024-03-03 10:39:14 +08:00
// -
// -------------------------------------------
'-' ,
// -------------------------------------------
// New
// -------------------------------------------
2024-03-17 11:13:48 +08:00
new _context _menu _item ( $ ( el _window ) . attr ( 'data-path' ) , el _window _body ) ,
2024-03-03 10:39:14 +08:00
// -------------------------------------------
// -
// -------------------------------------------
'-' ,
// -------------------------------------------
// Paste
// -------------------------------------------
{
2024-03-18 07:01:06 +08:00
html : i18n ( 'paste' ) ,
2024-03-03 10:39:14 +08:00
disabled : ( clipboard . length === 0 || $ ( el _window ) . attr ( 'data-path' ) === '/' ) ? true : false ,
onClick : function ( ) {
if ( clipboard _op === 'copy' )
copy _clipboard _items ( $ ( el _window ) . attr ( 'data-path' ) , el _window _body ) ;
else if ( clipboard _op === 'move' )
move _clipboard _items ( el _window _body )
}
} ,
// -------------------------------------------
2024-03-10 22:18:17 +08:00
// Undo
// -------------------------------------------
{
2024-03-18 07:01:06 +08:00
html : i18n ( 'undo' ) ,
2024-03-10 22:18:17 +08:00
disabled : actions _history . length > 0 ? false : true ,
onClick : function ( ) {
undo _last _action ( ) ;
}
} ,
// -------------------------------------------
2024-03-03 10:39:14 +08:00
// Upload Here
// -------------------------------------------
{
2024-03-18 07:01:06 +08:00
html : i18n ( 'upload_here' ) ,
2024-03-03 10:39:14 +08:00
disabled : $ ( el _window ) . attr ( 'data-path' ) === '/' ? true : false ,
onClick : function ( ) {
init _upload _using _dialog ( el _window _body , $ ( el _window ) . attr ( 'data-path' ) + '/' ) ;
}
} ,
// -------------------------------------------
// -
// -------------------------------------------
'-' ,
// -------------------------------------------
// Publish As Website
// -------------------------------------------
{
2024-03-18 07:01:06 +08:00
html : i18n ( 'publish_as_website' ) ,
2024-03-03 10:39:14 +08:00
disabled : ! options . is _dir ,
onClick : async function ( ) {
if ( window . require _email _verification _to _publish _website ) {
if ( window . user . is _temp &&
! await UIWindowSaveAccount ( {
send _confirmation _code : true ,
2024-03-18 07:01:06 +08:00
message : i18n ( 'save_account_to_publish_website' ) ,
2024-03-03 10:39:14 +08:00
window _options : {
backdrop : true ,
close _on _backdrop _click : false ,
}
} ) )
return ;
else if ( ! window . user . email _confirmed && ! await UIWindowEmailConfirmationRequired ( ) )
return ;
}
UIWindowPublishWebsite ( $ ( el _window ) . attr ( 'data-uid' ) , $ ( el _window ) . attr ( 'data-name' ) , $ ( el _window ) . attr ( 'data-path' ) ) ;
}
} ,
// -------------------------------------------
// Deploy as App
// -------------------------------------------
{
2024-03-18 07:01:06 +08:00
html : i18n ( 'deploy_as_app' ) ,
2024-03-03 10:39:14 +08:00
disabled : ! options . is _dir ,
onClick : async function ( ) {
launch _app ( {
name : 'dev-center' ,
file _path : $ ( el _window ) . attr ( 'data-path' ) ,
file _uid : $ ( el _window ) . attr ( 'data-uid' ) ,
params : {
source _path : $ ( el _window ) . attr ( 'data-path' ) ,
}
} )
}
} ,
// -------------------------------------------
// -
// -------------------------------------------
'-' ,
// -------------------------------------------
// Properties
// -------------------------------------------
{
2024-03-18 07:01:06 +08:00
html : i18n ( 'properties' ) ,
2024-03-03 10:39:14 +08:00
onClick : function ( ) {
let window _height = 500 ;
let window _width = 450 ;
2024-03-18 07:01:06 +08:00
2024-03-03 10:39:14 +08:00
let left = mouseX ;
left -= 200 ;
left = left > ( window . innerWidth - window _width ) ? ( window . innerWidth - window _width ) : left ;
2024-03-18 07:01:06 +08:00
2024-03-03 10:39:14 +08:00
let top = mouseY ;
top = top > ( window . innerHeight - ( window _height + window . taskbar _height + window . toolbar _height ) ) ? ( window . innerHeight - ( window _height + window . taskbar _height + window . toolbar _height ) ) : top ;
2024-03-18 07:01:06 +08:00
2024-03-03 10:39:14 +08:00
UIWindowItemProperties ( options . title , options . path , options . uid , left , top , window _width , window _height ) ;
}
} ,
]
} ) ;
}
// Trash conext menu
else {
UIContextMenu ( {
parent _element : el _window _body ,
items : [
// -------------------------------------------
// Empty Trash
// -------------------------------------------
{
2024-03-19 04:07:29 +08:00
html : i18n ( 'empty_trash' ) ,
2024-03-03 10:39:14 +08:00
disabled : false ,
onClick : async function ( ) {
const alert _resp = await UIAlert ( {
2024-03-18 07:01:06 +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 ;
// todo this has to be case-insensitive but the `i` selector doesn't work on ^=
$ ( ` .item[data-path^=" ${ html _encode ( trash _path ) } /"] ` ) . each ( function ( ) {
delete _item ( this ) ;
} )
// update other clients
if ( window . socket ) {
window . socket . emit ( 'trash.is_empty' , { is _empty : true } ) ;
}
// use the 'empty trash' icon
$ ( ` .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' ] ) ;
}
} ,
]
} ) ;
}
}
} ) ;
// --------------------------------------------------------
// Head Context Menu
// --------------------------------------------------------
if ( options . has _head ) {
$ ( el _window _head ) . bind ( "contextmenu taphold" , function ( event ) {
event . preventDefault ( ) ;
return false ;
} )
}
// --------------------------------------------------------
// Droppable sidebar items
// --------------------------------------------------------
$ ( el _window ) . find ( '.window-sidebar-item' ) . each ( function ( index ) {
// todo only continue if this item is a dir
const el _item = this ;
$ ( el _item ) . dragster ( {
enter : function ( dragsterEvent , event ) {
$ ( el _item ) . addClass ( 'item-selected' ) ;
} ,
leave : function ( dragsterEvent , event ) {
$ ( el _item ) . removeClass ( 'item-selected' ) ;
} ,
drop : function ( dragsterEvent , event ) {
const e = event . originalEvent ;
$ ( el _item ) . removeClass ( 'item-selected' ) ;
// if files were dropped...
if ( e . dataTransfer ? . items ? . length > 0 ) {
upload _items ( e . dataTransfer . items , $ ( el _item ) . attr ( 'data-path' ) )
}
e . stopPropagation ( ) ;
e . preventDefault ( ) ;
return false ;
}
} ) ;
} )
//set styles
$ ( el _window _body ) . css ( options . body _css ) ;
// is fullpage?
if ( options . is _fullpage ) {
$ ( el _window ) . hide ( )
setTimeout ( function ( ) {
enter _fullpage _mode ( el _window ) ;
$ ( el _window ) . show ( )
} , 5 ) ;
}
return el _window ;
}
function delete _window _element ( el _window ) {
// if this is the active element, set it to null
if ( active _element === el _window ) {
active _element = null ;
}
// remove DOM element
$ ( el _window ) . remove ( ) ;
// if no other windows open, reset window_counter
// resetting window counter is important so that next window opens at the center of the screen
if ( $ ( '.window' ) . length === 0 )
window . window _counter = 0 ;
}
$ ( document ) . on ( 'click' , '.window-sidebar-item' , async function ( e ) {
const el _window = $ ( this ) . closest ( '.window' ) ;
const parent _win _id = $ ( el _window ) . attr ( 'data-id' ) ;
const item _path = $ ( this ) . attr ( 'data-path' ) ;
// ctrl/cmd + click will open in new window
if ( e . metaKey || e . ctrlKey ) {
UIWindow ( {
path : item _path ,
title : path . basename ( item _path ) ,
icon : await item _icon ( { is _dir : true , path : item _path } ) ,
// todo
// uid: $(el_item).attr('data-uid'),
is _dir : true ,
// todo
// sort_by: $(el_item).attr('data-sort_by'),
app : 'explorer' ,
// top: options.maximized ? 0 : undefined,
// left: options.maximized ? 0 : undefined,
// height: options.maximized ? `calc(100% - ${window.taskbar_height + 1}px)` : undefined,
// width: options.maximized ? `100%` : undefined,
} ) ;
}
// update window path only if it's a new path AND no ctrl/cmd key pressed
else if ( item _path !== $ ( el _window ) . attr ( 'data-path' ) ) {
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 _window , item _path ) ;
}
} )
$ ( document ) . on ( 'contextmenu' , '.window-sidebar' , function ( e ) {
e . preventDefault ( ) ;
e . stopPropagation ( ) ;
return false ;
} )
$ ( document ) . on ( 'contextmenu taphold' , '.window-sidebar-item' , function ( event ) {
// dismiss taphold on regular devices
if ( event . type === 'taphold' && ! isMobile . phone && ! isMobile . tablet )
return ;
event . preventDefault ( ) ;
event . stopPropagation ( ) ;
// todo
// $(this).addClass('window-sidebar-item-highlighted');
const item = this ;
UIContextMenu ( {
parent _element : $ ( this ) ,
items : [
//--------------------------------------------------
// Open
//--------------------------------------------------
{
html : "Open" ,
onClick : function ( ) {
$ ( item ) . trigger ( 'click' ) ;
}
} ,
//--------------------------------------------------
// Open in New Window
//--------------------------------------------------
{
html : "Open in New Window" ,
onClick : async function ( ) {
let item _path = $ ( item ) . attr ( 'data-path' ) ;
UIWindow ( {
path : item _path ,
title : path . basename ( item _path ) ,
icon : await item _icon ( { is _dir : true , path : item _path } ) ,
// todo
// uid: $(el_item).attr('data-uid'),
is _dir : true ,
// todo
// sort_by: $(el_item).attr('data-sort_by'),
app : 'explorer' ,
// top: options.maximized ? 0 : undefined,
// left: options.maximized ? 0 : undefined,
// height: options.maximized ? `calc(100% - ${window.taskbar_height + 1}px)` : undefined,
// width: options.maximized ? `100%` : undefined,
} ) ;
}
}
]
} ) ;
return false ;
} )
$ ( document ) . on ( 'dblclick' , '.window .ui-resizable-handle' , function ( e ) {
let el _window = $ ( this ) . closest ( '.window' ) ;
// bottom
if ( $ ( this ) . hasClass ( 'ui-resizable-s' ) ) {
let height = window . innerHeight - $ ( el _window ) . position ( ) . top - window . taskbar _height - 1 ;
$ ( el _window ) . height ( height ) ;
}
// top
else if ( $ ( this ) . hasClass ( 'ui-resizable-n' ) ) {
let height = $ ( el _window ) . height ( ) + $ ( el _window ) . position ( ) . top - window . toolbar _height ;
$ ( el _window ) . css ( {
height : height ,
top : window . toolbar _height ,
} ) ;
}
// right
else if ( $ ( this ) . hasClass ( 'ui-resizable-e' ) ) {
let width = window . innerWidth - $ ( el _window ) . position ( ) . left ;
$ ( el _window ) . css ( {
width : width ,
} ) ;
}
// left
else if ( $ ( this ) . hasClass ( 'ui-resizable-w' ) ) {
let width = $ ( el _window ) . width ( ) + $ ( el _window ) . position ( ) . left ;
$ ( el _window ) . css ( {
width : width ,
left : 0
} ) ;
}
// bottom left
else if ( $ ( this ) . hasClass ( 'ui-resizable-sw' ) ) {
let width = $ ( el _window ) . width ( ) + $ ( el _window ) . position ( ) . left ;
let height = window . innerHeight - $ ( el _window ) . position ( ) . top - window . taskbar _height - 1 ;
$ ( el _window ) . css ( {
width : width ,
height : height ,
left : 0
} ) ;
}
// bottom right
else if ( $ ( this ) . hasClass ( 'ui-resizable-se' ) ) {
let width = window . innerWidth - $ ( el _window ) . position ( ) . left ;
let height = window . innerHeight - $ ( el _window ) . position ( ) . top - window . taskbar _height - 1 ;
$ ( el _window ) . css ( {
width : width ,
height : height ,
} ) ;
}
// top right
else if ( $ ( this ) . hasClass ( 'ui-resizable-ne' ) ) {
let width = window . innerWidth - $ ( el _window ) . position ( ) . left ;
let height = $ ( el _window ) . height ( ) + $ ( el _window ) . position ( ) . top - window . toolbar _height ;
$ ( el _window ) . css ( {
width : width ,
height : height ,
top : window . toolbar _height ,
} ) ;
}
// top left
else if ( $ ( this ) . hasClass ( 'ui-resizable-nw' ) ) {
let width = $ ( el _window ) . width ( ) + $ ( el _window ) . position ( ) . left ;
let height = $ ( el _window ) . height ( ) + $ ( el _window ) . position ( ) . top - window . toolbar _height ;
$ ( el _window ) . css ( {
width : width ,
height : height ,
top : window . toolbar _height ,
left : 0 ,
} ) ;
}
} )
$ ( document ) . on ( 'click' , '.window-navbar-path' , function ( e ) {
if ( ! $ ( e . target ) . hasClass ( 'window-navbar-path' ) )
return ;
$ ( e . target ) . hide ( ) ;
$ ( e . target ) . siblings ( '.window-navbar-path-input' ) . show ( ) . select ( ) ;
} )
$ ( document ) . on ( 'blur' , '.window-navbar-path-input' , function ( e ) {
$ ( e . target ) . hide ( ) ;
$ ( e . target ) . siblings ( '.window-navbar-path' ) . show ( ) . select ( ) ;
} )
$ ( document ) . on ( 'keyup' , '.window-navbar-path-input' , function ( e ) {
if ( e . key === 'Enter' || e . keyCode === 13 ) {
update _window _path ( $ ( e . target ) . closest ( '.window' ) , $ ( e . target ) . val ( ) ) ;
$ ( e . target ) . hide ( ) ;
$ ( e . target ) . siblings ( '.window-navbar-path' ) . show ( ) . select ( ) ;
}
} )
$ ( document ) . on ( 'click' , '.window-navbar-path-dirname' , function ( e ) {
const $el _parent _window = $ ( this ) . closest ( '.window' ) ;
const parent _win _id = $ ( $el _parent _window ) . attr ( 'data-id' ) ;
// open in new window
if ( e . metaKey || e . ctrlKey ) {
const dirpath = $ ( this ) . attr ( 'data-path' ) ;
UIWindow ( {
path : dirpath ,
title : dirpath === '/' ? root _dirname : path . basename ( dirpath ) ,
icon : window . icons [ 'folder.svg' ] ,
// uid: $(el_item).attr('data-uid'),
is _dir : true ,
app : 'explorer' ,
} ) ;
}
// only change dir if target is not the same as current path
else if ( $el _parent _window . attr ( 'data-path' ) !== $ ( this ) . attr ( 'data-path' ) ) {
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 ( $ ( this ) . attr ( 'data-path' ) ) ;
window _nav _history _current _position [ parent _win _id ] = window _nav _history [ parent _win _id ] . length - 1 ;
update _window _path ( $el _parent _window , $ ( this ) . attr ( 'data-path' ) ) ;
}
} )
$ ( document ) . on ( 'contextmenu taphold' , '.window-navbar' , function ( event ) {
// don't disable system ctxmenu on the address bar input
if ( $ ( event . target ) . hasClass ( 'window-navbar-path-input' ) )
return ;
// dismiss taphold on regular devices
if ( event . type === 'taphold' && ! isMobile . phone && ! isMobile . tablet )
return ;
event . preventDefault ( ) ;
event . stopPropagation ( ) ;
return false ;
} )
$ ( document ) . on ( 'contextmenu taphold' , '.window-navbar-path-dirname' , function ( event ) {
// dismiss taphold on regular devices
if ( event . type === 'taphold' && ! isMobile . phone && ! isMobile . tablet )
return ;
event . preventDefault ( ) ;
const menu _items = [ ] ;
const el = this ;
// -------------------------------------------
// Open
// -------------------------------------------
menu _items . push ( {
html : 'Open' ,
onClick : ( ) => {
$ ( this ) . trigger ( 'click' ) ;
}
} ) ;
// -------------------------------------------
// Open in New Window
// (only if the item is on a window)
// -------------------------------------------
menu _items . push ( {
html : 'Open in New Window' ,
onClick : function ( ) {
UIWindow ( {
path : $ ( el ) . attr ( 'data-path' ) ,
title : $ ( el ) . attr ( 'data-path' ) === '/' ? root _dirname : path . basename ( $ ( el ) . attr ( 'data-path' ) ) ,
icon : window . icons [ 'folder.svg' ] ,
uid : $ ( el ) . attr ( 'data-uid' ) ,
is _dir : true ,
app : 'explorer' ,
} ) ;
}
} ) ;
// -------------------------------------------
// -
// -------------------------------------------
menu _items . push ( '-' ) ,
// -------------------------------------------
// Paste
// -------------------------------------------
menu _items . push ( {
html : "Paste" ,
disabled : clipboard . length > 0 ? false : true ,
onClick : function ( ) {
if ( clipboard _op === 'copy' )
copy _clipboard _items ( $ ( el ) . attr ( 'data-path' ) , null ) ;
else if ( clipboard _op === 'move' )
move _clipboard _items ( null , $ ( el ) . attr ( 'data-path' ) )
}
} )
UIContextMenu ( {
parent _element : $ ( this ) ,
items : menu _items
} ) ;
} )
// if the click is on the mask, bring focus to the active child window
$ ( document ) . on ( 'click' , '.window-disable-mask' , async function ( e ) {
e . stopPropagation ( ) ;
e . preventDefault ( ) ;
return false ;
} )
// --------------------------------------------------------
// Navbar Dir Droppable
// --------------------------------------------------------
window . navbar _path _droppable = ( el _window ) => {
$ ( el _window ) . find ( '.window-navbar-path-dirname' ) . droppable ( {
accept : '.item' ,
tolerance : 'pointer' ,
drop : function ( event , ui ) {
// check if item was actually dropped on this navbar path
if ( $ ( mouseover _window ) . attr ( 'data-id' ) !== $ ( el _window ) . attr ( 'data-id' ) ) {
return ;
}
const items _to _move = [ ]
// first item
items _to _move . push ( ui . draggable ) ;
// all subsequent items
const cloned _items = document . getElementsByClassName ( 'item-selected-clone' ) ;
for ( let i = 0 ; i < cloned _items . length ; i ++ ) {
const source _item = document . getElementById ( 'item-' + $ ( cloned _items [ i ] ) . attr ( 'data-id' ) ) ;
if ( source _item !== null )
items _to _move . push ( source _item ) ;
}
// if alt key is down, create shortcut items
if ( event . altKey ) {
items _to _move . forEach ( ( item _to _move ) => {
create _shortcut (
path . basename ( $ ( item _to _move ) . attr ( 'data-path' ) ) ,
$ ( item _to _move ) . attr ( 'data-is_dir' ) === '1' ,
$ ( this ) . attr ( 'data-path' ) ,
null ,
$ ( item _to _move ) . attr ( 'data-shortcut_to' ) === '' ? $ ( item _to _move ) . attr ( 'data-uid' ) : $ ( item _to _move ) . attr ( 'data-shortcut_to' ) ,
$ ( item _to _move ) . attr ( 'data-shortcut_to_path' ) === '' ? $ ( item _to _move ) . attr ( 'data-path' ) : $ ( item _to _move ) . attr ( 'data-shortcut_to_path' ) ,
) ;
} ) ;
}
// move items
else {
move _items ( items _to _move , $ ( this ) . attr ( 'data-path' ) ) ;
}
$ ( '.item-container' ) . droppable ( 'enable' )
$ ( this ) . removeClass ( 'window-navbar-path-dirname-active' ) ;
return false ;
} ,
over : function ( event , ui ) {
// check if item was actually hovered over this window
if ( $ ( mouseover _window ) . attr ( 'data-id' ) !== $ ( el _window ) . attr ( 'data-id' ) )
return ;
// Don't do anything if the dragged item is NOT a UIItem
if ( ! $ ( ui . draggable ) . hasClass ( 'item' ) )
return ;
// highlight this dirname
$ ( this ) . addClass ( 'window-navbar-path-dirname-active' ) ;
$ ( '.ui-draggable-dragging' ) . css ( 'opacity' , 0.2 )
$ ( '.item-selected-clone' ) . css ( 'opacity' , 0.2 )
// disable all window bodies
$ ( '.item-container' ) . droppable ( 'disable' )
} ,
out : function ( event , ui ) {
// Don't do anything if the dragged element is NOT a UIItem
if ( ! $ ( ui . draggable ) . hasClass ( 'item' ) )
return ;
// unselect directory if item is dragged out
$ ( this ) . removeClass ( 'window-navbar-path-dirname-active' ) ;
$ ( '.ui-draggable-dragging' ) . css ( 'opacity' , 'initial' )
$ ( '.item-selected-clone' ) . css ( 'opacity' , 'initial' )
$ ( '.item-container' ) . droppable ( 'enable' )
}
} ) ;
}
/ * *
* Constructs a XSS - safe string that represents a navigation bar path .
* The result is a string with HTML span elements for each directory in the path , each accompanied by a separator icon .
* Each span element has a ` data-path ` attribute holding the encoded path to that directory , and contains the encoded directory name as text .
* The root directory name is a constant defined in globals . js , represented as 'root_dirname' .
*
* @ param { string } abs _path - The absolute path to be displayed in the navigation bar . It should be a string with directories separated by slashes ( '/' ) .
*
* @ returns { string } A string of HTML spans and separators , each span representing a directory in the navigation bar .
*
* /
window . navbar _path = ( abs _path ) => {
const dirs = ( abs _path === '/' ? [ '' ] : abs _path . split ( '/' ) ) ;
const dirpaths = ( abs _path === '/' ? [ '/' ] : [ ] )
const path _seperator _html = ` <img class="path-seperator" draggable="false" src=" ${ html _encode ( window . icons [ 'triangle-right.svg' ] ) } "> ` ;
if ( dirs . length > 1 ) {
for ( let i = 0 ; i < dirs . length ; i ++ ) {
dirpaths [ i ] = '' ;
for ( let j = 1 ; j <= i ; j ++ ) {
dirpaths [ i ] += '/' + dirs [ j ] ;
}
}
}
let str = ` ${ path _seperator _html } <span class="window-navbar-path-dirname" data-path=" ${ html _encode ( '/' ) } "> ${ html _encode ( window . root _dirname ) } </span> ` ;
for ( let k = 1 ; k < dirs . length ; k ++ ) {
2024-03-19 04:07:29 +08:00
str += ` ${ path _seperator _html } <span class="window-navbar-path-dirname" data-path=" ${ html _encode ( dirpaths [ k ] ) } "> ${ dirs [ k ] === 'Trash' ? i18n ( 'trash' ) : html _encode ( dirs [ k ] ) } </span> ` ;
2024-03-03 10:39:14 +08:00
}
return str ;
}
window . update _window _path = async function ( el _window , target _path ) {
const win _id = $ ( el _window ) . attr ( 'data-id' ) ;
const el _window _navbar _forward _btn = $ ( el _window ) . find ( '.window-navbar-btn-forward' ) ;
const el _window _navbar _back _btn = $ ( el _window ) . find ( '.window-navbar-btn-back' ) ;
const el _window _navbar _up _btn = $ ( el _window ) . find ( '.window-navbar-btn-up' ) ;
const el _window _body = $ ( el _window ) . find ( '.window-body' ) ;
const el _window _item _container = $ ( el _window ) . find ( '.item-container' ) ;
const el _window _navbar _path _input = $ ( el _window ) . find ( '.window-navbar-path-input' ) ;
const is _dir = ( $ ( el _window ) . attr ( 'data-is_dir' ) === '1' || $ ( el _window ) . attr ( 'data-is_dir' ) === 'true' ) ;
const old _path = $ ( el _window ) . attr ( 'data-path' ) ;
// update sidebar items' active status
$ ( el _window ) . find ( ` .window-sidebar-item ` ) . removeClass ( 'window-sidebar-item-active' ) ;
$ ( el _window ) . find ( ` .window-sidebar-item[data-path=" ${ html _encode ( target _path ) } "] ` ) . addClass ( 'window-sidebar-item-active' ) ;
// clean
$ ( el _window ) . find ( '.explore-table-headers-th > .header-sort-icon' ) . html ( '' ) ;
if ( is _dir ) {
// if nav history for this window is empty, disable forward btn
if ( window _nav _history [ win _id ] && window _nav _history [ win _id ] . length - 1 === window _nav _history _current _position [ win _id ] )
$ ( el _window _navbar _forward _btn ) . addClass ( 'window-navbar-btn-disabled' ) ;
// ... else, enable forawrd btn
else
$ ( el _window _navbar _forward _btn ) . removeClass ( 'window-navbar-btn-disabled' ) ;
// disable back button if path is root
if ( window _nav _history _current _position [ win _id ] === 0 )
$ ( el _window _navbar _back _btn ) . addClass ( 'window-navbar-btn-disabled' ) ;
// ... enable back btn in all other cases
else
$ ( el _window _navbar _back _btn ) . removeClass ( 'window-navbar-btn-disabled' ) ;
// disabled Up button if this is root
if ( target _path === '/' )
$ ( el _window _navbar _up _btn ) . addClass ( 'window-navbar-btn-disabled' ) ;
// ... enable back btn in all other cases
else
$ ( el _window _navbar _up _btn ) . removeClass ( 'window-navbar-btn-disabled' ) ;
$ ( el _window _item _container ) . attr ( 'data-path' , target _path ) ;
$ ( el _window ) . find ( '.window-navbar-path' ) . html ( navbar _path ( target _path , window . user . username ) ) ;
// empty body to be filled with the results of /readdir
$ ( el _window _body ) . find ( '.item' ) . removeItems ( )
// add the 'Detail View' table header
if ( $ ( el _window ) . find ( '.explore-table-headers' ) . length === 0 )
$ ( el _window _body ) . prepend ( window . explore _table _headers ( ) ) ;
// 'Detail View' table header is hidden by default
$ ( el _window ) . find ( '.explore-table-headers' ) . hide ( ) ;
// system directories with custom icons and predefined names
if ( target _path === window . desktop _path ) {
$ ( el _window ) . find ( '.window-head-icon' ) . attr ( 'src' , window . icons [ 'folder-desktop.svg' ] ) ;
$ ( el _window ) . find ( '.window-head-title' ) . text ( 'Desktop' )
} else if ( target _path === window . home _path ) {
$ ( el _window ) . find ( '.window-head-icon' ) . attr ( 'src' , window . icons [ 'folder-home.svg' ] ) ;
$ ( el _window ) . find ( '.window-head-title' ) . text ( 'Home' )
} else if ( target _path === window . docs _path ) {
$ ( el _window ) . find ( '.window-head-icon' ) . attr ( 'src' , window . icons [ 'folder-documents.svg' ] ) ;
$ ( el _window ) . find ( '.window-head-title' ) . text ( 'Documents' )
} else if ( target _path === window . videos _path ) {
$ ( el _window ) . find ( '.window-head-icon' ) . attr ( 'src' , window . icons [ 'folder-videos.svg' ] ) ;
$ ( el _window ) . find ( '.window-head-title' ) . text ( 'Videos' )
} else if ( target _path === window . pictures _path ) {
$ ( el _window ) . find ( '.window-head-icon' ) . attr ( 'src' , window . icons [ 'folder-pictures.svg' ] ) ;
$ ( el _window ) . find ( '.window-head-title' ) . text ( 'Pictures' )
} // root folder of a shared user?
else if ( ( target _path . split ( '/' ) . length - 1 ) === 1 && target _path !== '/' + window . user . username )
$ ( el _window ) . find ( '.window-head-icon' ) . attr ( 'src' , window . icons [ 'shared.svg' ] ) ;
else
$ ( el _window ) . find ( '.window-head-icon' ) . attr ( 'src' , window . icons [ 'folder.svg' ] ) ;
}
$ ( el _window ) . attr ( 'data-path' , html _encode ( target _path ) ) ;
$ ( el _window ) . attr ( 'data-name' , html _encode ( path . basename ( target _path ) ) ) ;
// /stat
if ( target _path !== '/' ) {
try {
puter . fs . stat ( target _path , function ( fsentry ) {
$ ( el _window ) . removeClass ( 'window-' + $ ( el _window ) . attr ( 'data-uid' ) ) ;
$ ( el _window ) . addClass ( 'window-' + fsentry . id ) ;
$ ( el _window ) . attr ( 'data-uid' , fsentry . id ) ;
$ ( el _window ) . attr ( 'data-sort_by' , fsentry . sort _by ? ? 'name' ) ;
$ ( el _window ) . attr ( 'data-sort_order' , fsentry . sort _order ? ? 'asc' ) ;
$ ( el _window ) . attr ( 'data-layout' , fsentry . layout ? ? 'icons' ) ;
$ ( el _window _item _container ) . attr ( 'data-uid' , fsentry . id ) ;
// title
if ( target _path === window . home _path )
$ ( el _window ) . find ( '.window-head-title' ) . text ( 'Home' )
else
$ ( el _window ) . find ( '.window-head-title' ) . text ( fsentry . name ) ;
// data-name
$ ( el _window ) . attr ( 'data-name' , html _encode ( fsentry . name ) ) ;
// data-path
$ ( el _window ) . attr ( 'data-path' , html _encode ( target _path ) ) ;
$ ( el _window _navbar _path _input ) . val ( target _path ) ;
$ ( el _window _navbar _path _input ) . attr ( 'data-path' , target _path ) ;
// update layout
update _window _layout ( el _window , fsentry . layout ) ;
// update explore header if in details view
if ( fsentry . layout === 'details' ) {
update _details _layout _sort _visuals ( el _window , fsentry . sort _by , fsentry . sort _order ) ;
}
} ) ;
} catch ( err ) {
UIAlert ( err . responseText )
// todo optim: this is dumb because updating the window should only happen if this /readdir request is successful,
// in that case there is no need for using update_window_path on error!!
update _window _path ( el _window , old _path ) ;
}
}
// path is '/' (global root)
else {
$ ( el _window ) . removeClass ( 'window-' + $ ( el _window ) . attr ( 'data-uid' ) ) ;
$ ( el _window ) . addClass ( 'window-null' ) ;
$ ( el _window ) . attr ( 'data-uid' , 'null' ) ;
$ ( el _window ) . attr ( 'data-name' , '' ) ;
$ ( el _window ) . find ( '.window-head-title' ) . text ( root _dirname ) ;
}
if ( is _dir ) {
refresh _item _container ( el _window _body ) ;
navbar _path _droppable ( el _window )
}
update _explorer _footer _selected _items _count ( el _window ) ;
}
// --------------------------------------------------------
// Sidebar Item Droppable
// --------------------------------------------------------
window . sidebar _item _droppable = ( el _window ) => {
$ ( el _window ) . find ( '.window-sidebar-item' ) . droppable ( {
accept : '.item' ,
tolerance : 'pointer' ,
drop : function ( event , ui ) {
// check if item was actually dropped on this navbar path
if ( $ ( mouseover _window ) . attr ( 'data-id' ) !== $ ( el _window ) . attr ( 'data-id' ) ) {
return ;
}
const items _to _move = [ ]
// first item
items _to _move . push ( ui . draggable ) ;
// all subsequent items
const cloned _items = document . getElementsByClassName ( 'item-selected-clone' ) ;
for ( let i = 0 ; i < cloned _items . length ; i ++ ) {
const source _item = document . getElementById ( 'item-' + $ ( cloned _items [ i ] ) . attr ( 'data-id' ) ) ;
if ( source _item !== null )
items _to _move . push ( source _item ) ;
}
// if alt key is down, create shortcut items
if ( event . altKey ) {
items _to _move . forEach ( ( item _to _move ) => {
create _shortcut (
path . basename ( $ ( item _to _move ) . attr ( 'data-path' ) ) ,
$ ( item _to _move ) . attr ( 'data-is_dir' ) === '1' ,
$ ( this ) . attr ( 'data-path' ) ,
null ,
$ ( item _to _move ) . attr ( 'data-shortcut_to' ) === '' ? $ ( item _to _move ) . attr ( 'data-uid' ) : $ ( item _to _move ) . attr ( 'data-shortcut_to' ) ,
$ ( item _to _move ) . attr ( 'data-shortcut_to_path' ) === '' ? $ ( item _to _move ) . attr ( 'data-path' ) : $ ( item _to _move ) . attr ( 'data-shortcut_to_path' ) ,
) ;
} ) ;
}
// move items
else {
move _items ( items _to _move , $ ( this ) . attr ( 'data-path' ) ) ;
}
$ ( '.item-container' ) . droppable ( 'enable' )
$ ( this ) . removeClass ( 'window-sidebar-item-drag-active' ) ;
return false ;
} ,
over : function ( event , ui ) {
// check if item was actually hovered over this window
if ( $ ( mouseover _window ) . attr ( 'data-id' ) !== $ ( el _window ) . attr ( 'data-id' ) )
return ;
// Don't do anything if the dragged item is NOT a UIItem
if ( ! $ ( ui . draggable ) . hasClass ( 'item' ) )
return ;
// highlight this item
$ ( this ) . addClass ( 'window-sidebar-item-drag-active' ) ;
$ ( '.ui-draggable-dragging' ) . css ( 'opacity' , 0.2 )
$ ( '.item-selected-clone' ) . css ( 'opacity' , 0.2 )
// disable all window bodies
$ ( '.item-container' ) . droppable ( 'disable' )
} ,
out : function ( event , ui ) {
// Don't do anything if the dragged element is NOT a UIItem
if ( ! $ ( ui . draggable ) . hasClass ( 'item' ) )
return ;
// unselect item if item is dragged out
$ ( this ) . removeClass ( 'window-sidebar-item-drag-active' ) ;
$ ( '.ui-draggable-dragging' ) . css ( 'opacity' , 'initial' )
$ ( '.item-selected-clone' ) . css ( 'opacity' , 'initial' )
$ ( '.item-container' ) . droppable ( 'enable' )
}
} ) ;
}
// closes a window
$ . fn . close = async function ( options ) {
options = options || { } ;
$ ( this ) . each ( async function ( ) {
const el _iframe = $ ( this ) . find ( '.window-app-iframe' ) ;
// tell child app that this window is about to close, get its response
if ( el _iframe . length > 0 && el _iframe . attr ( 'data-appUsesSDK' ) === 'true' ) {
if ( ! options . bypass _iframe _messaging ) {
const resp = await sendWindowWillCloseMsg ( el _iframe . get ( 0 ) ) ;
if ( ! resp . msg ) {
return false ;
}
}
}
// Process window close if this is a window
if ( $ ( this ) . hasClass ( 'window' ) ) {
const win _id = parseInt ( $ ( this ) . attr ( 'data-id' ) ) ;
let window _uuid = $ ( this ) . attr ( 'data-element_uuid' ) ;
// remove all instances of win_id from window_stack
_ . pullAll ( window _stack , [ win _id ] ) ;
// taskbar update
let open _window _count = parseInt ( $ ( ` .taskbar-item[data-app=" ${ $ ( this ) . attr ( 'data-app' ) } "] ` ) . attr ( 'data-open-windows' ) ) ;
// update open window count of corresponding taskbar item
if ( open _window _count > 0 ) {
$ ( ` .taskbar-item[data-app=" ${ $ ( this ) . attr ( 'data-app' ) } "] ` ) . attr ( 'data-open-windows' , open _window _count - 1 ) ;
}
// decide whether to remove taskbar item
if ( open _window _count === 1 ) {
$ ( ` .taskbar-item[data-app=" ${ $ ( this ) . attr ( 'data-app' ) } "] .active-taskbar-indicator ` ) . hide ( ) ;
remove _taskbar _item ( $ ( ` .taskbar-item[data-app=" ${ $ ( this ) . attr ( 'data-app' ) } "][data-keep-in-taskbar="false"] ` ) ) ;
}
// if no more windows of this app are open, remove taskbar item
if ( open _window _count - 1 === 0 )
$ ( ` .taskbar-item[data-app=" ${ $ ( this ) . attr ( 'data-app' ) } "] .active-taskbar-indicator ` ) . hide ( ) ;
// if a fullpage window is closed, show desktop and taskbar
if ( $ ( this ) . attr ( 'data-is_fullpage' ) === '1' ) {
exit _fullpage _mode ( ) ;
}
// FileDialog closed
if ( $ ( this ) . hasClass ( 'window-filedialog' ) || $ ( this ) . attr ( 'data-disable_parent_window' ) === 'true' ) {
// re-enable this FileDialog's parent window
$ ( ` .window[data-element_uuid=" ${ $ ( this ) . attr ( 'data-parent_uuid' ) } "] ` ) . addClass ( 'window-active' ) ;
$ ( ` .window[data-element_uuid=" ${ $ ( this ) . attr ( 'data-parent_uuid' ) } "] ` ) . removeClass ( 'window-disabled' ) ;
$ ( ` .window[data-element_uuid=" ${ $ ( this ) . attr ( 'data-parent_uuid' ) } "] ` ) . find ( '.window-disable-mask' ) . hide ( ) ;
// bring focus back to app iframe, if needed
$ ( ` .window[data-element_uuid=" ${ $ ( this ) . attr ( 'data-parent_uuid' ) } "] ` ) . focusWindow ( ) ;
}
// Other types of windows closed
else {
// close any open FileDialogs belonging to this window
$ ( ` .window-filedialog[data-parent_uuid=" ${ window _uuid } "] ` ) . close ( ) ;
// bring focus to the last window in the window-stack (only if not minimized)
if ( ! _ . isEmpty ( window _stack ) ) {
const $last _window _in _stack = $ ( ` .window[data-id=" ${ window _stack [ window _stack . length - 1 ] } "] ` ) ;
// check if previous window is not minimized
if ( $last _window _in _stack !== null && $last _window _in _stack . attr ( 'data-is_minimized' ) !== '1' && $last _window _in _stack . attr ( 'data-is_minimized' ) !== 'true' ) {
$ ( ` .window[data-id=" ${ window _stack [ window _stack . length - 1 ] } "] ` ) . focusWindow ( ) ;
}
// otherwise, change URL/Title to desktop
else {
window . history . replaceState ( null , document . title , '/' ) ;
document . title = 'Puter' ;
}
// if it's explore
if ( $last _window _in _stack . attr ( 'data-app' ) && $last _window _in _stack . attr ( 'data-app' ) . toLowerCase ( ) === 'explorer' ) {
window . history . replaceState ( null , document . title , '/' ) ;
document . title = 'Puter' ;
}
}
// otherwise, change URL/Title to desktop
else {
window . history . replaceState ( null , document . title , '/' ) ;
document . title = 'Puter' ;
}
}
// close child windows
$ ( ` .window[data-parent_uuid=" ${ window _uuid } "] ` ) . close ( ) ;
// remove backdrop
$ ( this ) . closest ( '.window-backdrop' ) . remove ( ) ;
// remove DOM element
if ( options ? . shrink _to _target ) {
// get target location
const target _pos = $ ( options . shrink _to _target ) . position ( ) ;
const target _size = $ ( options . shrink _to _target ) . get ( 0 ) . getBoundingClientRect ( ) ;
// animate window to target location
$ ( this ) . animate ( {
width : ` 1 ` ,
height : ` 1 ` ,
top : target _pos . top + target _size . height / 2 ,
left : target _pos . left + target _size . width / 2 ,
} , 300 , ( ) => {
// remove DOM element
delete _window _element ( this ) ;
} ) ;
}
else if ( window . animate _window _closing ) {
// start shrink animation
$ ( this ) . css ( {
'transition' : 'transform 400ms' ,
'transform' : 'scale(0)' ,
} ) ;
// remove DOM element after fadeout animation
$ ( this ) . fadeOut ( 80 , function ( ) {
delete _window _element ( this ) ;
} )
} else {
delete _window _element ( this ) ;
}
}
// focus back to desktop?
if ( _ . isEmpty ( window _stack ) ) {
// The following is to make sure the iphone keyboard is dismissed when the last window is closed
if ( isMobile . phone || isMobile . tablet ) {
document . activeElement . blur ( ) ;
$ ( "input" ) . blur ( ) ;
}
// focus back to desktop
$ ( '.desktop' ) . find ( '.item-blurred' ) . removeClass ( 'item-blurred' ) ;
active _item _container = $ ( '.desktop.item-container' ) . get ( 0 ) ;
}
} )
return this ;
} ;
window . scale _window = ( el _window ) => {
//maximize
if ( $ ( el _window ) . attr ( 'data-is_maximized' ) !== '1' ) {
// save original size and position
let el _window _rect = el _window . getBoundingClientRect ( ) ;
$ ( el _window ) . attr ( {
'data-left-before-maxim' : el _window _rect . left + 'px' ,
'data-top-before-maxim' : el _window _rect . top + 'px' ,
'data-width-before-maxim' : $ ( el _window ) . css ( 'width' ) ,
'data-height-before-maxim' : $ ( el _window ) . css ( 'height' ) ,
'data-is_maximized' : '1' ,
} ) ;
// shrink icon
$ ( el _window ) . find ( '.window-scale-btn>img' ) . attr ( 'src' , window . icons [ 'scale-down-3.svg' ] ) ;
// set new size and position
$ ( el _window ) . css ( {
'top' : toolbar _height + 'px' ,
'left' : '0' ,
'width' : '100%' ,
'height' : ` calc(100% - ${ window . taskbar _height + window . toolbar _height + 1 } px) ` ,
'transform' : 'none' ,
} ) ;
}
//shrink
else {
// set size and position to original before maximization
$ ( el _window ) . css ( {
'top' : $ ( el _window ) . attr ( 'data-top-before-maxim' ) ,
'left' : $ ( el _window ) . attr ( 'data-left-before-maxim' ) ,
'width' : $ ( el _window ) . attr ( 'data-width-before-maxim' ) ,
'height' : $ ( el _window ) . attr ( 'data-height-before-maxim' ) ,
'transform' : 'none' ,
} ) ;
// maximize icon
$ ( el _window ) . find ( '.window-scale-btn>img' ) . attr ( 'src' , window . icons [ 'scale.svg' ] ) ;
$ ( el _window ) . attr ( {
'data-is_maximized' : 0 ,
} ) ;
}
// record window size and position before scaling
$ ( el _window ) . attr ( {
'data-orig-width' : $ ( el _window ) . width ( ) ,
'data-orig-height' : $ ( el _window ) . height ( ) ,
'data-orig-top' : $ ( el _window ) . position ( ) . top ,
'data-orig-left' : $ ( el _window ) . position ( ) . left ,
'data-is_minimized' : false ,
} )
}
window . update _explorer _footer _item _count = function ( el _window ) {
//update dir count in explorer footer
let item _count = $ ( el _window ) . find ( '.item' ) . length ;
2024-03-19 04:29:27 +08:00
$ ( el _window ) . find ( '.explorer-footer .explorer-footer-item-count' ) . html ( item _count + ` ${ i18n ( 'item' ) } ` + ( item _count == 0 || item _count > 1 ? ` ${ i18n ( 'plural_suffix' ) } ` : '' ) ) ;
2024-03-03 10:39:14 +08:00
}
window . update _explorer _footer _selected _items _count = function ( el _window ) {
//update dir count in explorer footer
let item _count = $ ( el _window ) . find ( '.item-selected' ) . length ;
if ( item _count > 0 ) {
$ ( el _window ) . find ( '.explorer-footer-seperator, .explorer-footer-selected-items-count' ) . show ( ) ;
2024-03-19 04:29:27 +08:00
$ ( el _window ) . find ( '.explorer-footer .explorer-footer-selected-items-count' ) . html ( item _count + ` ${ i18n ( 'item' ) } ` + ( item _count == 0 || item _count > 1 ? ` ${ i18n ( 'plural_suffix' ) } ` : '' ) + ` ${ i18n ( 'selected' ) } ` ) ;
2024-03-03 10:39:14 +08:00
} else {
$ ( el _window ) . find ( '.explorer-footer-seperator, .explorer-footer-selected-items-count' ) . hide ( ) ;
}
}
window . set _sort _by = function ( item _uid , sort _by , sort _order ) {
if ( sort _order !== 'asc' && sort _order !== 'desc' )
sort _order = 'asc' ;
$ . ajax ( {
url : api _origin + "/set_sort_by" ,
type : 'POST' ,
data : JSON . stringify ( {
sort _by : sort _by ,
item _uid : item _uid ,
sort _order : sort _order ,
} ) ,
async : true ,
contentType : "application/json" ,
headers : {
"Authorization" : "Bearer " + auth _token
} ,
statusCode : {
401 : function ( ) {
logout ( ) ;
} ,
} ,
success : function ( ) {
}
} )
// update the sort_by & sort_order attr of every matching element
$ ( ` [data-uid=" ${ item _uid } "] ` ) . attr ( {
'data-sort_by' : sort _by ,
'data-sort_order' : sort _order ,
} ) ;
}
window . explore _table _headers = function ( ) {
let h = ` ` ;
h += ` <div class="explore-table-headers"> ` ;
h += ` <div class="explore-table-headers-th explore-table-headers-th--name">Name<span class="header-sort-icon"></span></div> ` ;
h += ` <div class="explore-table-headers-th explore-table-headers-th--modified">Modified<span class="header-sort-icon"></span></div> ` ;
h += ` <div class="explore-table-headers-th explore-table-headers-th--size">Size<span class="header-sort-icon"></span></div> ` ;
h += ` <div class="explore-table-headers-th explore-table-headers-th--type">Type<span class="header-sort-icon"></span></div> ` ;
h += ` </div> ` ;
return h ;
}
window . update _window _layout = function ( el _window , layout ) {
layout = layout ? ? 'icons' ;
if ( layout === 'icons' ) {
$ ( el _window ) . find ( '.explore-table-headers' ) . hide ( ) ;
$ ( el _window ) . find ( '.item-container' ) . removeClass ( 'item-container-list' ) ;
$ ( el _window ) . find ( '.item-container' ) . removeClass ( 'item-container-details' ) ;
$ ( el _window ) . find ( '.window-navbar-layout-settings' ) . attr ( 'src' , window . icons [ 'layout-icons.svg' ] ) ;
$ ( el _window ) . attr ( 'data-layout' , layout )
}
else if ( layout === 'list' ) {
$ ( el _window ) . find ( '.explore-table-headers' ) . hide ( ) ;
$ ( el _window ) . find ( '.item-container' ) . removeClass ( 'item-container-details' ) ;
$ ( el _window ) . find ( '.item-container' ) . addClass ( 'item-container-list' ) ;
$ ( el _window ) . find ( '.window-navbar-layout-settings' ) . attr ( 'src' , window . icons [ 'layout-list.svg' ] )
$ ( el _window ) . attr ( 'data-layout' , layout )
}
else if ( layout === 'details' ) {
$ ( el _window ) . find ( '.explore-table-headers' ) . show ( ) ;
$ ( el _window ) . find ( '.item-container' ) . removeClass ( 'item-container-list' ) ;
$ ( el _window ) . find ( '.item-container' ) . addClass ( 'item-container-details' ) ;
$ ( el _window ) . find ( '.window-navbar-layout-settings' ) . attr ( 'src' , window . icons [ 'layout-details.svg' ] )
$ ( el _window ) . attr ( 'data-layout' , layout )
}
}
$ . fn . showWindow = async function ( options ) {
$ ( this ) . each ( async function ( ) {
if ( $ ( this ) . hasClass ( 'window' ) ) {
// show window
const el _window = this ;
$ ( el _window ) . css ( {
'transition' : ` top 0.2s, left 0.2s, bottom 0.2s, right 0.2s, width 0.2s, height 0.2s ` ,
top : $ ( el _window ) . attr ( 'data-orig-top' ) + 'px' ,
left : $ ( el _window ) . attr ( 'data-orig-left' ) + 'px' ,
width : $ ( el _window ) . attr ( 'data-orig-width' ) + 'px' ,
height : $ ( el _window ) . attr ( 'data-orig-height' ) + 'px' ,
} ) ;
$ ( el _window ) . css ( 'z-index' , ++ last _window _zindex ) ;
setTimeout ( ( ) => {
$ ( this ) . focusWindow ( ) ;
} , 80 ) ;
// remove `transitions` a good while after setting css to make sure
// it doesn't interfere with an ongoing animation
setTimeout ( ( ) => {
$ ( el _window ) . css ( 'transition' , 'none' ) ;
} , 250 ) ;
}
} )
return this ;
} ;
window . show _or _hide _empty _folder _message = function ( el _item _container ) {
// if the item container is the desktop, don't show/hide the empty message
if ( $ ( el _item _container ) . hasClass ( 'desktop' ) )
return ;
// if the item container is empty, show the empty message
if ( $ ( el _item _container ) . has ( '.item' ) . length === 0 ) {
$ ( el _item _container ) . find ( '.explorer-empty-message' ) . show ( ) ;
}
// if the item container is not empty, hide the empty message
else {
$ ( el _item _container ) . find ( '.explorer-empty-message' ) . hide ( ) ;
}
}
$ . fn . focusWindow = function ( event ) {
if ( this . hasClass ( 'window' ) ) {
const $app _iframe = $ ( this ) . find ( '.window-app-iframe' ) ;
$ ( '.window' ) . not ( this ) . removeClass ( 'window-active' ) ;
$ ( this ) . addClass ( 'window-active' ) ;
// disable pointer events on all other windows' iframes, except for this window's iframe
$ ( '.window-app-iframe' ) . not ( $app _iframe ) . css ( 'pointer-events' , 'none' ) ;
// bring this window to front, only if it's not stay_on_top
if ( $ ( this ) . attr ( 'data-stay_on_top' ) !== 'true' ) {
$ ( this ) . css ( 'z-index' , ++ last _window _zindex ) ;
}
// if this window has a parent, bring them to the front too
if ( $ ( this ) . attr ( 'data-parent_uuid' ) !== 'null' ) {
$ ( ` .window[data-element_uuid=" ${ $ ( this ) . attr ( 'data-parent_uuid' ) } "] ` ) . css ( 'z-index' , last _window _zindex ) ;
}
// if this window has child windows, bring them to the front too
if ( $ ( this ) . attr ( 'data-element_uuid' ) !== 'null' ) {
$ ( ` .window[data-parent_uuid=" ${ $ ( this ) . attr ( 'data-element_uuid' ) } "] ` ) . css ( 'z-index' , ++ last _window _zindex ) ;
}
//
// if this has an iframe, focus on it
if ( ! $ ( this ) . hasClass ( 'window-disabled' ) && $app _iframe . length > 0 ) {
$ ( $app _iframe ) . css ( 'pointer-events' , 'all' ) ;
$app _iframe . get ( 0 ) ? . focus ( { preventScroll : true } ) ;
$app _iframe . get ( 0 ) ? . contentWindow ? . focus ( { preventScroll : true } ) ;
// todo check if iframe is using SDK before sending messages
$app _iframe . get ( 0 ) . contentWindow . postMessage ( { msg : "focus" } , '*' ) ;
var rect = $app _iframe . get ( 0 ) . getBoundingClientRect ( ) ;
// send click event to iframe, if this focus event was triggered by a click or similar mouse event
if (
event !== undefined &&
( event . type === 'click' || event . type === 'dblclick' || event . type === 'contextmenu' || event . type === 'mousedown' || event . type === 'mouseup' || event . type === 'mousemove' )
) {
$app _iframe . get ( 0 ) . contentWindow . postMessage ( { msg : "click" , x : ( mouseX - rect . left ) , y : ( mouseY - rect . top ) } , '*' ) ;
}
}
// set active_item_container
active _item _container = $ ( this ) . find ( '.item-container' ) . get ( 0 ) ;
// grey out all selected items on other windows/desktop
$ ( '.item-container' ) . not ( active _item _container ) . find ( '.item-selected' ) . addClass ( 'item-blurred' ) ;
// update window-stack
window _stack . push ( parseInt ( $ ( this ) . attr ( 'data-id' ) ) ) ;
// remove blurred class from items on this window
$ ( active _item _container ) . find ( '.item-blurred' ) . removeClass ( 'item-blurred' ) ;
//change window URL
const update _window _url = $ ( this ) . attr ( 'data-update_window_url' ) ;
if ( update _window _url === 'true' || update _window _url === null ) {
window . history . replaceState ( { window _id : $ ( this ) . attr ( 'data-id' ) } , '' , '/app/' + $ ( this ) . attr ( 'data-app' ) ) ;
document . title = $ ( this ) . attr ( 'data-name' ) ;
}
$ ( ` .taskbar .taskbar-item[data-app=" ${ $ ( this ) . attr ( 'data-app' ) } "] ` ) . addClass ( 'taskbar-item-active' ) ;
} else {
$ ( '.window' ) . find ( '.item-selected' ) . addClass ( 'item-blurred' ) ;
$ ( '.desktop' ) . find ( '.item-blurred' ) . removeClass ( 'item-blurred' ) ;
}
return this ;
}
// hides a window
$ . fn . hideWindow = async function ( options ) {
$ ( this ) . each ( async function ( ) {
if ( $ ( this ) . hasClass ( 'window' ) ) {
// get taskbar item location
const taskbar _item _pos = $ ( ` .taskbar .taskbar-item[data-app=" ${ $ ( this ) . attr ( 'data-app' ) } "] ` ) . position ( ) ;
$ ( this ) . attr ( {
'data-orig-width' : $ ( this ) . width ( ) ,
'data-orig-height' : $ ( this ) . height ( ) ,
'data-orig-top' : $ ( this ) . position ( ) . top ,
'data-orig-left' : $ ( this ) . position ( ) . left ,
'data-is_minimized' : true ,
} )
$ ( this ) . css ( {
'transition' : ` top 0.2s, left 0.2s, bottom 0.2s, right 0.2s, width 0.2s, height 0.2s ` ,
width : ` 0 ` ,
height : ` 0 ` ,
top : 'calc(100% - 60px)' ,
left : taskbar _item _pos . left + 29 ,
} ) ;
// remove transitions a good while after setting css to make sure
// it doesn't interfere with an ongoing animation
setTimeout ( ( ) => {
$ ( this ) . css ( {
'transition' : 'none' ,
'transform' : 'none'
} ) ;
} , 250 ) ;
// update title and window URL
window . history . replaceState ( null , document . title , '/' ) ;
document . title = 'Puter' ;
}
} )
return this ;
} ;
$ ( document ) . on ( 'click' , '.explore-table-headers-th' , function ( e ) {
let sort _by = 'name' ;
let sort _icon = ` <img src=" ${ window . icons [ 'up-arrow.svg' ] } "> ` ;
// current sort order
let sort _order = $ ( e . target ) . closest ( '.window' ) . attr ( 'data-sort_order' ) ? ? 'asc' ;
// flip sort order
if ( sort _order === 'asc' ) {
sort _order = 'desc' ;
sort _icon = ` <img src=" ${ window . icons [ 'down-arrow.svg' ] } "> ` ;
} else if ( sort _order === 'desc' ) {
sort _icon = ` <img src=" ${ window . icons [ 'up-arrow.svg' ] } "> ` ;
sort _order = 'asc' ;
}
// remove active class from all headers
$ ( e . target ) . closest ( '.window' ) . find ( '.explore-table-headers-th' ) . removeClass ( 'explore-table-headers-th-active' ) ;
// remove icons from all headers
$ ( e . target ) . closest ( '.window' ) . find ( '.header-sort-icon' ) . html ( '' ) ;
// add active class to this header
$ ( e . target ) . addClass ( 'explore-table-headers-th-active' ) ;
// set sort icon
$ ( e . target ) . closest ( '.window' ) . find ( '.explore-table-headers-th-active > .header-sort-icon' ) . html ( sort _icon ) ;
// set sort_by
if ( $ ( e . target ) . hasClass ( 'explore-table-headers-th--name' ) ) {
sort _by = 'name' ;
} else if ( $ ( e . target ) . hasClass ( 'explore-table-headers-th--modified' ) ) {
sort _by = 'modified' ;
} else if ( $ ( e . target ) . hasClass ( 'explore-table-headers-th--size' ) ) {
sort _by = 'size' ;
} else if ( $ ( e . target ) . hasClass ( 'explore-table-headers-th--type' ) ) {
sort _by = 'type' ;
}
// sort
sort _items ( $ ( e . target ) . closest ( '.window-body' ) , sort _by , sort _order ) ;
set _sort _by ( $ ( e . target ) . closest ( '.window' ) . attr ( 'data-uid' ) , sort _by , sort _order )
} )
window . set _layout = function ( item _uid , layout ) {
$ . ajax ( {
url : api _origin + "/set_layout" ,
type : 'POST' ,
data : JSON . stringify ( {
item _uid : item _uid ,
layout : layout ,
} ) ,
async : true ,
contentType : "application/json" ,
headers : {
"Authorization" : "Bearer " + auth _token
} ,
statusCode : {
401 : function ( ) {
logout ( ) ;
} ,
} ,
success : function ( ) {
if ( layout === 'details' ) {
let el _window = $ ( ` .window[data-uid=" ${ item _uid } "] ` ) ;
if ( el _window . length > 0 ) {
let sort _by = el _window . attr ( 'data-sort_by' ) ;
let sort _order = el _window . attr ( 'data-sort_order' ) ;
update _details _layout _sort _visuals ( el _window , sort _by , sort _order ) ;
}
}
}
} )
}
window . update _details _layout _sort _visuals = function ( el _window , sort _by , sort _order ) {
let sort _icon = '' ;
$ ( el _window ) . find ( '.explore-table-headers-th > .header-sort-icon' ) . html ( '' ) ;
if ( ! sort _order || sort _order === 'asc' )
sort _icon = ` <img src=" ${ window . icons [ 'up-arrow.svg' ] } "> ` ;
else if ( sort _order === 'desc' )
sort _icon = ` <img src=" ${ window . icons [ 'down-arrow.svg' ] } "> ` ;
if ( ! sort _by || sort _by === 'name' ) {
$ ( el _window ) . find ( '.explore-table-headers-th' ) . removeClass ( 'explore-table-headers-th-active' ) ;
$ ( el _window ) . find ( '.explore-table-headers-th--name' ) . addClass ( 'explore-table-headers-th-active' ) ;
$ ( el _window ) . find ( '.explore-table-headers-th--name > .header-sort-icon' ) . html ( sort _icon ) ;
} else if ( sort _by === 'size' ) {
$ ( el _window ) . find ( '.explore-table-headers-th' ) . removeClass ( 'explore-table-headers-th-active' ) ;
$ ( el _window ) . find ( '.explore-table-headers-th--size' ) . addClass ( 'explore-table-headers-th-active' ) ;
$ ( el _window ) . find ( '.explore-table-headers-th--size > .header-sort-icon' ) . html ( sort _icon ) ;
} else if ( sort _by === 'modified' ) {
$ ( el _window ) . find ( '.explore-table-headers-th' ) . removeClass ( 'explore-table-headers-th-active' ) ;
$ ( el _window ) . find ( '.explore-table-headers-th--modified' ) . addClass ( 'explore-table-headers-th-active' ) ;
$ ( el _window ) . find ( '.explore-table-headers-th--modified > .header-sort-icon' ) . html ( sort _icon ) ;
} else if ( sort _by === 'type' ) {
$ ( el _window ) . find ( '.explore-table-headers-th' ) . removeClass ( 'explore-table-headers-th-active' ) ;
$ ( el _window ) . find ( '.explore-table-headers-th--type' ) . addClass ( 'explore-table-headers-th-active' ) ;
$ ( el _window ) . find ( '.explore-table-headers-th--type > .header-sort-icon' ) . html ( sort _icon ) ;
}
}
// This is a hack to fix the issue where the window scrolls to the bottom when an app scrolls.
// this is due to an issue with iframes being able to hijack the scroll event for the parent object.
// w3c is working on a fix for this, but it's not ready yet.
// more info here: https://github.com/w3c/webappsec-permissions-policy/issues/171
document . addEventListener ( 'scroll' , function ( event ) {
if ( $ ( event . target ) . hasClass ( 'window-app' ) || $ ( event . target ) . hasClass ( 'window-app-iframe' ) || $ ( event . target ? . activeElement ) . hasClass ( 'window-app-iframe' ) ) {
setTimeout ( function ( ) {
// scroll window back to top
$ ( '.window-app' ) . scrollTop ( 0 ) ;
// some times it's document that scrolls, so we need to check that too
$ ( document ) . scrollTop ( 0 ) ;
} , 1 ) ;
}
} , true ) ;
2024-03-20 16:13:17 +08:00
export default UIWindow ;