diff --git a/src/UI/UIWindowTaskManager.js b/src/UI/UIWindowTaskManager.js index 3b4ab301..636f1132 100644 --- a/src/UI/UIWindowTaskManager.js +++ b/src/UI/UIWindowTaskManager.js @@ -2,11 +2,291 @@ import { END_HARD, END_SOFT } from "../definitions.js"; import UIAlert from "./UIAlert.js"; import UIContextMenu from "./UIContextMenu.js"; import UIWindow from "./UIWindow.js"; +import { Component, defineComponent } from '../util/Component.js'; +import UIComponentWindow from './UIComponentWindow.js'; +import Table from './Components/Table.js'; +import Placeholder from '../util/Placeholder.js'; +import TestView from './Components/TestView.js'; + +const end_process = async (uuid, force) => { + const svc_process = globalThis.services.get('process'); + const process = svc_process.get_by_uuid(uuid); + if (!process) { + console.warn(`Can't end process with uuid='${uuid}': does not exist`); + return; + } + + 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); +}; + +class TaskManagerTable extends Component { + static PROPERTIES = { + tasks: { value: [] }, + }; + + static CSS = /*css*/` + :host { + 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; + } + `; + + #svc_process = globalThis.services.get('process'); + + create_template ({ template }) { + $(template).html(` +
+ `); + } + + on_ready ({ listen }) { + this.table = new Table({ + headings: [ + i18n('taskmgr_header_name'), + i18n('taskmgr_header_type'), + i18n('taskmgr_header_status'), + ] + }); + this.table.attach(this.dom_.querySelector('.taskmgr-taskarea')); + + listen('tasks', tasks => { + // TODO: Update DOM instead of replacing the entire table + this.table.set('rows', this.#iter_tasks(tasks, { indent_level: 0, is_last_item_stack: [] })); + }); + } + + #calculate_indent_string (indent_level, is_last_item_stack, is_last_item) { + // Returns a string of '| ├└' + let result = ''; + + for ( let i=0; i < indent_level; i++ ) { + const last_cell = i === indent_level - 1; + const has_trunk = (last_cell && ( ! is_last_item )) || + (!last_cell && !is_last_item_stack[i+1]); + const has_branch = last_cell; + + if (has_trunk && has_branch) { + result += '├'; + } else if (has_trunk) { + result += '|'; + } else if (has_branch) { + result += '└'; + } else { + result += ' '; + } + } + + return result; + } + + #iter_tasks (items, { indent_level, is_last_item_stack }) { + const rows = []; + for (let i = 0; i < items.length; i++) { + const item = items[i]; + const is_last_item = i === items.length - 1; + rows.push(new TaskManagerRow({ + name: item.name, + uuid: item.uuid, + process_type: item.type, + process_status: item.status.i18n_key, + indentation: this.#calculate_indent_string(indent_level, is_last_item_stack, is_last_item), + })); + + const children = this.#svc_process.get_children_of(item.uuid); + if (children) { + rows.push(...this.#iter_tasks(children, { + indent_level: indent_level + 1, + is_last_item_stack: + [ ...is_last_item_stack, is_last_item ], + })); + } + } + return rows; + }; +} +defineComponent('c-task-manager-table', TaskManagerTable); + +class TaskManagerRow extends Component { + static PROPERTIES = { + name: {}, + uuid: {}, + process_type: {}, + process_status: {}, + indentation: { value: '' }, + }; + + static CSS = /*css*/` + :host { + display: table-row; + } + + td > span { + padding: 0 calc(2.5 * var(--scale)); + } + + .task { + display: flex; + height: calc(10 * var(--scale)); + line-height: calc(10 * var(--scale)); + } + + .task-name { + flex-grow: 1; + padding-left: calc(2.5 * var(--scale)); + } + + .task-indentation { + display: flex; + } + + .indentcell { + position: relative; + align-items: right; + width: calc(10 * var(--scale)); + height: calc(10 * var(--scale)); + } + + .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); + } + + .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)); + } + `; + + create_template ({ template }) { + template.innerHTML = ` + +
+
+
+
+ + + + `; + } + + on_ready ({ listen }) { + listen('name', name => { + $(this.dom_).find('.task-name').text(name); + }); + listen('uuid', uuid => { + this.setAttribute('data-uuid', uuid); + }); + listen('process_type', type => { + $(this.dom_).find('.process-type').text(i18n('process_type_' + type)); + }); + listen('process_status', status => { + $(this.dom_).find('.process-status').text(i18n('process_status_' + status)); + }); + listen('indentation', indentation => { + const el = $(this.dom_).find('.task-indentation'); + let h = ''; + for (const c of indentation) { + h += `
`; + switch (c) { + case ' ': + break; + case '|': + h += `
`; + break; + case '└': + h += `
`; + break; + case '├': + h += `
`; + h += `
`; + break; + } + h += `
`; + } + el.html(h); + }); + + $(this).on('contextmenu', () => { + const uuid = this.get('uuid'); + UIContextMenu({ + items: [ + { + html: i18n('close'), + onClick: () => { + end_process(uuid); + }, + }, + { + html: i18n('force_quit'), + onClick: () => { + end_process(uuid, true); + }, + }, + ], + }); + }); + } +} +defineComponent('c-task-manager-row', TaskManagerRow); const UIWindowTaskManager = async function UIWindowTaskManager () { const svc_process = globalThis.services.get('process'); - const w = await UIWindow({ + let task_manager_table = new TaskManagerTable({ + tasks: [svc_process.get_init()], + }); + + const interval = setInterval(() => { + const processes = [svc_process.get_init()]; + task_manager_table.set('tasks', processes); + }, 500); + + const w = await UIComponentWindow({ + component: task_manager_table, title: i18n('task_manager'), icon: globalThis.icons['cog.svg'], uid: null, @@ -40,220 +320,18 @@ const UIWindowTaskManager = async function UIWindowTaskManager () { var(--primary-lightness), var(--primary-alpha))`, 'backdrop-filter': 'blur(3px)', - - } + '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', + }, + on_close: () => { + clearInterval(interval); + }, }); - 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, - parent_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; - Indent({ - has_trunk: (last_cell && ( ! last_item )) || - (!last_cell && !parent_last_item[i+1]), - 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 { - el () { return el; }, - appendTo (parent) { - parent.appendChild(el); - return this; - } - }; - } - - // 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; - }, - clear () { - el_tbody.innerHTML = ''; - } - }; - }; - - 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: [ - 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(); - const item = items[i]; - const last_item = i === items.length - 1; - row.add(Task({ - placement: { - parent_last_item, - indent_level, - last_item, - }, - name: item.name - })); - 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, { - indent_level: indent_level + 1, - parent_last_item: - [...parent_last_item, last_item], - }); - } - } - }; - - 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); - } - - w_body.appendChild(el_taskarea); } export default UIWindowTaskManager; diff --git a/src/css/style.css b/src/css/style.css index a2657158..2c272663 100644 --- a/src/css/style.css +++ b/src/css/style.css @@ -3830,113 +3830,3 @@ fieldset[name=number-code] { gap: 10px; justify-content: flex-end; } - -.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 * { - 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 { - 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 { - 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)); -}