diff --git a/src/UI/UIDesktop.js b/src/UI/UIDesktop.js index 933d1294..b1f62a09 100644 --- a/src/UI/UIDesktop.js +++ b/src/UI/UIDesktop.js @@ -1195,7 +1195,7 @@ $(document).on('click', '.user-options-menu-btn', async function(e){ // Task Manager //-------------------------------------------------- { - html: i18n('task-manager'), + html: i18n('task_manager'), onClick: async function(){ UIWindowTaskManager(); } diff --git a/src/UI/UIWindow.js b/src/UI/UIWindow.js index 0fc8a887..19bf7ed9 100644 --- a/src/UI/UIWindow.js +++ b/src/UI/UIWindow.js @@ -2754,6 +2754,7 @@ window.sidebar_item_droppable = (el_window)=>{ // closes a window $.fn.close = async function(options) { options = options || {}; + console.log(options); $(this).each(async function() { const el_iframe = $(this).find('.window-app-iframe'); const app_uses_sdk = el_iframe.length > 0 && el_iframe.attr('data-appUsesSDK') === 'true'; diff --git a/src/UI/UIWindowTaskManager.js b/src/UI/UIWindowTaskManager.js index 0caa71ea..b01737a9 100644 --- a/src/UI/UIWindowTaskManager.js +++ b/src/UI/UIWindowTaskManager.js @@ -1,3 +1,6 @@ +import { END_HARD, END_SOFT } from "../definitions.js"; +import UIAlert from "./UIAlert.js"; +import UIContextMenu from "./UIContextMenu.js"; import UIWindow from "./UIWindow.js"; const UIWindowTaskManager = async function UIWindowTaskManager () { @@ -5,10 +8,11 @@ const UIWindowTaskManager = async function UIWindowTaskManager () { const w = await UIWindow({ title: i18n('task_manager'), - icon: null, + icon: globalThis.icons['cog.svg'], uid: null, is_dir: false, message: 'message', + app: 'taskmgr', // body_icon: options.body_icon, // backdrop: options.backdrop ?? false, is_resizable: true, @@ -17,9 +21,8 @@ const UIWindowTaskManager = async function UIWindowTaskManager () { selectable_body: true, draggable_body: false, allow_context_menu: true, - allow_native_ctxmenu: true, + // allow_native_ctxmenu: true, show_in_taskbar: true, - window_class: 'window-alert', dominant: true, body_content: '', width: 350, @@ -153,11 +156,49 @@ const UIWindowTaskManager = async function UIWindowTaskManager () { el_taskarea.classList.add('taskmgr-taskarea'); const tasktable = Table({ - headings: ['Name', 'Type', 'Status'] + headings: [ + i18n('taskmgr_header_name'), + i18n('taskmgr_header_type'), + i18n('taskmgr_header_status'), + ] }); el_taskarea.appendChild(tasktable.el()); + const end_process_ = async (process, force) => { + let confirmation; + + if ( process.is_init() ) { + if ( ! force ) { + confirmation = i18n('close_all_windows_confirm'); + } else { + confirmation = i18n('restart_puter_confirm'); + } + } else if ( force ) { + confirmation = i18n('end_process_force_confirm'); + } + + if ( confirmation ) { + const alert_resp = await UIAlert({ + message: confirmation, + buttons:[ + { + label: i18n('yes'), + value: true, + type: 'primary', + }, + { + label: i18n('no'), + value: false, + }, + ] + }) + if ( ! alert_resp ) return; + } + + process.signal(force ? END_HARD : END_SOFT); + } + const iter_tasks = (items, { indent_level, parent_last_item }) => { for ( let i=0 ; i < items.length; i++ ) { const row = Row(); @@ -171,10 +212,30 @@ const UIWindowTaskManager = async function UIWindowTaskManager () { }, name: item.name })); - row.add($(`${item.type}`)[0]) - row.add($('open')[0]) + row.add($(`${i18n('process_type_' + item.type)}`)[0]) + row.add($(`${i18n('process_status_' + item.status.i18n_key)}`)[0]) tasktable.add(row); + $(row.el()).on('contextmenu', () => { + UIContextMenu({ + parent_element: $(el_taskarea), + items: [ + { + html: i18n('close'), + onClick: () => { + end_process_(item); + } + }, + { + html: i18n('force_quit'), + onClick: () => { + end_process_(item, true); + } + } + ] + }); + }) + const children = svc_process.get_children_of(item.uuid); if ( children ) { iter_tasks(children, { diff --git a/src/definitions.js b/src/definitions.js index a38a3609..026ace3c 100644 --- a/src/definitions.js +++ b/src/definitions.js @@ -20,18 +20,38 @@ export class Service { // }; +export const PROCESS_INITIALIZING = { i18n_key: 'initializing' }; +export const PROCESS_RUNNING = { i18n_key: 'running' }; + +// Something is cloning these objects, so '===' checks don't work. +// To work around this, the `i` property is used to compare them. +export const END_SOFT = { i: 0, end: true, i18n_key: 'end_soft' }; +export const END_HARD = { i: 1, end: true, i18n_key: 'end_hard' }; + export class Process { constructor ({ uuid, parent, name, meta }) { this.uuid = uuid; this.parent = parent; this.name = name; this.meta = meta; + this.references = {}; + + this.status = PROCESS_INITIALIZING; this._construct(); } - _construct () {} + chstatus (status) { + this.status = status; + } + + is_init () {} + + signal (sig) { + this._signal(sig); + } + get type () { const _to_type_name = (name) => { return name.replace(/Process$/, '').toLowerCase(); @@ -44,6 +64,8 @@ export class Process { export class InitProcess extends Process { static created_ = false; + is_init () { return true; } + _construct () { this.name = 'Puter'; @@ -53,11 +75,38 @@ export class InitProcess extends Process { InitProcess.created_ = true; } + + _signal (sig) { + const svc_process = globalThis.services.get('process'); + for ( const process of svc_process.processes ) { + if ( process === this ) continue; + process.signal(sig); + } + + if ( sig.i !== END_HARD.i ) return; + + // Currently this is the only way to terminate `init`. + window.location.reload(); + } } export class PortalProcess extends Process { _construct () { this.type_ = 'app' } + _signal (sig) { + if ( sig.end ) { + $(this.references.el_win).close({ + bypass_iframe_messaging: sig.i === END_HARD.i + }); + } + } }; export class PseudoProcess extends Process { _construct () { this.type_ = 'ui' } + _signal (sig) { + if ( sig.end ) { + $(this.references.el_win).close({ + bypass_iframe_messaging: sig.i === END_HARD.i + }); + } + } }; diff --git a/src/helpers.js b/src/helpers.js index 2de6b02c..4089444d 100644 --- a/src/helpers.js +++ b/src/helpers.js @@ -36,7 +36,7 @@ import update_username_in_gui from './helpers/update_username_in_gui.js'; import update_title_based_on_uploads from './helpers/update_title_based_on_uploads.js'; import content_type_to_icon from './helpers/content_type_to_icon.js'; import UIWindowDownloadDirProg from './UI/UIWindowDownloadDirProg.js'; -import { PortalProcess, PseudoProcess } from "./definitions.js"; +import { PROCESS_RUNNING, PortalProcess, PseudoProcess } from "./definitions.js"; window.is_auth = ()=>{ if(localStorage.getItem("auth_token") === null || auth_token === null) @@ -1854,7 +1854,7 @@ window.launch_app = async (options)=>{ is_fullpage: options.is_fullpage, ...window_options, show_in_taskbar: app_info.background ? false : window_options?.show_in_taskbar, - }); + }); if ( ! app_info.background ) { $(el_win).show(); @@ -1895,6 +1895,9 @@ window.launch_app = async (options)=>{ const svc_process = globalThis.services.get('process'); svc_process.unregister(process.uuid); }); + + process.references.el_win = el; + process.chstatus(PROCESS_RUNNING); })(); } diff --git a/src/i18n/translations/en.js b/src/i18n/translations/en.js index 0bfbf314..11c2a787 100644 --- a/src/i18n/translations/en.js +++ b/src/i18n/translations/en.js @@ -39,7 +39,9 @@ const en = { change_password: "Change Password", change_ui_colors: "Change UI Colors", change_username: "Change Username", + close: 'Close', close_all_windows: "Close All Windows", + close_all_windows_confirm: "Are you sure you want to close all windows?", close_all_windows_and_log_out: 'Close Windows and Log Out', change_always_open_with: "Do you want to always open this type of file with", color: 'Color', @@ -87,11 +89,15 @@ const en = { empty_trash: 'Empty Trash', empty_trash_confirmation: `Are you sure you want to permanently delete the items in Trash?`, emptying_trash: 'Emptying Trash…', + end_hard: "End Hard", + end_process_force_confirm: "Are you sure you want to force-quit this process?", + end_soft: "End Soft", enter_password_to_confirm_delete_user: "Enter your password to confirm account deletion", feedback: "Feedback", feedback_c2a: "Please use the form below to send us your feedback, comments, and bug reports.", feedback_sent_confirmation: "Thank you for contacting us. If you have an email associated with your account, you will hear back from us as soon as possible.", fit: "Fit", + force_quit: 'Force Quit', forgot_pass_c2a: "Forgot password?", from: "From", general: "General", @@ -152,6 +158,11 @@ const en = { privacy: "Privacy", proceed_to_login: 'Proceed to login', proceed_with_account_deletion: "Proceed with Account Deletion", + process_status_initializing: "Initializing", + process_status_running: "Running", + process_type_app: 'App', + process_type_init: 'Init', + process_type_ui: 'UI', properties: "Properties", publish: "Publish", publish_as_website: 'Publish as website', @@ -170,6 +181,7 @@ const en = { replace_all: 'Replace All', resend_confirmation_code: "Re-send Confirmation Code", reset_colors: "Reset Colors", + restart_puter_confirm: "Are you sure you want to restart Puter?", restore: "Restore", saturation: 'Saturation', save_account: 'Save account', @@ -202,6 +214,9 @@ const en = { storage_puter_used: 'used by Puter', taking_longer_than_usual: 'Taking a little longer than usual. Please wait...', task_manager: "Task Manager", + taskmgr_header_name: "Name", + taskmgr_header_status: "Status", + taskmgr_header_type: "Type", terms: "Terms", text_document: 'Text document', tos_fineprint: `By clicking 'Create Free Account' you agree to Puter's {{link=terms}}Terms of Service{{/link}} and {{link=privacy}}Privacy Policy{{/link}}.`, diff --git a/src/initgui.js b/src/initgui.js index 4d0d19cd..e5ebd71c 100644 --- a/src/initgui.js +++ b/src/initgui.js @@ -39,6 +39,7 @@ import UIWindowThemeDialog from './UI/UIWindowThemeDialog.js'; import { BroadcastService } from './services/BroadcastService.js'; import UIWindowTaskManager from './UI/UIWindowTaskManager.js'; import { ProcessService } from './services/ProcessService.js'; +import { PROCESS_RUNNING } from './definitions.js'; const launch_services = async function () { const services_l_ = []; @@ -60,7 +61,11 @@ const launch_services = async function () { await instance._init(); } - UIWindowTaskManager(); + // Set init process status + { + const svc_process = globalThis.services.get('process'); + svc_process.get_init().chstatus(PROCESS_RUNNING); + } }; window.initgui = async function(){