1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020 |
- /**
- * 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/>.
- */
- import UIDesktop from './UI/UIDesktop.js'
- import UIWindow from './UI/UIWindow.js'
- import UIAlert from './UI/UIAlert.js'
- import UIWindowLogin from './UI/UIWindowLogin.js';
- import UIWindowSignup from './UI/UIWindowSignup.js';
- import path from "./lib/path.js";
- import UIWindowSaveAccount from './UI/UIWindowSaveAccount.js';
- import UIWindowNewPassword from './UI/UIWindowNewPassword.js';
- import UIWindowLoginInProgress from './UI/UIWindowLoginInProgress.js';
- import UIWindowEmailConfirmationRequired from './UI/UIWindowEmailConfirmationRequired.js';
- import UIWindowSessionList from './UI/UIWindowSessionList.js';
- import UIWindowRequestPermission from './UI/UIWindowRequestPermission.js';
- import UIWindowChangeUsername from './UI/UIWindowChangeUsername.js';
- import update_last_touch_coordinates from './helpers/update_last_touch_coordinates.js';
- import update_title_based_on_uploads from './helpers/update_title_based_on_uploads.js';
- import PuterDialog from './UI/PuterDialog.js';
- import determine_active_container_parent from './helpers/determine_active_container_parent.js';
- window.initgui = async function(){
- let url = new URL(window.location);
- url = url.href;
- // update SDK if auth_token is different from the one in the SDK
- if(window.auth_token && puter.authToken !== window.auth_token)
- puter.setAuthToken(window.auth_token);
- // update SDK if api_origin is different from the one in the SDK
- if(window.api_origin && puter.APIOrigin !== window.api_origin)
- puter.setAPIOrigin(api_origin);
- // determine locale
- window.locale = window.user_preferences.language;
- // Checks the type of device the user is on (phone, tablet, or desktop).
- // Depending on the device type, it sets a class attribute on the body tag
- // to style or script the page differently for each device type.
- if(isMobile.phone)
- $('body').attr('class', 'device-phone');
- else if(isMobile.tablet)
- $('body').attr('class', 'device-tablet');
- else
- $('body').attr('class', 'device-desktop');
- // Appends a meta tag to the head of the document specifying the character encoding to be UTF-8.
- // This ensures that special characters and symbols display correctly across various platforms and browsers.
- $('head').append(`<meta charset="utf-8">`);
- // Appends a viewport meta tag to the head of the document, ensuring optimal display on mobile devices.
- // This tag sets the width of the viewport to the device width, and locks the zoom level to 1 (prevents user scaling).
- $('head').append(`<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1">`);
- // GET query params provided
- window.url_query_params = new URLSearchParams(window.location.search);
- // will hold the result of the whoami API call
- let whoami;
- //--------------------------------------------------------------------------------------
- // Determine if an app was launched from URL
- // i.e. https://puter.com/app/<app_name>
- //--------------------------------------------------------------------------------------
- const url_paths = window.location.pathname.split('/').filter(element => element);
- if(url_paths[0]?.toLocaleLowerCase() === 'app' && url_paths[1]){
- window.app_launched_from_url = url_paths[1];
- // get query params, any param that doesn't start with 'puter.' will be passed to the app
- window.app_query_params = {};
- for (let [key, value] of url_query_params) {
- if(!key.startsWith('puter.'))
- app_query_params[key] = value;
- }
- }
- //--------------------------------------------------------------------------------------
- // Extract 'action' from URL
- //--------------------------------------------------------------------------------------
- let action;
- if(url_paths[0]?.toLocaleLowerCase() === 'action' && url_paths[1]){
- action = url_paths[1].toLowerCase();
- }
- //--------------------------------------------------------------------------------------
- // Determine if we are in full-page mode
- // i.e. https://puter.com/app/<app_name>/?puter.fullpage=true
- //--------------------------------------------------------------------------------------
- if(url_query_params.has('puter.fullpage') && (url_query_params.get('puter.fullpage') === 'false' || url_query_params.get('puter.fullpage') === '0')){
- window.is_fullpage_mode = false;
- }else if(url_query_params.has('puter.fullpage') && (url_query_params.get('puter.fullpage') === 'true' || url_query_params.get('puter.fullpage') === '1')){
- // In fullpage mode, we want to hide the taskbar for better UX
- window.taskbar_height = 0;
- // Puter is in fullpage mode.
- window.is_fullpage_mode = true;
- }
- //--------------------------------------------------------------------------------------
- // Is GUI embedded in a popup?
- // i.e. https://puter.com/?embedded_in_popup=true
- //--------------------------------------------------------------------------------------
- if(url_query_params.has('embedded_in_popup') && (url_query_params.get('embedded_in_popup') === 'true' || url_query_params.get('embedded_in_popup') === '1')){
- window.embedded_in_popup = true;
- $('body').addClass('embedded-in-popup');
- // determine the origin of the opener
- window.openerOrigin = document.referrer;
- // if no referrer, request it from the opener via messaging
- if(!document.referrer){
- try{
- openerOrigin = await requestOpenerOrigin();
- }catch(e){
- throw new Error('No referrer found');
- }
- }
- // this is the referrer in terms of user acquisition
- window.referrerStr = openerOrigin;
- if(action === 'sign-in' && !is_auth()){
- // show signup window
- if(await UIWindowSignup({
- reload_on_success: false,
- send_confirmation_code: false,
- show_close_button: false,
- window_options:{
- has_head: false,
- cover_page: true,
- }
- }))
- await getUserAppToken(openerOrigin);
- }
- else if(action === 'sign-in' && is_auth()){
- if(await UIWindowSessionList({
- reload_on_success: false,
- draggable_body: false,
- has_head: false,
- cover_page: true,
- }))
- await getUserAppToken(openerOrigin);
- }
- }
- //--------------------------------------------------------------------------------------
- // Get user referral code from URL query params
- // i.e. https://puter.com/?r=123456
- //--------------------------------------------------------------------------------------
- if(url_query_params.has('r')){
- window.referral_code = url_query_params.get('r');
- // remove 'r' from URL
- window.history.pushState(null, document.title, '/');
- // show referral notice, this will be used later if Desktop is loaded
- if(window.first_visit_ever)
- window.show_referral_notice = true;
- }
- //--------------------------------------------------------------------------------------
- // Action: Request Permission
- //--------------------------------------------------------------------------------------
- if(action === 'request-permission'){
- let app_uid = url_query_params.get('app_uid');
- let origin = openerOrigin ?? url_query_params.get('origin');
- let permission = url_query_params.get('permission');
- let granted = await UIWindowRequestPermission({
- app_uid: app_uid,
- origin: origin,
- permission: permission,
- });
- let messageTarget = embedded_in_popup ? window.opener : window.parent;
- messageTarget.postMessage({
- msg: "permissionGranted",
- granted: granted,
- }, origin);
- }
- //--------------------------------------------------------------------------------------
- // Action: Password recovery
- //--------------------------------------------------------------------------------------
- else if(action === 'set-new-password'){
- let user = url_query_params.get('user');
- let token = url_query_params.get('token');
- await UIWindowNewPassword({
- user: user,
- token: token,
- });
- }
- //--------------------------------------------------------------------------------------
- // Action: Change Username
- //--------------------------------------------------------------------------------------
- else if(action === 'change-username'){
- await UIWindowChangeUsername();
- }
- //--------------------------------------------------------------------------------------
- // Action: Login
- //--------------------------------------------------------------------------------------
- else if(action === 'login'){
- await UIWindowLogin();
- }
- //--------------------------------------------------------------------------------------
- // Action: Signup
- //--------------------------------------------------------------------------------------
- else if(action === 'signup'){
- await UIWindowSignup();
- }
- // -------------------------------------------------------------------------------------
- // If in embedded in a popup, it is important to check whether the opener app has a relationship with the user
- // if yes, we need to get the user app token and send it to the opener
- // if not, we need to ask the user for confirmation before proceeding BUT only if the action is a file-picker action
- // -------------------------------------------------------------------------------------
- if(window.embedded_in_popup && openerOrigin){
- let response = await checkUserSiteRelationship(openerOrigin);
- window.userAppToken = response.token;
- if(logged_in_users.length > 0 && (!userAppToken || url_query_params.get('request_auth') )){
- await UIWindowSessionList({
- reload_on_success: false,
- draggable_body: false,
- has_head: false,
- cover_page: true,
- });
- }
- // if not and action is show-open-file-picker, we need confirmation before proceeding
- if(action === 'show-open-file-picker' || action === 'show-save-file-picker' || action === 'show-directory-picker'){
- if(!userAppToken){
- let is_confirmed = await PuterDialog();
-
- if(is_confirmed === false){
- if(!is_auth()){
- window.first_visit_ever = false;
- localStorage.removeItem("has_visited_before", true);
- }
- window.close();
- window.open('','_self').close();
- }
- }
- }
- }
- // -------------------------------------------------------------------------------------
- // `auth_token` provided in URL, use it to log in
- // -------------------------------------------------------------------------------------
- else if(url_query_params.has('auth_token')){
- let query_param_auth_token = url_query_params.get('auth_token');
- try{
- whoami = await puter.os.user();
- }catch(e){
- if(e.status === 401){
- logout();
- return;
- }
- }
- if(whoami){
- if(whoami.requires_email_confirmation){
- let is_verified;
- do{
- is_verified = await UIWindowEmailConfirmationRequired({
- stay_on_top: true,
- has_head: false
- });
- }
- while(!is_verified)
- }
- // if user is logging in using an auth token that means it's not their first ever visit to Puter.com
- // it might be their first visit to Puter on this specific device but it's not their first time ever visiting Puter.
- window.first_visit_ever = false;
- // show login progress window
- UIWindowLoginInProgress({user_info: whoami});
- // update auth data
- update_auth_data(query_param_auth_token, whoami);
- }
- // remove auth_token from URL
- window.history.pushState(null, document.title, '/');
- }
- // -------------------------------------------------------------------------------------
- // Authed
- // -------------------------------------------------------------------------------------
- if(is_auth()){
- // try to get user data using /whoami, only if that data is missing
- if(!whoami){
- try{
- whoami = await puter.os.user();
- }catch(e){
- if(e.status === 401){
- logout();
- return;
- }
- }
- }
- // update local user data
- if(whoami){
- // is email confirmation required?
- if(whoami.requires_email_confirmation){
- let is_verified;
- do{
- is_verified = await UIWindowEmailConfirmationRequired({
- stay_on_top: true,
- has_head: false
- });
- }
- while(!is_verified)
- }
- update_auth_data(window.auth_token, whoami);
- // -------------------------------------------------------------------------------------
- // Load desktop, only if we're not embedded in a popup
- // -------------------------------------------------------------------------------------
- if(!window.embedded_in_popup){
- puter.fs.stat(desktop_path, async function(desktop_fsentry){
- UIDesktop({desktop_fsentry: desktop_fsentry});
- })
- }
- // -------------------------------------------------------------------------------------
- // If embedded in a popup, send the token to the opener and close the popup
- // -------------------------------------------------------------------------------------
- else{
- let msg_id = url_query_params.get('msg_id');
- try{
- let data = await getUserAppToken(new URL(openerOrigin).origin);
- // This is an implicit app and the app_uid is sent back from the server
- // we cache it here so that we can use it later
- window.host_app_uid = data.app_uid;
- // send token to parent
- window.opener.postMessage({
- msg: 'puter.token',
- success: true,
- token: data.token,
- app_uid: data.app_uid,
- username: user.username,
- msg_id: msg_id,
- }, openerOrigin);
- // close popup
- if(!action || action==='sign-in'){
- window.close();
- window.open('','_self').close();
- }
- }catch(err){
- // send error to parent
- window.opener.postMessage({
- msg: 'puter.token',
- success: false,
- token: null,
- msg_id: msg_id,
- }, openerOrigin);
- // close popup
- window.close();
- window.open('','_self').close();
- }
- let app_uid;
-
- if(openerOrigin){
- app_uid = await getAppUIDFromOrigin(openerOrigin);
- window.host_app_uid = app_uid;
- }
-
- if(action === 'show-open-file-picker'){
- let options = url_query_params.get('options');
- options = JSON.parse(options ?? '{}');
- // Open dialog
- UIWindow({
- allowed_file_types: options?.accept,
- selectable_body: options?.multiple,
- path: '/' + window.user.username + '/Desktop',
- // this is the uuid of the window to which this dialog will return
- return_to_parent_window: true,
- show_maximize_button: false,
- show_minimize_button: false,
- title: 'Open',
- is_dir: true,
- is_openFileDialog: true,
- is_resizable: false,
- has_head: false,
- cover_page: true,
- // selectable_body: is_selectable_body,
- iframe_msg_uid: msg_id,
- center: true,
- initiating_app_uuid: app_uid,
- on_close: function(){
- window.opener.postMessage({
- msg: "fileOpenCanceled",
- original_msg_id: msg_id,
- }, '*');
- }
- });
- }
- //--------------------------------------------------------------------------------------
- // Action: Show Directory Picker
- //--------------------------------------------------------------------------------------
- else if(action === 'show-directory-picker'){
- // open directory picker dialog
- UIWindow({
- path: '/' + window.user.username + '/Desktop',
- // this is the uuid of the window to which this dialog will return
- // parent_uuid: event.data.appInstanceID,
- return_to_parent_window: true,
- show_maximize_button: false,
- show_minimize_button: false,
- title: 'Open',
- is_dir: true,
- is_directoryPicker: true,
- is_resizable: false,
- has_head: false,
- cover_page: true,
- // selectable_body: is_selectable_body,
- iframe_msg_uid: msg_id,
- center: true,
- initiating_app_uuid: app_uid,
- on_close: function(){
- window.opener.postMessage({
- msg: "directoryOpenCanceled",
- original_msg_id: msg_id,
- }, '*');
- }
- });
- }
- //--------------------------------------------------------------------------------------
- // Action: Show Save File Dialog
- //--------------------------------------------------------------------------------------
- else if(action === 'show-save-file-picker'){
- let allowed_file_types = url_query_params.get('allowed_file_types');
- // send 'sendMeFileData' event to parent
- window.opener.postMessage({
- msg: 'sendMeFileData',
- }, '*');
-
- // listen for 'showSaveFilePickerPopup' event from parent
- window.addEventListener('message', async (event) => {
- if(event.data.msg !== 'showSaveFilePickerPopup')
- return;
- // Open dialog
- UIWindow({
- allowed_file_types: allowed_file_types,
- path: '/' + window.user.username + '/Desktop',
- // this is the uuid of the window to which this dialog will return
- return_to_parent_window: true,
- show_maximize_button: false,
- show_minimize_button: false,
- title: 'Save',
- is_dir: true,
- is_saveFileDialog: true,
- is_resizable: false,
- has_head: false,
- cover_page: true,
- // selectable_body: is_selectable_body,
- iframe_msg_uid: msg_id,
- center: true,
- initiating_app_uuid: app_uid,
- on_close: function(){
- window.opener.postMessage({
- msg: "fileSaveCanceled",
- original_msg_id: msg_id,
- }, '*');
- },
- onSaveFileDialogSave: async function(target_path, el_filedialog_window){
- $(el_filedialog_window).find('.window-disable-mask, .busy-indicator').show();
- let busy_init_ts = Date.now();
- let overwrite = false;
- let file_to_upload = new File([event.data.content], path.basename(target_path));
- let item_with_same_name_already_exists = true;
- while(item_with_same_name_already_exists){
- // overwrite?
- if(overwrite)
- item_with_same_name_already_exists = false;
- // upload
- try{
- const res = await puter.fs.write(
- target_path,
- file_to_upload,
- {
- dedupeName: false,
- overwrite: overwrite
- }
- );
- let file_signature = await puter.fs.sign(app_uid, {uid: res.uid, action: 'write'});
- file_signature = file_signature.items;
- item_with_same_name_already_exists = false;
- window.opener.postMessage({
- msg: "fileSaved",
- original_msg_id: msg_id,
- filename: res.name,
- saved_file: {
- name: file_signature.fsentry_name,
- readURL: file_signature.read_url,
- writeURL: file_signature.write_url,
- metadataURL: file_signature.metadata_url,
- type: file_signature.type,
- uid: file_signature.uid,
- path: `~/` + res.path.split('/').slice(2).join('/'),
- },
- }, '*');
- window.close();
- window.open('','_self').close();
- }
- catch(err){
- // item with same name exists
- if(err.code === 'item_with_same_name_exists'){
- const alert_resp = await UIAlert({
- message: `<strong>${html_encode(err.entry_name)}</strong> already exists.`,
- buttons:[
- {
- label: i18n('replace'),
- value: 'replace',
- type: 'primary',
- },
- {
- label: i18n('cancel'),
- value: 'cancel',
- },
- ],
- parent_uuid: $(el_filedialog_window).attr('data-element_uuid'),
- })
- if(alert_resp === 'replace'){
- overwrite = true;
- }else if(alert_resp === 'cancel'){
- // enable parent window
- $(el_filedialog_window).find('.window-disable-mask, .busy-indicator').hide();
- return;
- }
- }
- else{
- console.log(err);
- // show error
- await UIAlert({
- message: err.message ?? "Upload failed.",
- parent_uuid: $(el_filedialog_window).attr('data-element_uuid'),
- });
- // enable parent window
- $(el_filedialog_window).find('.window-disable-mask, .busy-indicator').hide();
- return;
- }
- }
- }
- // done
- let busy_duration = (Date.now() - busy_init_ts);
- if( busy_duration >= busy_indicator_hide_delay){
- $(el_filedialog_window).close();
- }else{
- setTimeout(() => {
- // close this dialog
- $(el_filedialog_window).close();
- }, Math.abs(busy_indicator_hide_delay - busy_duration));
- }
- }
- });
- });
- }
- }
- // ----------------------------------------------------------
- // Get user's sites
- // ----------------------------------------------------------
- update_sites_cache();
- }
- }
- // -------------------------------------------------------------------------------------
- // Desktop Background
- // If we're in fullpage/emebedded/Auth Popup mode, we don't want to load the custom background
- // because it's not visible anyway and it's a waste of bandwidth
- // -------------------------------------------------------------------------------------
- if(!window.is_fullpage_mode && !window.embedded_in_popup){
- refresh_desktop_background();
- }
- // -------------------------------------------------------------------------------------
- // Un-authed but not first visit -> try to log in/sign up
- // -------------------------------------------------------------------------------------
- if(!is_auth() && !first_visit_ever){
- if(logged_in_users.length > 0){
- UIWindowSessionList();
- }
- else{
- await UIWindowLogin({
- reload_on_success: true,
- send_confirmation_code: false,
- window_options:{
- has_head: false
- }
- });
- }
- }
- // -------------------------------------------------------------------------------------
- // Un-authed and first visit ever -> create temp user
- // -------------------------------------------------------------------------------------
- else if(!is_auth() && first_visit_ever){
- let referrer;
- try{
- referrer = new URL(window.location.href).pathname;
- }catch(e){
- console.log(e)
- }
- referrer = window.openerOrigin ?? referrer;
- // a global object that will be used to store the user's referrer
- window.referrerStr = referrer;
- // in case there is also a referrer query param, add it to the referrer URL
- if(url_query_params.has('ref')){
- if(!referrer)
- referrer = '/';
- referrer += '?ref=' + html_encode(url_query_params.get('ref'));
- }
-
- let headers = {};
- if(window.custom_headers)
- headers = window.custom_headers;
- $.ajax({
- url: gui_origin + "/signup",
- type: 'POST',
- async: true,
- headers: headers,
- contentType: "application/json",
- data: JSON.stringify({
- referrer: referrer,
- referral_code: window.referral_code,
- is_temp: true,
- }),
- success: async function (data){
- update_auth_data(data.token, data.user);
- document.dispatchEvent(new Event("login", { bubbles: true}));
- },
- error: function (err){
- $('#signup-error-msg').html(err.responseText);
- $('#signup-error-msg').fadeIn();
- // re-enable 'Create Account' button
- $('.signup-btn').prop('disabled', false);
- }
- });
- }
- // if there is at least one window open (only non-Explorer windows), ask user for confirmation when navigating away
- if(feature_flags.prompt_user_when_navigation_away_from_puter){
- window.onbeforeunload = function(){
- if($(`.window:not(.window[data-app="explorer"])`).length > 0)
- return true;
- };
- }
- // -------------------------------------------------------------------------------------
- // `login` event handler
- // --------------------------------------------------------------------------------------
- $(document).on("login", async (e) => {
- // close all windows
- $('.window').close();
- // -------------------------------------------------------------------------------------
- // Load desktop, if not embedded in a popup
- // -------------------------------------------------------------------------------------
- if(!window.embedded_in_popup){
- puter.fs.stat(desktop_path, function (desktop_fsentry) {
- UIDesktop({ desktop_fsentry: desktop_fsentry });
- })
- }
- // -------------------------------------------------------------------------------------
- // If embedded in a popup, send the 'ready' event to referrer and close the popup
- // -------------------------------------------------------------------------------------
- else{
- let msg_id = url_query_params.get('msg_id');
- try{
- let data = await getUserAppToken(new URL(openerOrigin).origin);
- // This is an implicit app and the app_uid is sent back from the server
- // we cache it here so that we can use it later
- window.host_app_uid = data.app_uid;
- // send token to parent
- window.opener.postMessage({
- msg: 'puter.token',
- success: true,
- msg_id: msg_id,
- token: data.token,
- username: user.username,
- app_uid: data.app_uid,
- }, openerOrigin);
- // close popup
- if(!action || action==='sign-in'){
- window.close();
- window.open('','_self').close();
- }
- }catch(err){
- // send error to parent
- window.opener.postMessage({
- msg: 'puter.token',
- msg_id: msg_id,
- success: false,
- token: null,
- }, openerOrigin);
- // close popup
- window.close();
- window.open('','_self').close();
- }
- let app_uid;
- if(openerOrigin){
- app_uid = await getAppUIDFromOrigin(openerOrigin);
- window.host_app_uid = app_uid;
- }
- //--------------------------------------------------------------------------------------
- // Action: Show Open File Picker
- //--------------------------------------------------------------------------------------
- if(action === 'show-open-file-picker'){
- let options = url_query_params.get('options');
- options = JSON.parse(options ?? '{}');
- // Open dialog
- UIWindow({
- allowed_file_types: options?.accept,
- selectable_body: options?.multiple,
- path: '/' + window.user.username + '/Desktop',
- return_to_parent_window: true,
- show_maximize_button: false,
- show_minimize_button: false,
- title: 'Open',
- is_dir: true,
- is_openFileDialog: true,
- is_resizable: false,
- has_head: false,
- cover_page: true,
- iframe_msg_uid: msg_id,
- center: true,
- initiating_app_uuid: app_uid,
- on_close: function(){
- window.opener.postMessage({
- msg: "fileOpenCanceled",
- original_msg_id: msg_id,
- }, '*');
- }
- });
- }
- //--------------------------------------------------------------------------------------
- // Action: Show Directory Picker
- //--------------------------------------------------------------------------------------
- else if(action === 'show-directory-picker'){
- // open directory picker dialog
- UIWindow({
- path: '/' + window.user.username + '/Desktop',
- // this is the uuid of the window to which this dialog will return
- // parent_uuid: event.data.appInstanceID,
- return_to_parent_window: true,
- show_maximize_button: false,
- show_minimize_button: false,
- title: 'Open',
- is_dir: true,
- is_directoryPicker: true,
- is_resizable: false,
- has_head: false,
- cover_page: true,
- // selectable_body: is_selectable_body,
- iframe_msg_uid: msg_id,
- center: true,
- initiating_app_uuid: app_uid,
- on_close: function(){
- window.opener.postMessage({
- msg: "directoryOpenCanceled",
- original_msg_id: msg_id,
- }, '*');
- }
- });
- }
- //--------------------------------------------------------------------------------------
- // Action: Show Save File Dialog
- //--------------------------------------------------------------------------------------
- else if(action === 'show-save-file-picker'){
- let allowed_file_types = url_query_params.get('allowed_file_types');
- // send 'sendMeFileData' event to parent
- window.opener.postMessage({
- msg: 'sendMeFileData',
- }, '*');
-
- // listen for 'showSaveFilePickerPopup' event from parent
- window.addEventListener('message', async (event) => {
- if(event.data.msg !== 'showSaveFilePickerPopup')
- return;
- // Open dialog
- UIWindow({
- allowed_file_types: allowed_file_types,
- path: '/' + window.user.username + '/Desktop',
- // this is the uuid of the window to which this dialog will return
- return_to_parent_window: true,
- show_maximize_button: false,
- show_minimize_button: false,
- title: 'Save',
- is_dir: true,
- is_saveFileDialog: true,
- is_resizable: false,
- has_head: false,
- cover_page: true,
- // selectable_body: is_selectable_body,
- iframe_msg_uid: msg_id,
- center: true,
- initiating_app_uuid: app_uid,
- on_close: function(){
- window.opener.postMessage({
- msg: "fileSaveCanceled",
- original_msg_id: msg_id,
- }, '*');
- },
- onSaveFileDialogSave: async function(target_path, el_filedialog_window){
- $(el_filedialog_window).find('.window-disable-mask, .busy-indicator').show();
- let busy_init_ts = Date.now();
- let overwrite = false;
- let file_to_upload = new File([event.data.content], path.basename(target_path));
- let item_with_same_name_already_exists = true;
- while(item_with_same_name_already_exists){
- // overwrite?
- if(overwrite)
- item_with_same_name_already_exists = false;
- // upload
- try{
- const res = await puter.fs.write(
- target_path,
- file_to_upload,
- {
- dedupeName: false,
- overwrite: overwrite
- }
- );
- let file_signature = await puter.fs.sign(app_uid, {uid: res.uid, action: 'write'});
- file_signature = file_signature.items;
- item_with_same_name_already_exists = false;
- window.opener.postMessage({
- msg: "fileSaved",
- original_msg_id: msg_id,
- filename: res.name,
- saved_file: {
- name: file_signature.fsentry_name,
- readURL: file_signature.read_url,
- writeURL: file_signature.write_url,
- metadataURL: file_signature.metadata_url,
- type: file_signature.type,
- uid: file_signature.uid,
- path: `~/` + res.path.split('/').slice(2).join('/'),
- },
- }, '*');
- window.close();
- window.open('','_self').close();
- // show_save_account_notice_if_needed();
- }
- catch(err){
- // item with same name exists
- if(err.code === 'item_with_same_name_exists'){
- const alert_resp = await UIAlert({
- message: `<strong>${html_encode(err.entry_name)}</strong> already exists.`,
- buttons:[
- {
- label: i18n('replace'),
- value: 'replace',
- type: 'primary',
- },
- {
- label: i18n('cancel'),
- value: 'cancel',
- },
- ],
- parent_uuid: $(el_filedialog_window).attr('data-element_uuid'),
- })
- if(alert_resp === 'replace'){
- overwrite = true;
- }else if(alert_resp === 'cancel'){
- // enable parent window
- $(el_filedialog_window).find('.window-disable-mask, .busy-indicator').hide();
- return;
- }
- }
- else{
- console.log(err);
- // show error
- await UIAlert({
- message: err.message ?? "Upload failed.",
- parent_uuid: $(el_filedialog_window).attr('data-element_uuid'),
- });
- // enable parent window
- $(el_filedialog_window).find('.window-disable-mask, .busy-indicator').hide();
- return;
- }
- }
- }
- // done
- let busy_duration = (Date.now() - busy_init_ts);
- if( busy_duration >= busy_indicator_hide_delay){
- $(el_filedialog_window).close();
- }else{
- setTimeout(() => {
- // close this dialog
- $(el_filedialog_window).close();
- }, Math.abs(busy_indicator_hide_delay - busy_duration));
- }
- }
- });
- });
- }
- }
- })
- $(".popover, .context-menu").on("remove", function () {
- $('.window-active .window-app-iframe').css('pointer-events', 'all');
- })
- // If the document is clicked/tapped somewhere
- $(document).bind("mousedown touchstart", function (e) {
- // update last touch coordinates
- update_last_touch_coordinates(e);
- // dismiss touchstart on regular devices
- if(e.type === 'touchstart' && !isMobile.phone && !isMobile.tablet)
- return;
- // If .item-container clicked, unselect all its item children
- if($(e.target).hasClass('item-container') && !e.ctrlKey && !e.metaKey){
- $(e.target).children('.item-selected').removeClass('item-selected');
- update_explorer_footer_selected_items_count(e.target);
- }
- // If the clicked element is not a context menu, remove all context menus
- if ($(e.target).parents(".context-menu").length === 0) {
- const $ctxmenus = $(".context-menu");
- $ctxmenus.fadeOut(200, function(){
- $ctxmenus.remove();
- });
- }
- // click on anything will close all popovers, but there are some exceptions
- if(!$(e.target).hasClass('start-app')
- && !$(e.target).hasClass('launch-search')
- && !$(e.target).hasClass('launch-search-clear')
- && $(e.target).closest('.start-app').length === 0
- && !isMobile.phone && !isMobile.table
- && !$(e.target).hasClass('popover')
- && $(e.target).parents('.popover').length === 0){
- $(".popover").fadeOut(200, function(){
- $(".popover").remove();
- });
- }
- // Close all tooltips
- $('.ui-tooltip').remove();
- // rename items whose names were being edited
- if(!$(e.target).hasClass('item-name-editor')){
- // blurring an Item Name Editor will automatically trigger renaming the item
- $(".item-name-editor-active").blur();
- }
- // update active_item_container
- if($(e.target).hasClass('item-container')){
- active_item_container = e.target;
- }else{
- let ic = $(e.target).closest('.item-container')
- if(ic.length > 0){
- active_item_container = ic.get(0);
- }else{
- let pp = $(e.target).find('.item-container')
- if(pp.length > 0){
- active_item_container = pp.get(0);
- }
- }
- }
- //active element
- active_element = e.target;
- });
- $(document).bind('keydown', async function(e){
- const focused_el = document.activeElement;
- //-----------------------------------------------------------------------
- // ← ↑ → ↓: an arrow key is pressed
- //-----------------------------------------------------------------------
- if((e.which === 37 || e.which === 38 || e.which === 39 || e.which === 40)){
- // ----------------------------------------------
- // Launch menu is open
- // ----------------------------------------------
- if($('.launch-popover').length > 0){
- // If no item is selected and down arrow is pressed, select the first item
- if($('.launch-popover .start-app-card.launch-app-selected').length === 0 && (e.which === 40)){
- $('.launch-popover .start-app-card:visible').first().addClass('launch-app-selected');
- // blur search input
- $('.launch-popover .launch-search').blur();
- return false;
- }
- // if search input is focused and left or right arrow is pressed, return false
- else if($('.launch-popover .launch-search').is(':focus') && (e.which === 37 || e.which === 39)){
- return false;
- }
- else{
- // If an item is already selected, move the selection up, down, left or right
- let selected_item = $('.launch-popover .start-app-card.launch-app-selected').get(0);
- let selected_item_index = $('.launch-popover .start-app-card:visible').index(selected_item);
- let selected_item_row = Math.floor(selected_item_index / 5);
- let selected_item_col = selected_item_index % 5;
- let selected_item_row_count = Math.ceil($('.launch-popover .start-app-card:visible').length / 5);
- let selected_item_col_count = 5;
- let new_selected_item_index = selected_item_index;
- let new_selected_item_row = selected_item_row;
- let new_selected_item_col = selected_item_col;
- let new_selected_item;
-
- // if up arrow is pressed
- if(e.which === 38){
- // if this item is in the first row, up arrow should bring the focus back to the search input
- if(selected_item_row === 0){
- $('.launch-popover .launch-search').focus();
- // unselect all items
- $('.launch-popover .start-app-card.launch-app-selected').removeClass('launch-app-selected');
- // bring cursor to the end of the search input
- $('.launch-popover .launch-search').val($('.launch-popover .launch-search').val());
- return false;
- }
- // if this item is not in the first row, move the selection up
- else{
- new_selected_item_row = selected_item_row - 1;
- if(new_selected_item_row < 0)
- new_selected_item_row = selected_item_row_count - 1;
- }
- }
- // if down arrow is pressed
- else if(e.which === 40){
- new_selected_item_row = selected_item_row + 1;
- if(new_selected_item_row >= selected_item_row_count)
- new_selected_item_row = 0;
- }
- // if left arrow is pressed
- else if(e.which === 37){
- new_selected_item_col = selected_item_col - 1;
- if(new_selected_item_col < 0)
- new_selected_item_col = selected_item_col_count - 1;
- }
- // if right arrow is pressed
- else if(e.which === 39){
- new_selected_item_col = selected_item_col + 1;
- if(new_selected_item_col >= selected_item_col_count)
- new_selected_item_col = 0;
- }
- new_selected_item_index = (new_selected_item_row * selected_item_col_count) + new_selected_item_col;
- new_selected_item = $('.launch-popover .start-app-card:visible').get(new_selected_item_index);
- $(selected_item).removeClass('launch-app-selected');
- $(new_selected_item).addClass('launch-app-selected');
- // make sure the selected item is visible in the popover by scrolling the popover
- let popover = $('.launch-popover').get(0);
- let popover_height = $('.launch-popover').height();
- let popover_scroll_top = popover.getBoundingClientRect().top;
- let popover_scroll_bottom = popover_scroll_top + popover_height;
- let selected_item_top = new_selected_item.getBoundingClientRect().top;
- let selected_item_bottom = new_selected_item.getBoundingClientRect().bottom;
- let isVisible = (selected_item_top >= popover_scroll_top) && (selected_item_bottom <= popover_scroll_top + popover_height);
- if ( ! isVisible ) {
- const scrollTop = selected_item_top - popover_scroll_top;
- const scrollBot = selected_item_bottom - popover_scroll_bottom;
- if (Math.abs(scrollTop) < Math.abs(scrollBot)) {
- popover.scrollTop += scrollTop;
- } else {
- popover.scrollTop += scrollBot;
- }
- }
- return false;
- }
- }
- // ----------------------------------------------
- // A context menu is open
- // ----------------------------------------------
- else if($('.context-menu').length > 0){
- // if no item is selected and down arrow is pressed, select the first item
- if($('.context-menu-active .context-menu-item-active').length === 0 && (e.which === 40)){
- let selected_item = $('.context-menu-active .context-menu-item').get(0);
- select_ctxmenu_item(selected_item);
- return false;
- }
- // if no item is selected and up arrow is pressed, select the last item
- else if($('.context-menu-active .context-menu-item-active').length === 0 && (e.which === 38)){
- let selected_item = $('.context-menu .context-menu-item').get($('.context-menu .context-menu-item').length - 1);
- select_ctxmenu_item(selected_item);
- return false;
- }
- // if an item is selected and down arrow is pressed, select the next enabled item
- else if($('.context-menu-active .context-menu-item-active').length > 0 && (e.which === 40)){
- let selected_item = $('.context-menu-active .context-menu-item-active').get(0);
- let selected_item_index = $('.context-menu-active .context-menu-item').index(selected_item);
- let new_selected_item_index = selected_item_index + 1;
- let new_selected_item = $('.context-menu-active .context-menu-item').get(new_selected_item_index);
- while($(new_selected_item).hasClass('context-menu-item-disabled')){
- new_selected_item_index = new_selected_item_index + 1;
- new_selected_item = $('.context-menu-active .context-menu-item').get(new_selected_item_index);
- }
- select_ctxmenu_item(new_selected_item);
- return false;
- }
- // if an item is selected and up arrow is pressed, select the previous enabled item
- else if($('.context-menu-active .context-menu-item-active').length > 0 && (e.which === 38)){
- let selected_item = $('.context-menu-active .context-menu-item-active').get(0);
- let selected_item_index = $('.context-menu-active .context-menu-item').index(selected_item);
- let new_selected_item_index = selected_item_index - 1;
- let new_selected_item = $('.context-menu-active .context-menu-item').get(new_selected_item_index);
- while($(new_selected_item).hasClass('context-menu-item-disabled')){
- new_selected_item_index = new_selected_item_index - 1;
- new_selected_item = $('.context-menu-active .context-menu-item').get(new_selected_item_index);
- }
- select_ctxmenu_item(new_selected_item);
- return false;
- }
- // if right arrow is pressed, open the submenu by triggering a mouseover event
- else if($('.context-menu-active .context-menu-item-active').length > 0 && (e.which === 39)){
- const selected_item = $('.context-menu-active .context-menu-item-active').get(0);
- $(selected_item).trigger('mouseover');
- // if the submenu is open, select the first item in the submenu
- if($(selected_item).hasClass('context-menu-item-submenu') === true){
- $(selected_item).removeClass('context-menu-item-active');
- $(selected_item).addClass('context-menu-item-active-blurred');
- select_ctxmenu_item($('.context-menu[data-is-submenu="true"] .context-menu-item').get(0));
- }
- return false;
- }
- // if left arrow is pressed on a submenu, close the submenu
- else if($('.context-menu-active[data-is-submenu="true"]').length > 0 && (e.which === 37)){
- // get parent menu
- let parent_menu_id = $('.context-menu-active[data-is-submenu="true"]').data('parent-id');
- let parent_menu = $('.context-menu[data-element-id="' + parent_menu_id + '"]');
- // remove the submenu
- $('.context-menu-active[data-is-submenu="true"]').remove();
- // activate the parent menu
- $(parent_menu).addClass('context-menu-active');
- // select the item that opened the submenu
- let selected_item = $('.context-menu-active .context-menu-item-active-blurred').get(0);
- $(selected_item).removeClass('context-menu-item-active-blurred');
- $(selected_item).addClass('context-menu-item-active');
- return false;
- }
- // if enter is pressed, trigger a click event on the selected item
- else if($('.context-menu-active .context-menu-item-active').length > 0 && (e.which === 13)){
- let selected_item = $('.context-menu-active .context-menu-item-active').get(0);
- $(selected_item).trigger('click');
- return false;
- }
- }
- // ----------------------------------------------
- // Navigate items in the active item container
- // ----------------------------------------------
- else if(!$(focused_el).is('input') && !$(focused_el).is('textarea') && (e.which === 37 || e.which === 38 || e.which === 39 || e.which === 40)){
- let item_width = 110, item_height = 110, selected_item;
- // select first item in container if none is selected
- if($(active_item_container).find('.item-selected').length === 0){
- selected_item = $(active_item_container).find('.item').get(0);
- active_element = selected_item;
- $(active_item_container).find('.item-selected').removeClass('item-selected');
- $(selected_item).addClass('item-selected');
- return false;
- }
- // if Shift key is pressed and ONE item is already selected, pick that item
- else if($(active_item_container).find('.item-selected').length === 1 && e.shiftKey){
- selected_item = $(active_item_container).find('.item-selected').get(0);
- }
- // if Shift key is pressed and MORE THAN ONE item is selected, pick the latest active item
- else if($(active_item_container).find('.item-selected').length > 1 && e.shiftKey){
- selected_item = $(active_element).hasClass('item') ? active_element : $(active_element).closest('.item').get(0);
- }
- // otherwise if an item is selected, pick that item
- else if($(active_item_container).find('.item-selected').length === 1){
- selected_item = $(active_item_container).find('.item-selected').get(0);
- }
- else{
- selected_item = $(active_element).hasClass('item') ? active_element : $(active_element).closest('.item').get(0);
- }
-
- // override the default behavior of ctrl/meta key
- // in some browsers ctrl/meta key + arrow keys will scroll the page or go back/forward in history
- if(e.ctrlKey || e.metaKey){
- e.preventDefault();
- e.stopPropagation();
- }
- // get the position of the selected item
- let active_el_pos = $(selected_item).hasClass('item') ? selected_item.getBoundingClientRect() : $(selected_item).closest('.item').get(0).getBoundingClientRect();
- let xpos = active_el_pos.left + item_width/2;
- let ypos = active_el_pos.top + item_height/2;
- // these hold next item's position on the grid
- let x_nxtpos, y_nxtpos;
- // these hold the amount of pixels to scroll the container
- let x_scroll = 0, y_scroll = 0;
- // determine next item's position on the grid
- // left
- if(e.which === 37){
- x_nxtpos = (xpos - item_width) > 0 ? (xpos - item_width) : 0;
- y_nxtpos = (ypos);
- x_scroll = (item_width / 2);
- }
- // up
- else if(e.which === 38){
- x_nxtpos = (xpos);
- y_nxtpos = (ypos - item_height) > 0 ? (ypos - item_height) : 0;
- y_scroll = -1 * (item_height / 2);
- }
- // right
- else if(e.which === 39){
- x_nxtpos = (xpos + item_width);
- y_nxtpos = (ypos);
- x_scroll = -1 * (item_width / 2);
- }
- // down
- else if(e.which === 40){
- x_nxtpos = (xpos);
- y_nxtpos = (ypos + item_height);
- y_scroll = (item_height / 2);
- }
- let elements_at_next_pos = document.elementsFromPoint(x_nxtpos, y_nxtpos);
- let next_item;
- for (let index = 0; index < elements_at_next_pos.length; index++) {
- const elem_at_next_pos = elements_at_next_pos[index];
- if($(elem_at_next_pos).hasClass('item') && $(elem_at_next_pos).closest('.item-container').is(active_item_container)){
- next_item = elem_at_next_pos;
- break;
- }
- }
-
- if(next_item){
- selected_item = next_item;
- active_element = next_item;
- // if ctrl or meta key is not pressed, unselect all items
- if(!e.shiftKey){
- $(active_item_container).find('.item').removeClass('item-selected');
- }
- $(next_item).addClass('item-selected');
- window.latest_selected_item = next_item;
- // scroll to the selected item only if this was a down or up move
- if(e.which === 38 || e.which === 40)
- next_item.scrollIntoView(false);
- }
- }
- }
- //-----------------------------------------------------------------------
- // if the Esc key is pressed on a FileDialog/Alert, close that FileDialog/Alert
- //-----------------------------------------------------------------------
- else if(
- // escape key code
- e.which === 27 &&
- // active window must be a FileDialog or Alert
- ($('.window-active').hasClass('window-filedialog') || $('.window-active').hasClass('window-alert')) &&
- // either don't close if an input is focused or if the input is the filename input
- ((!$(focused_el).is('input') && !$(focused_el).is('textarea')) || $(focused_el).hasClass('savefiledialog-filename'))
- ){
- // close the FileDialog
- $('.window-active').close();
- }
- //-----------------------------------------------------------------------
- // if the Esc key is pressed on a Window Navbar Editor, deactivate the editor
- //-----------------------------------------------------------------------
- else if( e.which === 27 && $(focused_el).hasClass('window-navbar-path-input')){
- $(focused_el).blur();
- $(focused_el).val($(focused_el).closest('.window').attr('data-path'));
- $(focused_el).attr('data-path', $(focused_el).closest('.window').attr('data-path'));
- }
- //-----------------------------------------------------------------------
- // Esc key should:
- // - always close open context menus
- // - close the Launch Popover if it's open
- //-----------------------------------------------------------------------
- if( e.which === 27){
- // close open context menus
- $('.context-menu').remove();
- // close the Launch Popover if it's open
- $(".launch-popover").closest('.popover').fadeOut(200, function(){
- $(".launch-popover").closest('.popover').remove();
- });
- }
- })
- $(document).bind('keydown', async function(e){
- const focused_el = document.activeElement;
- //-----------------------------------------------------------------------
- // Shift+Delete (win)/ option+command+delete (Mac) key pressed
- // Permanent delete bypassing trash after alert
- //-----------------------------------------------------------------------
- if((e.keyCode === 46 && e.shiftKey) || (e.altKey && e.metaKey && e.keyCode === 8)) {
- let $selected_items = $(active_element).closest(`.item-container`).find(`.item-selected`);
- if($selected_items.length > 0){
- const alert_resp = await UIAlert({
- message: i18n('confirm_delete_multiple_items'),
- buttons:[
- {
- label: i18n('delete'),
- type: 'primary',
- },
- {
- label: i18n('cancel')
- },
- ]
- })
- if((alert_resp) === 'Delete'){
- for (let index = 0; index < $selected_items.length; index++) {
- const element = $selected_items[index];
- await delete_item(element);
- }
- }
- }
- return false;
- }
- //-----------------------------------------------------------------------
- // Delete (win)/ ctrl+delete (Mac) / cmd+delete (Mac) key pressed
- // Permanent delete from trash after alert or move to trash
- //-----------------------------------------------------------------------
- if(e.keyCode === 46 || (e.keyCode === 8 && (e.ctrlKey || e.metaKey))) {
- // permanent delete?
- let $selected_items = $(active_element).closest(`.item-container`).find(`.item-selected[data-path^="${trash_path + '/'}"]`);
- if($selected_items.length > 0){
- const alert_resp = await UIAlert({
- message: i18n('confirm_delete_multiple_items'),
- buttons:[
- {
- label: i18n('delete'),
- type: 'primary',
- },
- {
- label: i18n('cancel')
- },
- ]
- })
- if((alert_resp) === 'Delete'){
- for (let index = 0; index < $selected_items.length; index++) {
- const element = $selected_items[index];
- await delete_item(element);
- }
- const trash = await puter.fs.stat(trash_path);
- if(window.socket){
- window.socket.emit('trash.is_empty', {is_empty: trash.is_empty});
- }
- if(trash.is_empty){
- $(`[data-app="trash"]`).find('.taskbar-icon > img').attr('src', window.icons['trash.svg']);
- $(`.item[data-path="${html_encode(trash_path)}" i]`).find('.item-icon > img').attr('src', window.icons['trash.svg']);
- $(`.window[data-path="${html_encode(trash_path)}"]`).find('.window-head-icon').attr('src', window.icons['trash.svg']);
- }
- }
- }
- // regular delete?
- else{
- $selected_items = $(active_element).closest('.item-container').find('.item-selected');
- if($selected_items.length > 0){
- // Only delete the items if we're not renaming one.
- if ($selected_items.children('.item-name-editor-active').length === 0) {
- move_items($selected_items, trash_path);
- }
- }
- }
- return false;
- }
- //-----------------------------------------------------------------------
- // A letter or number is pressed and there is no context menu open: search items by name
- //-----------------------------------------------------------------------
- if(!e.ctrlKey && !e.metaKey && !$(focused_el).is('input') && !$(focused_el).is('textarea') && $('.context-menu').length === 0){
- if(keypress_item_seach_term !== '')
- clearTimeout(keypress_item_seach_buffer_timeout);
-
- keypress_item_seach_buffer_timeout = setTimeout(()=>{
- keypress_item_seach_term = '';
- }, 700);
- keypress_item_seach_term += e.key.toLocaleLowerCase();
- let matches= [];
- const selected_items = $(active_item_container).find(`.item-selected`).not('.item-disabled').first();
- // if one item is selected and the selected item matches the search term, don't continue search and select this item again
- if(selected_items.length === 1 && $(selected_items).attr('data-name').toLowerCase().startsWith(keypress_item_seach_term)){
- return false;
- }
- // search for matches
- let haystack = $(active_item_container).find(`.item`).not('.item-disabled');
- for(let j=0; j < haystack.length; j++){
- if($(haystack[j]).attr('data-name').toLowerCase().startsWith(keypress_item_seach_term)){
- matches.push(haystack[j])
- }
- }
- if(matches.length > 0){
- // if there are multiple matches and an item is already selected, remove all matches before the selected item
- if(selected_items.length > 0 && matches.length > 1){
- let match_index;
- for(let i=0; i < matches.length - 1; i++){
- if($(matches[i]).is(selected_items)){
- match_index = i;
- break;
- }
- }
- matches.splice(0, match_index+1);
- }
- // deselect all selected sibling items
- $(active_item_container).find(`.item-selected`).removeClass('item-selected');
- // select matching item
- $(matches[0]).not('.item-disabled').addClass('item-selected');
- matches[0].scrollIntoView(false);
- update_explorer_footer_selected_items_count($(active_element).closest('.window'));
- }
- return false;
- }
- //-----------------------------------------------------------------------
- // A letter or number is pressed and there is a context menu open: search items by name
- //-----------------------------------------------------------------------
- else if(!e.ctrlKey && !e.metaKey && !$(focused_el).is('input') && !$(focused_el).is('textarea') && $('.context-menu').length > 0){
- if(keypress_item_seach_term !== '')
- clearTimeout(keypress_item_seach_buffer_timeout);
-
- keypress_item_seach_buffer_timeout = setTimeout(()=>{
- keypress_item_seach_term = '';
- }, 700);
- keypress_item_seach_term += e.key.toLocaleLowerCase();
- let matches= [];
- const selected_items = $('.context-menu').find(`.context-menu-item-active`).first();
- // if one item is selected and the selected item matches the search term, don't continue search and select this item again
- if(selected_items.length === 1 && $(selected_items).text().toLowerCase().startsWith(keypress_item_seach_term)){
- return false;
- }
- // search for matches
- let haystack = $('.context-menu-active').find(`.context-menu-item`);
- for(let j=0; j < haystack.length; j++){
- if($(haystack[j]).text().toLowerCase().startsWith(keypress_item_seach_term)){
- matches.push(haystack[j])
- }
- }
- if(matches.length > 0){
- // if there are multiple matches and an item is already selected, remove all matches before the selected item
- if(selected_items.length > 0 && matches.length > 1){
- let match_index;
- for(let i=0; i < matches.length - 1; i++){
- if($(matches[i]).is(selected_items)){
- match_index = i;
- break;
- }
- }
- matches.splice(0, match_index+1);
- }
- // deselect all selected sibling items
- $('.context-menu').find(`.context-menu-item-active`).removeClass('context-menu-item-active');
- // select matching item
- $(matches[0]).addClass('context-menu-item-active');
- // matches[0].scrollIntoView(false);
- // update_explorer_footer_selected_items_count($(active_element).closest('.window'));
- }
- return false;
- }
- })
- $(document).bind("keyup keydown", async function(e){
- const focused_el = document.activeElement;
- //-----------------------------------------------------------------------------
- // Override ctrl/cmd + s/o
- //-----------------------------------------------------------------------------
- if((e.ctrlKey || e.metaKey) && (e.which === 83 || e.which === 79)){
- e.preventDefault()
- return false;
- }
- //-----------------------------------------------------------------------------
- // Select All
- // ctrl/command + a, will select all items on desktop and windows
- //-----------------------------------------------------------------------------
- if((e.ctrlKey || e.metaKey) && e.which === 65 && !$(focused_el).is('input') && !$(focused_el).is('textarea')){
- let $parent_container = $(active_element).closest('.item-container');
- if($parent_container.length === 0)
- $parent_container = $(active_element).find('.item-container');
- if($parent_container.attr('data-multiselectable') === 'false')
- return false;
- if($parent_container){
- $($parent_container).find('.item').not('.item-disabled').addClass('item-selected');
- update_explorer_footer_selected_items_count($parent_container.closest('.window'));
- }
- return false;
- }
- //-----------------------------------------------------------------------------
- // Close Window
- // ctrl + w, will close the active window
- //-----------------------------------------------------------------------------
- if(e.ctrlKey && e.which === 87){
- let $parent_window = $(active_element).closest('.window');
- if($parent_window.length === 0)
- $parent_window = $(active_element).find('.window');
- if($parent_window !== null){
- $($parent_window).close();
- }
- }
- //-----------------------------------------------------------------------------
- // Copy
- // ctrl/command + c, will copy selected items on the active element to the clipboard
- //-----------------------------------------------------------------------------
- if((e.ctrlKey || e.metaKey) && e.which === 67 &&
- $(mouseover_window).attr('data-is_dir') !== 'false' &&
- $(mouseover_window).attr('data-path') !== trash_path &&
- !$(focused_el).is('input') &&
- !$(focused_el).is('textarea')){
- let $selected_items;
- let parent_container = $(active_element).closest('.item-container');
- if(parent_container.length === 0)
- parent_container = $(active_element).find('.item-container');
- if(parent_container !== null){
- $selected_items = $(parent_container).find('.item-selected');
- if($selected_items.length > 0){
- clipboard = [];
- clipboard_op = 'copy';
- $selected_items.each(function() {
- // error if trash is being copied
- if($(this).attr('data-path') === trash_path){
- return;
- }
- // add to clipboard
- clipboard.push({path: $(this).attr('data-path'), uid: $(this).attr('data-uid'), metadata: $(this).attr('data-metadata')});
- })
- }
- }
- return false;
- }
- //-----------------------------------------------------------------------------
- // Cut
- // ctrl/command + x, will copy selected items on the active element to the clipboard
- //-----------------------------------------------------------------------------
- if((e.ctrlKey || e.metaKey) && e.which === 88 && !$(focused_el).is('input') && !$(focused_el).is('textarea')){
- let $selected_items;
- let parent_container = $(active_element).closest('.item-container');
- if(parent_container.length === 0)
- parent_container = $(active_element).find('.item-container');
- if(parent_container !== null){
- $selected_items = $(parent_container).find('.item-selected');
- if($selected_items.length > 0){
- clipboard = [];
- clipboard_op = 'move';
- $selected_items.each(function() {
- clipboard.push($(this).attr('data-path'));
- })
- }
- }
- return false;
- }
- //-----------------------------------------------------------------------
- // Open
- // Enter key on a selected item will open it
- //-----------------------------------------------------------------------
- if(e.which === 13 && !$(focused_el).is('input') && !$(focused_el).is('textarea') && (Date.now() - last_enter_pressed_to_rename_ts) >200
- // prevent firing twice, because this will be fired on both keyup and keydown
- && e.type === 'keydown'){
- let $selected_items;
- e.preventDefault();
- e.stopPropagation();
-
- // ---------------------------------------------
- // if this is a selected Launch menu item, open it
- // ---------------------------------------------
- if($('.launch-app-selected').length > 0){
- // close launch menu
- $(".launch-popover").fadeOut(200, function(){
- launch_app({
- name: $('.launch-app-selected').attr('data-name'),
- })
- $(".launch-popover").remove();
- });
- return false;
- }
- // ---------------------------------------------
- // if this is a selected context menu item, open it
- // ---------------------------------------------
- else if($('.context-menu-active .context-menu-item-active').length > 0 && (e.which === 13)){
- // let selected_item = $('.context-menu-active .context-menu-item-active').get(0);
- // $(selected_item).trigger('mouseover');
- // $(selected_item).trigger('click');
- let selected_item = $('.context-menu-active .context-menu-item-active').get(0);
- $(selected_item).removeClass('context-menu-item-active');
- $(selected_item).addClass('context-menu-item-active-blurred');
- $(selected_item).trigger('mouseover');
- $(selected_item).trigger('click');
- if($('.context-menu[data-is-submenu="true"]').length > 0){
- let selected_item = $('.context-menu[data-is-submenu="true"] .context-menu-item').get(0);
- select_ctxmenu_item(selected_item);
- }
- return false;
- }
- // ---------------------------------------------
- // if this is a selected item, open it
- // ---------------------------------------------
- else if(active_item_container){
- $selected_items = $(active_item_container).find('.item-selected');
- if($selected_items.length > 0){
- $selected_items.each(function() {
- open_item({
- item: this,
- new_window: e.metaKey || e.ctrlKey,
- });
- })
- }
- return false;
- }
-
- return false;
- }
- //----------------------------------------------
- // Paste
- // ctrl/command + v, will paste items from the clipboard to the active element
- //----------------------------------------------
- if((e.ctrlKey || e.metaKey) && e.which === 86 && !$(focused_el).is('input') && !$(focused_el).is('textarea')){
- let target_path, target_el;
- // continue only if there is something in the clipboard
- if(clipboard.length === 0)
- return;
- let parent_container = determine_active_container_parent();
- if(parent_container){
- target_el = parent_container;
- target_path = $(parent_container).attr('data-path');
- // don't allow pasting in Trash
- if((target_path === trash_path || target_path.startsWith(trash_path + '/')) && clipboard_op !== 'move')
- return;
- // execute clipboard operation
- if(clipboard_op === 'copy')
- copy_clipboard_items(target_path);
- else if(clipboard_op === 'move')
- move_clipboard_items(target_el, target_path);
- }
- return false;
- }
- //-----------------------------------------------------------------------------
- // Undo
- // ctrl/command + z, will undo last action
- //-----------------------------------------------------------------------------
- if((e.ctrlKey || e.metaKey) && e.which === 90){
- undo_last_action();
- return false;
- }
- });
- $(document).on('click', '.remove-permission-link', async function(e){
- const el_remove_perm_link= this;
- const perm_uid = $(el_remove_perm_link).attr('data-perm-uid');
- $.ajax({
- url: api_origin + "/remove-perm",
- type: 'POST',
- async: true,
- contentType: "application/json",
- data: JSON.stringify({
- uid: perm_uid,
- }),
- headers: {
- "Authorization": "Bearer "+auth_token
- },
- statusCode: {
- 401: function () {
- logout();
- },
- },
- success: async function (res){
- $(`[data-perm-uid="${perm_uid}"]`).hide("slide", { direction: "right" }, 300, function(e){
- $(this).remove();
- });
- },
- complete: function(){
- }
- })
- })
- // update mouse position coordinates
- $(document).mousemove(function(event){
- mouseX = event.clientX;
- mouseY = event.clientY;
-
- // mouse in top-left corner of screen
- if((mouseX < 150 && mouseY < toolbar_height + 20) || (mouseX < 20 && mouseY < 150))
- current_active_snap_zone = 'nw';
- // mouse in left edge of screen
- else if(mouseX < 20 && mouseY >= 150 && mouseY < desktop_height - 150)
- current_active_snap_zone = 'w';
- // mouse in bottom-left corner of screen
- else if(mouseX < 20 && mouseY > desktop_height - 150)
- current_active_snap_zone = 'sw';
- // mouse in right edge of screen
- else if(mouseX > desktop_width - 20 && mouseY >= 150 && mouseY < desktop_height - 150)
- current_active_snap_zone = 'e';
- // mouse in top-right corner of screen
- else if((mouseX > desktop_width - 150 && mouseY < toolbar_height + 20) || (mouseX > desktop_width - 20 && mouseY < 150))
- current_active_snap_zone = 'ne';
- // mouse in bottom-right corner of screen
- else if(mouseX > desktop_width - 20 && mouseY >= desktop_height - 150)
- current_active_snap_zone = 'se';
- // mouse in top edge of screen
- else if(mouseY < toolbar_height + 20 && mouseX >= 150 && mouseX < desktop_width - 150)
- current_active_snap_zone = 'n';
- // not in any snap zone
- else
- current_active_snap_zone = undefined;
- // mouseover_window
- var windows = document.getElementsByClassName("window");
- let active_win;
- if(windows.length > 0){
- let highest_window_zindex = 0;
- for(let i=0; i<windows.length; i++){
- const rect = windows[i].getBoundingClientRect();
- if( mouseX > rect.x && mouseX < (rect.x + rect.width) && mouseY > rect.y && mouseY < (rect.y + rect.height)){
- if(parseInt($(windows[i]).css('z-index')) >= highest_window_zindex){
- active_win = windows[i];
- highest_window_zindex = parseInt($(windows[i]).css('z-index'));
- }
- }
- }
- }
- window.mouseover_window = active_win;
- // mouseover_item_container
- var item_containers = document.getElementsByClassName("item-container");
- let active_ic;
- if(item_containers.length > 0){
- let highest_window_zindex = 0;
- for(let i=0; i<item_containers.length; i++){
- const rect = item_containers[i].getBoundingClientRect();
- if( mouseX > rect.x && mouseX < (rect.x + rect.width) && mouseY > rect.y && mouseY < (rect.y + rect.height)){
- let active_container_zindex = parseInt($(item_containers[i]).closest('.window').css('z-index'));
- if( !isNaN(active_container_zindex) && active_container_zindex >= highest_window_zindex){
- active_ic = item_containers[i];
- highest_window_zindex = active_container_zindex;
- }
- }
- }
- }
- window.mouseover_item_container = active_ic;
- });
- //--------------------------------------------------------
- // Window Activation
- //--------------------------------------------------------
- $(document).on('mousedown', function(e){
- // if taskbar or any parts of it is clicked, drop the event
- if($(e.target).hasClass('taskbar') || $(e.target).closest('.taskbar').length > 0)
- return;
- // if mouse is clicked on a window, activate it
- if(mouseover_window !== undefined){
- $(mouseover_window).focusWindow(e);
- }
- })
- // if an element has the .long-hover class, fire a long-hover event after 600ms
- $(document).on('mouseenter', '.long-hover', function(){
- let el = this;
- el.long_hover_timeout = setTimeout(() => {
- $(el).trigger('long-hover');
- }, 600);
- })
- // if an element has the .long-hover class, cancel the long-hover event if the mouse leaves
- $(document).on('mouseleave', '.long-hover', function(){
- clearTimeout(this.long_hover_timeout);
- })
- // if an element has the .long-hover class, cancel the long-hover event if the mouse leaves
- $(document).on('paste', function(event){
- event = event.originalEvent ?? event;
- let clipboardData = event.clipboardData || window.clipboardData;
- let items = clipboardData.items || clipboardData.files;
- // return if paste is on input or textarea
- if($(event.target).is('input') || $(event.target).is('textarea'))
- return;
- if(!(items instanceof DataTransferItemList))
- return;
- // upload files
- if(items?.length>0){
- let parent_container = determine_active_container_parent();
- if(parent_container){
- upload_items(items, $(parent_container).attr('data-path'));
- }
- }
- event.stopPropagation();
- event.preventDefault();
- return false;
- })
- document.addEventListener("visibilitychange", (event) => {
- if (document.visibilityState !== "visible") {
- window.doc_title_before_blur = document.title;
- if(!_.isEmpty(window.active_uploads)){
- update_title_based_on_uploads();
- }
- }else if(window.active_uploads){
- document.title = window.doc_title_before_blur ?? 'Puter';
- }
- });
- /**
- * Event handler for a custom 'logout' event attached to the document.
- * This function handles the process of logging out, including user confirmation,
- * communication with the backend, and subsequent UI updates. It takes special
- * precautions if the user is identified as using a temporary account.
- *
- * @listens Document#event:logout
- * @async
- * @param {Event} event - The JQuery event object associated with the logout event.
- * @returns {Promise<void>} - This function does not return anything meaningful, but it performs an asynchronous operation.
- */
- $(document).on("logout", async function(event) {
- // is temp user?
- if(window.user && window.user.is_temp){
- const alert_resp = await UIAlert({
- message: `<strong>Save account before logging out!</strong><p>You are using a temporary account and logging out will erase all your data.</p>`,
- buttons:[
- {
- label: i18n('save_account'),
- value: 'save_account',
- type: 'primary',
- },
- {
- label: i18n('log_out'),
- value: 'log_out',
- type: 'danger',
- },
- {
- label: i18n('cancel'),
- },
- ]
- })
- if(alert_resp === 'save_account'){
- let saved = await UIWindowSaveAccount({
- send_confirmation_code: false,
- default_username: window.user.username
- });
- if(saved)
- logout();
- }else if (alert_resp === 'log_out'){
- logout();
- }
- else{
- return;
- }
- }
- // logout
- try{
- await $.ajax({
- url: gui_origin + "/logout",
- type: 'POST',
- async: true,
- contentType: "application/json",
- headers: {
- "Authorization": "Bearer " + auth_token
- },
- statusCode: {
- 401: function () {
- },
- },
- })
- }catch(e){
- }
- // remove this user from the array of logged_in_users
- for (let i = 0; i < window.logged_in_users.length; i++) {
- if(window.logged_in_users[i].uuid === window.user.uuid){
- window.logged_in_users.splice(i, 1);
- break;
- }
- }
- // update logged_in_users in local storage
- localStorage.setItem('logged_in_users', JSON.stringify(window.logged_in_users));
- // delete this user from local storage
- window.user = null;
- localStorage.removeItem('user');
- window.auth_token = null;
- localStorage.removeItem('auth_token');
- // close all windows
- $('.window').close();
- // close all ctxmenus
- $('.context-menu').remove();
- // remove desktop
- $('.desktop').remove();
- // remove taskbar
- $('.taskbar').remove();
- // disable native browser exit confirmation
- window.onbeforeunload = null;
- // go to home page
- window.location.replace("/");
- });
- }
- function requestOpenerOrigin() {
- return new Promise((resolve, reject) => {
- if (!window.opener) {
- reject(new Error("No window.opener available"));
- return;
- }
- // Function to handle the message event
- const handleMessage = (event) => {
- // Check if the message is the expected response
- if (event.data.msg === 'originResponse') {
- // Clean up by removing the event listener
- window.removeEventListener('message', handleMessage);
- resolve(event.origin);
- }
- };
- // Set up the listener for the response
- window.addEventListener('message', handleMessage, false);
- // Send the request to the opener
- window.opener.postMessage({ msg: 'requestOrigin' }, '*');
- // Optional: Reject the promise if no response is received within a timeout
- setTimeout(() => {
- window.removeEventListener('message', handleMessage);
- reject(new Error("Response timed out"));
- }, 5000); // Timeout after 5 seconds
- });
- }
- $(document).on('click', '.generic-close-window-button', function(e){
- $(this).closest('.window').close();
- });
- // Re-calculate desktop height and width on window resize and re-position the login and signup windows
- $(window).on("resize", function () {
- // If host env is popup, don't continue because the popup window has its own resize requirements.
- if (window.embedded_in_popup)
- return;
- const ratio = window.desktop_width / window.innerWidth;
- window.desktop_height = window.innerHeight - window.toolbar_height - window.taskbar_height;
- window.desktop_width = window.innerWidth;
- // Re-center the login window
- const top = $(".window-login").position()?.top;
- const width = $(".window-login").width();
- $(".window-login").css({
- left: (window.desktop_width - width) / 2,
- top: top / ratio,
- });
- // Re-center the create account window
- const top2 = $(".window-signup").position()?.top;
- const width2 = $(".window-signup").width();
- $(".window-signup").css({
- left: (window.desktop_width - width2) / 2,
- top: top2 / ratio,
- });
- });
|