From d39bb412f3306e9fd0f75c1404bd93af3b4b8aa4 Mon Sep 17 00:00:00 2001 From: KernelDeimos Date: Sat, 13 Apr 2024 19:36:47 -0400 Subject: [PATCH 1/8] Begin work on task manager --- src/UI/UIDesktop.js | 12 ++- src/UI/UIWindowTaskManager.js | 148 ++++++++++++++++++++++++++++++++ src/UI/UIWindowThemeDialog.js | 7 ++ src/css/style.css | 51 +++++++++++ src/i18n/translations/en.js | 1 + src/services/InstanceService.js | 5 ++ 6 files changed, 223 insertions(+), 1 deletion(-) create mode 100644 src/UI/UIWindowTaskManager.js create mode 100644 src/services/InstanceService.js diff --git a/src/UI/UIDesktop.js b/src/UI/UIDesktop.js index 182abb766..933d1294a 100644 --- a/src/UI/UIDesktop.js +++ b/src/UI/UIDesktop.js @@ -35,6 +35,7 @@ import new_context_menu_item from "../helpers/new_context_menu_item.js" import refresh_item_container from "../helpers/refresh_item_container.js" import changeLanguage from "../i18n/i18nChangeLanguage.js" import UIWindowSettings from "./Settings/UIWindowSettings.js" +import UIWindowTaskManager from "./UIWindowTaskManager.js" async function UIDesktop(options){ let h = ''; @@ -1182,7 +1183,7 @@ $(document).on('click', '.user-options-menu-btn', async function(e){ } }, //-------------------------------------------------- - // Change Password + // Settings //-------------------------------------------------- { html: i18n('settings'), @@ -1191,6 +1192,15 @@ $(document).on('click', '.user-options-menu-btn', async function(e){ } }, //-------------------------------------------------- + // Task Manager + //-------------------------------------------------- + { + html: i18n('task-manager'), + onClick: async function(){ + UIWindowTaskManager(); + } + }, + //-------------------------------------------------- // Contact Us //-------------------------------------------------- { diff --git a/src/UI/UIWindowTaskManager.js b/src/UI/UIWindowTaskManager.js new file mode 100644 index 000000000..f76469aa9 --- /dev/null +++ b/src/UI/UIWindowTaskManager.js @@ -0,0 +1,148 @@ +import UIWindow from "./UIWindow.js"; + +const UIWindowTaskManager = async function UIWindowTaskManager () { + const sample_data = [ + { + name: 'root', + children: [ + { + name: 'terminal', + children: [ + { + name: 'phoenix' + } + ], + children: [ + { + name: 'ai-plugin' + } + ] + }, + { + name: 'editor' + } + ] + } + ]; + + const w = await UIWindow({ + title: i18n('task_manager'), + icon: null, + uid: null, + is_dir: false, + message: 'message', + // body_icon: options.body_icon, + // backdrop: options.backdrop ?? false, + is_resizable: true, + is_droppable: false, + has_head: true, + stay_on_top: true, + selectable_body: true, + draggable_body: false, + allow_context_menu: true, + show_in_taskbar: false, + window_class: 'window-alert', + dominant: true, + body_content: '', + width: 350, + // parent_uuid: options.parent_uuid, + // ...options.window_options, + window_css:{ + height: 'initial', + }, + body_css: { + width: 'initial', + padding: '20px', + // 'background-color': `hsla( + // var(--primary-hue), + // calc(max(var(--primary-saturation) - 15%, 0%)), + // calc(min(100%,var(--primary-lightness) + 20%)), .91)`, + 'background-color': `hsla( + var(--primary-hue), + var(--primary-saturation), + var(--primary-lightness), + var(--primary-alpha))`, + 'backdrop-filter': 'blur(3px)', + + } + }); + const w_body = w.querySelector('.window-body'); + w_body.classList.add('taskmgr'); + + const Indent = ({ has_trunk, has_branch }) => { + const el = document.createElement('div'); + el.classList.add('taskmgr-indentcell'); + if ( has_trunk ) { + // Add new child element + const el_indentcell_child = document.createElement('div'); + el_indentcell_child.classList.add('taskmgr-indentcell-trunk'); + el.appendChild(el_indentcell_child); + } + if ( has_branch ) { + const el_indentcell_child = document.createElement('div'); + el_indentcell_child.classList.add('taskmgr-indentcell-branch'); + el.appendChild(el_indentcell_child); + } + + return { + appendTo (parent) { + parent.appendChild(el); + return this; + } + }; + }; + + const Task = ({ placement, name }) => { + const { indent_level, last_item } = placement; + + const el = document.createElement('div'); + el.classList.add('taskmgr-task'); + + for ( let i=0; i < indent_level; i++ ) { + const last_cell = i === indent_level - 1; + console.log('last_cell', last_cell); + console.log('last_item', last_item); + Indent({ + has_trunk: (last_cell && ( ! last_item )) || + ! last_cell, + has_branch: last_cell + }).appendTo(el); + } + + const el_title = document.createElement('div'); + el_title.classList.add('taskmgr-task-title'); + el_title.innerText = name; + el.appendChild(el_title); + + return { + appendTo (parent) { + parent.appendChild(el); + return this; + } + }; + } + + const el_tasklist = document.createElement('div'); + el_tasklist.classList.add('taskmgr-tasklist'); + const iter_tasks = (items, { indent_level }) => { + for ( let i=0 ; i < items.length; i++ ) { + const item = items[i]; + Task({ + placement: { + indent_level, + last_item: i === items.length - 1, + }, + name: item.name + }).appendTo(el_tasklist); + if ( item.children ) { + iter_tasks(item.children, { + indent_level: indent_level + 1 + }); + } + } + }; + iter_tasks(sample_data, { indent_level: 0 }); + w_body.appendChild(el_tasklist); +} + +export default UIWindowTaskManager; diff --git a/src/UI/UIWindowThemeDialog.js b/src/UI/UIWindowThemeDialog.js index 5f507daa0..67bfb9b14 100644 --- a/src/UI/UIWindowThemeDialog.js +++ b/src/UI/UIWindowThemeDialog.js @@ -1,4 +1,5 @@ import UIWindow from "./UIWindow.js"; +import UIWindowColorPicker from "./UIWindowColorPicker.js"; const UIWindowThemeDialog = async function UIWindowThemeDialog () { const services = globalThis.services; @@ -112,6 +113,12 @@ const UIWindowThemeDialog = async function UIWindowThemeDialog () { svc_theme.reset(); }) ; + Button({ label: i18n('reset_colors') }) + .appendTo(w_body) + .onPress(() => { + UIWindowColorPicker(); + }) + ; Slider({ label: i18n('hue'), diff --git a/src/css/style.css b/src/css/style.css index 7096bae29..9a09126fb 100644 --- a/src/css/style.css +++ b/src/css/style.css @@ -3781,4 +3781,55 @@ label { flex-direction: row; gap: 10px; justify-content: flex-end; +} + +.taskmgr { + box-sizing: border-box; + + --scale: 2pt; + --line-color: #6e6e6ebd; +} + +.taskmgr-tasklist { + background-color: rgba(255,255,255,0.8); + border: 2px inset rgba(127, 127, 127, 0.3); +} + +.taskmgr-indentcell { + position: relative; + align-items: right; + width: calc(10 * var(--scale)); + height: calc(10 * var(--scale)); +} + +.taskmgr-indentcell-trunk { + position: absolute; + top: 0; + left: calc(5 * var(--scale)); + width: calc(5 * var(--scale)); + height: calc(10 * var(--scale)); + border-left: 2px solid var(--line-color); +} + +.taskmgr-indentcell-branch { + position: absolute; + top: 0; + left: calc(5 * var(--scale)); + width: calc(5 * var(--scale)); + height: calc(5 * var(--scale)); + border-left: 2px solid var(--line-color); + border-bottom: 2px solid var(--line-color); + border-radius: 0 0 0 calc(2.5 * var(--scale)); +} + +.taskmgr-task { + border-bottom: 1px solid #e0e0e0; + display: flex; + height: calc(10 * var(--scale)); + line-height: calc(10 * var(--scale)); +} + +.taskmgr-task-title { + flex-grow: 1; + padding-left: calc(2.5 * var(--scale)); } \ No newline at end of file diff --git a/src/i18n/translations/en.js b/src/i18n/translations/en.js index 8e2c5c3a3..0bfbf3144 100644 --- a/src/i18n/translations/en.js +++ b/src/i18n/translations/en.js @@ -201,6 +201,7 @@ const en = { storage_usage: "Storage Usage", storage_puter_used: 'used by Puter', taking_longer_than_usual: 'Taking a little longer than usual. Please wait...', + task_manager: "Task Manager", 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/services/InstanceService.js b/src/services/InstanceService.js new file mode 100644 index 000000000..25498270b --- /dev/null +++ b/src/services/InstanceService.js @@ -0,0 +1,5 @@ +import { Service } from "../definitions"; + +export class InstanceService extends Service { + // +} \ No newline at end of file From be1bb6bc062196a39567cd593c31d588b2c8eb5b Mon Sep 17 00:00:00 2001 From: KernelDeimos Date: Sun, 14 Apr 2024 17:20:36 -0400 Subject: [PATCH 2/8] Improve task manager style --- src/UI/UIWindowTaskManager.js | 66 ++++++++++++++++++++++++++++++++--- src/css/style.css | 63 +++++++++++++++++++++++++++++++-- src/initgui.js | 3 ++ 3 files changed, 125 insertions(+), 7 deletions(-) diff --git a/src/UI/UIWindowTaskManager.js b/src/UI/UIWindowTaskManager.js index f76469aa9..21f65bb98 100644 --- a/src/UI/UIWindowTaskManager.js +++ b/src/UI/UIWindowTaskManager.js @@ -40,6 +40,7 @@ const UIWindowTaskManager = async function UIWindowTaskManager () { selectable_body: true, draggable_body: false, allow_context_menu: true, + allow_native_ctxmenu: true, show_in_taskbar: false, window_class: 'window-alert', dominant: true, @@ -115,6 +116,7 @@ const UIWindowTaskManager = async function UIWindowTaskManager () { el.appendChild(el_title); return { + el () { return el; }, appendTo (parent) { parent.appendChild(el); return this; @@ -122,18 +124,72 @@ const UIWindowTaskManager = async function UIWindowTaskManager () { }; } - const el_tasklist = document.createElement('div'); - el_tasklist.classList.add('taskmgr-tasklist'); + // https://codepen.io/fomkin/pen/gOgoBVy + const Table = ({ headings }) => { + const el_table = $(` + + + + ${headings.map(heading => + ``).join('')} + + + +
${heading}
+ `)[0]; + + const el_tbody = el_table.querySelector('tbody'); + + return { + el () { return el_table; }, + add (el) { + if ( typeof el.el === 'function' ) el = el.el(); + el_tbody.appendChild(el); + return this; + } + }; + }; + + const Row = () => { + const el_tr = document.createElement('tr'); + return { + attach (parent) { + parent.appendChild(el_tr); + return this; + }, + el () { return el_tr; }, + add (el) { + if ( typeof el.el === 'function' ) el = el.el(); + const el_td = document.createElement('td'); + el_td.appendChild(el); + el_tr.appendChild(el_td); + return this; + } + }; + }; + + const el_taskarea = document.createElement('div'); + el_taskarea.classList.add('taskmgr-taskarea'); + + const tasktable = Table({ + headings: ['Name', 'Status'] + }); + + el_taskarea.appendChild(tasktable.el()); + const iter_tasks = (items, { indent_level }) => { for ( let i=0 ; i < items.length; i++ ) { + const row = Row(); const item = items[i]; - Task({ + row.add(Task({ placement: { indent_level, last_item: i === items.length - 1, }, name: item.name - }).appendTo(el_tasklist); + })); + row.add($('open')[0]) + tasktable.add(row); if ( item.children ) { iter_tasks(item.children, { indent_level: indent_level + 1 @@ -142,7 +198,7 @@ const UIWindowTaskManager = async function UIWindowTaskManager () { } }; iter_tasks(sample_data, { indent_level: 0 }); - w_body.appendChild(el_tasklist); + w_body.appendChild(el_taskarea); } export default UIWindowTaskManager; diff --git a/src/css/style.css b/src/css/style.css index 9a09126fb..cabcd58a5 100644 --- a/src/css/style.css +++ b/src/css/style.css @@ -3785,14 +3785,74 @@ label { .taskmgr { box-sizing: border-box; + /* could have been avoided with box-sizing: border-box */ + height: calc(100% - 30px); + display: flex; + flex-direction: column; --scale: 2pt; --line-color: #6e6e6ebd; } -.taskmgr-tasklist { +.taskmgr * { + box-sizing: border-box; +} + +.taskmgr table { + border-collapse: collapse; +} + +.taskmgr-taskarea { + flex-grow: 1; + display: flex; + flex-direction: column; background-color: rgba(255,255,255,0.8); border: 2px inset rgba(127, 127, 127, 0.3); + overflow: auto; +} + +.taskmgr-taskarea table thead { +} + +.taskmgr-taskarea table th { + -webkit-box-shadow: 0 1px 4px -2px rgba(0,0,0,0.2); + box-shadow: 0 1px 4px -2px rgba(0,0,0,0.2); + backdrop-filter: blur(2px); + position: sticky; + z-index: 100; + padding: 0; + top: 0; + background-color: hsla(0, 0%, 100%, 0.8); + text-align: left; +} + +.taskmgr-taskarea table th > span { + display: inline-block; + width: 100%; + /* we set borders on this span because */ + /* borders fly away from sticky headers */ + border-bottom: 1px solid #e0e0e0; + + /* padding order: top right bottom left */ + padding: + calc(10 * var(--scale)) + calc(2.5 * var(--scale)) + calc(5 * var(--scale)) + calc(2.5 * var(--scale)); +} + +.taskmgr-taskarea table th:not(:last-of-type) > span { + /* we set borders on this span because */ + /* borders fly away from sticky headers */ + border-right: 1px solid #e0e0e0; +} + +.taskmgr-taskarea table td { + border-bottom: 1px solid #e0e0e0; +} + +.taskmgr-taskarea table td > span { + padding: 0 calc(2.5 * var(--scale)); } .taskmgr-indentcell { @@ -3823,7 +3883,6 @@ label { } .taskmgr-task { - border-bottom: 1px solid #e0e0e0; display: flex; height: calc(10 * var(--scale)); line-height: calc(10 * var(--scale)); diff --git a/src/initgui.js b/src/initgui.js index 5c156975b..83cf4ccb9 100644 --- a/src/initgui.js +++ b/src/initgui.js @@ -37,6 +37,7 @@ import determine_active_container_parent from './helpers/determine_active_contai import { ThemeService } from './services/ThemeService.js'; import UIWindowThemeDialog from './UI/UIWindowThemeDialog.js'; import { BroadcastService } from './services/BroadcastService.js'; +import UIWindowTaskManager from './UI/UIWindowTaskManager.js'; const launch_services = async function () { const services_l_ = []; @@ -56,6 +57,8 @@ const launch_services = async function () { for (const [_, instance] of services_l_) { await instance._init(); } + + UIWindowTaskManager(); }; window.initgui = async function(){ From 9d9e091a7a4eb76775d3c17bc066bcb3af2877e2 Mon Sep 17 00:00:00 2001 From: KernelDeimos Date: Sun, 14 Apr 2024 20:39:26 -0400 Subject: [PATCH 3/8] Add process management --- src/UI/UIWindowTaskManager.js | 1 + src/definitions.js | 35 ++++++++++++++++++++ src/helpers.js | 27 +++++++++++++-- src/initgui.js | 2 ++ src/services/InstanceService.js | 5 --- src/services/ProcessService.js | 58 +++++++++++++++++++++++++++++++++ 6 files changed, 121 insertions(+), 7 deletions(-) delete mode 100644 src/services/InstanceService.js create mode 100644 src/services/ProcessService.js diff --git a/src/UI/UIWindowTaskManager.js b/src/UI/UIWindowTaskManager.js index 21f65bb98..4d2dd815d 100644 --- a/src/UI/UIWindowTaskManager.js +++ b/src/UI/UIWindowTaskManager.js @@ -1,6 +1,7 @@ import UIWindow from "./UIWindow.js"; const UIWindowTaskManager = async function UIWindowTaskManager () { + const svc_process = globalThis.services.get('process'); const sample_data = [ { name: 'root', diff --git a/src/definitions.js b/src/definitions.js index 872e6315a..130d14cdd 100644 --- a/src/definitions.js +++ b/src/definitions.js @@ -19,3 +19,38 @@ export class Service { // }; + +export class Process { + constructor ({ uuid, parent, meta }) { + this.uuid = uuid; + this.parent = parent; + this.meta = meta; + + this._construct(); + } + + _construct () {} + + get type () { + const _to_type_name = (name) => { + return name.replace(/Process$/, '').toLowerCase(); + }; + return this.type || _to_type_name(this.constructor.name) || + 'invalid' + } +}; + +export class InitProccess extends Process { + static created_ = false; + + _construct () { + if (InitProccess.created_) { + throw new Error('InitProccess already created'); + } + + InitProccess.created_ = true; + } +} + +export class PortalProcess extends Process {}; +export class PseudoProcess extends Process {}; diff --git a/src/helpers.js b/src/helpers.js index ebbe9f1c6..5fdfdfd44 100644 --- a/src/helpers.js +++ b/src/helpers.js @@ -36,6 +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 } from "./definitions.js"; window.is_auth = ()=>{ if(localStorage.getItem("auth_token") === null || auth_token === null) @@ -1675,6 +1676,24 @@ window.launch_app = async (options)=>{ // add file_signature to options file_signature = file_signature.items; } + + // ----------------------------------- + // Create entry to track the "portal" + // (portals are processese in Puter's GUI) + // ----------------------------------- + const portal = new PortalProcess({ + uuid, + parent: options.parent_instance_id, + meta: { + launch_options: options, + app_info: app_info, + } + }); + const svc_process = globalThis.services.get('process'); + svc_process.register(portal); + + let el_win; + //------------------------------------ // Explorer //------------------------------------ @@ -1692,7 +1711,7 @@ window.launch_app = async (options)=>{ title = path.dirname(options.path); // open window - UIWindow({ + el_win = UIWindow({ element_uuid: uuid, icon: icon, path: options.path ?? window.home_path, @@ -1803,7 +1822,7 @@ window.launch_app = async (options)=>{ console.log('backgrounded??', app_info.background); - const el_win = UIWindow({ + el_win = UIWindow({ element_uuid: uuid, title: title, iframe_url: iframe_url.href, @@ -1854,6 +1873,10 @@ window.launch_app = async (options)=>{ }) } } + + $(el_win).on('remove', () => { + svc_process.unregister(portal.uuid); + }); } window.open_item = async function(options){ diff --git a/src/initgui.js b/src/initgui.js index 83cf4ccb9..4d0d19cd0 100644 --- a/src/initgui.js +++ b/src/initgui.js @@ -38,6 +38,7 @@ import { ThemeService } from './services/ThemeService.js'; import UIWindowThemeDialog from './UI/UIWindowThemeDialog.js'; import { BroadcastService } from './services/BroadcastService.js'; import UIWindowTaskManager from './UI/UIWindowTaskManager.js'; +import { ProcessService } from './services/ProcessService.js'; const launch_services = async function () { const services_l_ = []; @@ -53,6 +54,7 @@ const launch_services = async function () { register('broadcast', new BroadcastService()); register('theme', new ThemeService()); + register('process', new ProcessService()) for (const [_, instance] of services_l_) { await instance._init(); diff --git a/src/services/InstanceService.js b/src/services/InstanceService.js deleted file mode 100644 index 25498270b..000000000 --- a/src/services/InstanceService.js +++ /dev/null @@ -1,5 +0,0 @@ -import { Service } from "../definitions"; - -export class InstanceService extends Service { - // -} \ No newline at end of file diff --git a/src/services/ProcessService.js b/src/services/ProcessService.js new file mode 100644 index 000000000..d31832e92 --- /dev/null +++ b/src/services/ProcessService.js @@ -0,0 +1,58 @@ +import { InitProccess, Service } from "../definitions.js"; + +// The NULL UUID is also the UUID for the init process. +const NULL_UUID = '00000000-0000-0000-0000-000000000000'; + +export class ProcessService extends Service { + async _init () { + this.processes = []; + this.processes_map = new Map(); + this.uuid_to_treelist = new Map(); + + const root = new InitProccess({ + uuid: NULL_UUID, + }); + this.register_(root); + } + + register (process) { + this.register_(process); + this.attach_to_parent_(process); + } + + register_ (process) { + this.processes.push(process); + this.processes_map.set(process.uuid, process); + this.uuid_to_treelist.set(process.uuid, []); + } + + attach_to_parent_ (process) { + process.parent = process.parent ?? NULL_UUID; + const parent_list = this.uuid_to_treelist.get(process.parent); + parent_list.push(process); + } + + unregister (uuid) { + const process = this.processes_map.get(uuid); + if ( ! process ) { + throw new Error(`Process with uuid ${uuid} not found`); + } + + this.processes_map.delete(uuid); + this.processes.splice(this.processes.indexOf(process), 1); + + const parent_list = this.uuid_to_treelist.get(process.parent.uuid); + parent_list.splice(parent_list.indexOf(process), 1); + + const children = this.uuid_to_treelist.get(process.uuid); + + delete this.uuid_to_treelist[process.uuid]; + this.processes.splice(this.processes.indexOf(process), 1); + + // Transfer children to init process + for ( const child of children ) { + child.parent = NULL_UUID; + this.attach_to_parent_(child); + } + } +} From 79bfcf226b3f419565cc2c60317ee9c0c88162c0 Mon Sep 17 00:00:00 2001 From: KernelDeimos Date: Sun, 14 Apr 2024 23:23:33 -0400 Subject: [PATCH 4/8] Integrate ProcessService with task manager --- src/UI/UIWindowTaskManager.js | 52 ++++++++++++++-------------------- src/definitions.js | 5 +++- src/helpers.js | 35 ++++++++++++++++------- src/services/ProcessService.js | 12 ++++++++ 4 files changed, 61 insertions(+), 43 deletions(-) diff --git a/src/UI/UIWindowTaskManager.js b/src/UI/UIWindowTaskManager.js index 4d2dd815d..f34b0ea48 100644 --- a/src/UI/UIWindowTaskManager.js +++ b/src/UI/UIWindowTaskManager.js @@ -2,29 +2,6 @@ import UIWindow from "./UIWindow.js"; const UIWindowTaskManager = async function UIWindowTaskManager () { const svc_process = globalThis.services.get('process'); - const sample_data = [ - { - name: 'root', - children: [ - { - name: 'terminal', - children: [ - { - name: 'phoenix' - } - ], - children: [ - { - name: 'ai-plugin' - } - ] - }, - { - name: 'editor' - } - ] - } - ]; const w = await UIWindow({ title: i18n('task_manager'), @@ -95,7 +72,10 @@ const UIWindowTaskManager = async function UIWindowTaskManager () { }; const Task = ({ placement, name }) => { - const { indent_level, last_item } = placement; + const { + indent_level, last_item, + parent_last_item, + } = placement; const el = document.createElement('div'); el.classList.add('taskmgr-task'); @@ -106,7 +86,7 @@ const UIWindowTaskManager = async function UIWindowTaskManager () { console.log('last_item', last_item); Indent({ has_trunk: (last_cell && ( ! last_item )) || - ! last_cell, + (!last_cell && !parent_last_item[i]), has_branch: last_cell }).appendTo(el); } @@ -178,27 +158,37 @@ const UIWindowTaskManager = async function UIWindowTaskManager () { el_taskarea.appendChild(tasktable.el()); - const iter_tasks = (items, { indent_level }) => { + const iter_tasks = (items, { indent_level, parent_last_item }) => { + console.log('aaah', parent_last_item); for ( let i=0 ; i < items.length; i++ ) { const row = Row(); const item = items[i]; + const last_item = i === items.length - 1; row.add(Task({ placement: { + parent_last_item, indent_level, - last_item: i === items.length - 1, + last_item, }, name: item.name })); row.add($('open')[0]) tasktable.add(row); - if ( item.children ) { - iter_tasks(item.children, { - indent_level: indent_level + 1 + + const children = svc_process.get_children_of(item.uuid); + if ( children ) { + iter_tasks(children, { + indent_level: indent_level + 1, + parent_last_item: + [...parent_last_item, last_item], }); } } }; - iter_tasks(sample_data, { indent_level: 0 }); + + const processes = [svc_process.get_init()]; + + iter_tasks(processes, { indent_level: 0, parent_last_item: [] }); w_body.appendChild(el_taskarea); } diff --git a/src/definitions.js b/src/definitions.js index 130d14cdd..33e2df588 100644 --- a/src/definitions.js +++ b/src/definitions.js @@ -21,9 +21,10 @@ export class Service { }; export class Process { - constructor ({ uuid, parent, meta }) { + constructor ({ uuid, parent, name, meta }) { this.uuid = uuid; this.parent = parent; + this.name = name; this.meta = meta; this._construct(); @@ -44,6 +45,8 @@ export class InitProccess extends Process { static created_ = false; _construct () { + this.name = 'Puter'; + if (InitProccess.created_) { throw new Error('InitProccess already created'); } diff --git a/src/helpers.js b/src/helpers.js index 5fdfdfd44..ab3aa0a1c 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 } from "./definitions.js"; +import { PortalProcess, PseudoProcess } from "./definitions.js"; window.is_auth = ()=>{ if(localStorage.getItem("auth_token") === null || auth_token === null) @@ -1681,16 +1681,6 @@ window.launch_app = async (options)=>{ // Create entry to track the "portal" // (portals are processese in Puter's GUI) // ----------------------------------- - const portal = new PortalProcess({ - uuid, - parent: options.parent_instance_id, - meta: { - launch_options: options, - app_info: app_info, - } - }); - const svc_process = globalThis.services.get('process'); - svc_process.register(portal); let el_win; @@ -1698,6 +1688,17 @@ window.launch_app = async (options)=>{ // Explorer //------------------------------------ if(options.name === 'explorer'){ + const process = new PseudoProcess({ + uuid, + name: 'explorer', + parent: options.parent_instance_id, + meta: { + launch_options: options, + app_info: app_info, + } + }); + const svc_process = globalThis.services.get('process'); + svc_process.register(process); if(options.path === window.home_path){ title = 'Home'; icon = window.icons['folder-home.svg']; @@ -1727,6 +1728,18 @@ window.launch_app = async (options)=>{ // All other apps //------------------------------------ else{ + const portal = new PortalProcess({ + uuid, + name: app_info.name, + parent: options.parent_instance_id, + meta: { + launch_options: options, + app_info: app_info, + } + }); + const svc_process = globalThis.services.get('process'); + svc_process.register(portal); + //----------------------------------- // iframe_url //----------------------------------- diff --git a/src/services/ProcessService.js b/src/services/ProcessService.js index d31832e92..a06556c66 100644 --- a/src/services/ProcessService.js +++ b/src/services/ProcessService.js @@ -15,6 +15,18 @@ export class ProcessService extends Service { this.register_(root); } + get_init () { + return this.processes_map.get(NULL_UUID); + } + + get_children_of (uuid) { + if ( ! uuid ) { + uuid = NULL_UUID; + } + + return this.uuid_to_treelist.get(uuid); + } + register (process) { this.register_(process); this.attach_to_parent_(process); From 6e7fd2ca95957f3b581647b206feaa4d3934b30d Mon Sep 17 00:00:00 2001 From: KernelDeimos Date: Sun, 14 Apr 2024 23:44:52 -0400 Subject: [PATCH 5/8] Add polling --- src/UI/UIWindowTaskManager.js | 19 +++++++++++++------ src/helpers.js | 18 ++++++++++++------ src/services/ProcessService.js | 2 +- 3 files changed, 26 insertions(+), 13 deletions(-) diff --git a/src/UI/UIWindowTaskManager.js b/src/UI/UIWindowTaskManager.js index f34b0ea48..7ce8def06 100644 --- a/src/UI/UIWindowTaskManager.js +++ b/src/UI/UIWindowTaskManager.js @@ -82,11 +82,9 @@ const UIWindowTaskManager = async function UIWindowTaskManager () { for ( let i=0; i < indent_level; i++ ) { const last_cell = i === indent_level - 1; - console.log('last_cell', last_cell); - console.log('last_item', last_item); Indent({ has_trunk: (last_cell && ( ! last_item )) || - (!last_cell && !parent_last_item[i]), + (!last_cell && !parent_last_item[i+1]), has_branch: last_cell }).appendTo(el); } @@ -127,6 +125,9 @@ const UIWindowTaskManager = async function UIWindowTaskManager () { if ( typeof el.el === 'function' ) el = el.el(); el_tbody.appendChild(el); return this; + }, + clear () { + el_tbody.innerHTML = ''; } }; }; @@ -159,7 +160,6 @@ const UIWindowTaskManager = async function UIWindowTaskManager () { el_taskarea.appendChild(tasktable.el()); const iter_tasks = (items, { indent_level, parent_last_item }) => { - console.log('aaah', parent_last_item); for ( let i=0 ; i < items.length; i++ ) { const row = Row(); const item = items[i]; @@ -186,9 +186,16 @@ const UIWindowTaskManager = async function UIWindowTaskManager () { } }; - const processes = [svc_process.get_init()]; + const interval = setInterval(() => { + tasktable.clear(); + const processes = [svc_process.get_init()]; + iter_tasks(processes, { indent_level: 0, parent_last_item: [] }); + }, 500) + + w.on_close = () => { + clearInterval(interval); + } - iter_tasks(processes, { indent_level: 0, parent_last_item: [] }); w_body.appendChild(el_taskarea); } diff --git a/src/helpers.js b/src/helpers.js index ab3aa0a1c..2de6b02cb 100644 --- a/src/helpers.js +++ b/src/helpers.js @@ -1683,12 +1683,13 @@ window.launch_app = async (options)=>{ // ----------------------------------- let el_win; + let process; //------------------------------------ // Explorer //------------------------------------ if(options.name === 'explorer'){ - const process = new PseudoProcess({ + process = new PseudoProcess({ uuid, name: 'explorer', parent: options.parent_instance_id, @@ -1728,7 +1729,7 @@ window.launch_app = async (options)=>{ // All other apps //------------------------------------ else{ - const portal = new PortalProcess({ + process = new PortalProcess({ uuid, name: app_info.name, parent: options.parent_instance_id, @@ -1738,7 +1739,7 @@ window.launch_app = async (options)=>{ } }); const svc_process = globalThis.services.get('process'); - svc_process.register(portal); + svc_process.register(process); //----------------------------------- // iframe_url @@ -1887,9 +1888,14 @@ window.launch_app = async (options)=>{ } } - $(el_win).on('remove', () => { - svc_process.unregister(portal.uuid); - }); + (async () => { + const el = await el_win; + console.log('RESOV', el); + $(el).on('remove', () => { + const svc_process = globalThis.services.get('process'); + svc_process.unregister(process.uuid); + }); + })(); } window.open_item = async function(options){ diff --git a/src/services/ProcessService.js b/src/services/ProcessService.js index a06556c66..3d7c9c1ac 100644 --- a/src/services/ProcessService.js +++ b/src/services/ProcessService.js @@ -53,7 +53,7 @@ export class ProcessService extends Service { this.processes_map.delete(uuid); this.processes.splice(this.processes.indexOf(process), 1); - const parent_list = this.uuid_to_treelist.get(process.parent.uuid); + const parent_list = this.uuid_to_treelist.get(process.parent); parent_list.splice(parent_list.indexOf(process), 1); const children = this.uuid_to_treelist.get(process.uuid); From 61f1caa12294d08ac22be6b1dff4552ce25eb95e Mon Sep 17 00:00:00 2001 From: KernelDeimos Date: Sun, 14 Apr 2024 23:58:14 -0400 Subject: [PATCH 6/8] Add type column --- src/UI/UIWindowTaskManager.js | 6 +++--- src/definitions.js | 12 +++++++----- src/services/ProcessService.js | 4 ++-- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/UI/UIWindowTaskManager.js b/src/UI/UIWindowTaskManager.js index 7ce8def06..0caa71eab 100644 --- a/src/UI/UIWindowTaskManager.js +++ b/src/UI/UIWindowTaskManager.js @@ -14,12 +14,11 @@ const UIWindowTaskManager = async function UIWindowTaskManager () { is_resizable: true, is_droppable: false, has_head: true, - stay_on_top: true, selectable_body: true, draggable_body: false, allow_context_menu: true, allow_native_ctxmenu: true, - show_in_taskbar: false, + show_in_taskbar: true, window_class: 'window-alert', dominant: true, body_content: '', @@ -154,7 +153,7 @@ const UIWindowTaskManager = async function UIWindowTaskManager () { el_taskarea.classList.add('taskmgr-taskarea'); const tasktable = Table({ - headings: ['Name', 'Status'] + headings: ['Name', 'Type', 'Status'] }); el_taskarea.appendChild(tasktable.el()); @@ -172,6 +171,7 @@ const UIWindowTaskManager = async function UIWindowTaskManager () { }, name: item.name })); + row.add($(`${item.type}`)[0]) row.add($('open')[0]) tasktable.add(row); diff --git a/src/definitions.js b/src/definitions.js index 33e2df588..1bb0c7f35 100644 --- a/src/definitions.js +++ b/src/definitions.js @@ -36,24 +36,26 @@ export class Process { const _to_type_name = (name) => { return name.replace(/Process$/, '').toLowerCase(); }; - return this.type || _to_type_name(this.constructor.name) || + return this.type_ || _to_type_name(this.constructor.name) || 'invalid' } }; -export class InitProccess extends Process { +export class InitProcess extends Process { static created_ = false; _construct () { this.name = 'Puter'; - if (InitProccess.created_) { + if (InitProcess.created_) { throw new Error('InitProccess already created'); } - InitProccess.created_ = true; + InitProcess.created_ = true; } } export class PortalProcess extends Process {}; -export class PseudoProcess extends Process {}; +export class PseudoProcess extends Process { + _construct () { this.type_ = 'ui' } +}; diff --git a/src/services/ProcessService.js b/src/services/ProcessService.js index 3d7c9c1ac..8047083c2 100644 --- a/src/services/ProcessService.js +++ b/src/services/ProcessService.js @@ -1,4 +1,4 @@ -import { InitProccess, Service } from "../definitions.js"; +import { InitProcess, Service } from "../definitions.js"; // The NULL UUID is also the UUID for the init process. const NULL_UUID = '00000000-0000-0000-0000-000000000000'; @@ -9,7 +9,7 @@ export class ProcessService extends Service { this.processes_map = new Map(); this.uuid_to_treelist = new Map(); - const root = new InitProccess({ + const root = new InitProcess({ uuid: NULL_UUID, }); this.register_(root); From 7c2a3b46126b6b1042aeb4f095bb280ed32736dd Mon Sep 17 00:00:00 2001 From: KernelDeimos Date: Mon, 15 Apr 2024 02:12:39 -0400 Subject: [PATCH 7/8] Portal shows up as 'app' --- src/definitions.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/definitions.js b/src/definitions.js index 1bb0c7f35..a38a36094 100644 --- a/src/definitions.js +++ b/src/definitions.js @@ -55,7 +55,9 @@ export class InitProcess extends Process { } } -export class PortalProcess extends Process {}; +export class PortalProcess extends Process { + _construct () { this.type_ = 'app' } +}; export class PseudoProcess extends Process { _construct () { this.type_ = 'ui' } }; From 5730dc642a6fe92a4df6d155824dc83f6157345b Mon Sep 17 00:00:00 2001 From: KernelDeimos Date: Mon, 15 Apr 2024 03:37:04 -0400 Subject: [PATCH 8/8] Add process ending and i18n --- src/UI/UIDesktop.js | 2 +- src/UI/UIWindow.js | 1 + src/UI/UIWindowTaskManager.js | 73 ++++++++++++++++++++++++++++++++--- src/definitions.js | 51 +++++++++++++++++++++++- src/helpers.js | 7 +++- src/i18n/translations/en.js | 15 +++++++ src/initgui.js | 7 +++- 7 files changed, 145 insertions(+), 11 deletions(-) diff --git a/src/UI/UIDesktop.js b/src/UI/UIDesktop.js index 933d1294a..b1f62a09d 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 0fc8a887c..19bf7ed9e 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 0caa71eab..b01737a9e 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 a38a36094..026ace3ce 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 2de6b02cb..4089444d4 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 0bfbf3144..11c2a787e 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 4d0d19cd0..e5ebd71c6 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(){