Use regular UIWindow component in UIWindowTaskManager
Docker Image CI / build-and-push-image (push) Has been cancelled
Maintain Release Merge PR / update-release-pr (push) Has been cancelled
release-please / release-please (push) Has been cancelled
test / test (18.x) (push) Has been cancelled
test / test (20.x) (push) Has been cancelled
test / test (22.x) (push) Has been cancelled

This commit is contained in:
jelveh
2025-07-25 23:59:29 -07:00
parent cadaff441c
commit 8a697652d9
2 changed files with 215 additions and 276 deletions
+125 -276
View File
@@ -19,8 +19,7 @@
import { END_HARD, END_SOFT } from "../definitions.js";
import UIAlert from "./UIAlert.js";
import UIContextMenu from "./UIContextMenu.js";
import { Component, defineComponent } from '../util/Component.js';
import UIComponentWindow from './UIComponentWindow.js';
import UIWindow from './UIWindow.js';
const end_process = async (uuid, force) => {
const svc_process = globalThis.services.get('process');
@@ -62,310 +61,122 @@ const end_process = async (uuid, force) => {
process.signal(force ? END_HARD : END_SOFT);
};
class TaskManagerTable extends Component {
static ID = 'ui.component.TaskManagerTable';
static PROPERTIES = {
tasks: { value: [] },
};
const calculate_indent_string = (indent_level, is_last_item_stack, is_last_item) => {
// Returns a string of '| ├└'
let result = '';
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;
}
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;
table {
box-sizing: border-box;
border-collapse: collapse;
width: 100%;
if (has_trunk && has_branch) {
result += '├';
} else if (has_trunk) {
result += '|';
} else if (has_branch) {
result += '└';
} else {
result += ' ';
}
thead th {
box-shadow: 0 1px 4px -2px rgba(0,0,0,0.2);
backdrop-filter: blur(2px);
position: sticky;
z-index: 100;
padding:
calc(10 * var(--scale))
calc(2.5 * var(--scale))
calc(5 * var(--scale))
calc(2.5 * var(--scale));
top: 0;
background-color: hsla(0, 0%, 100%, 0.8);
text-align: left;
border-bottom: 1px solid #e0e0e0;
}
thead th:not(:last-of-type) {
border-right: 1px solid #e0e0e0;
}
tbody > tr > td {
border-bottom: 1px solid #e0e0e0;
padding: 0 calc(2.5 * var(--scale));
vertical-align: middle;
}
`;
#svc_process = globalThis.services.get('process');
create_template ({ template }) {
$(template).html(`
<table>
<thead>
<tr>
<th>${i18n('taskmgr_header_name')}</th>
<th>${i18n('taskmgr_header_type')}</th>
<th>${i18n('taskmgr_header_status')}</th>
</tr>
</thead>
<tbody class="taskmgr-taskarea">
</tbody>
</table>
`);
}
on_ready ({ listen }) {
listen('tasks', tasks => {
const row_data = this.#iter_tasks(tasks, { indent_level: 0, is_last_item_stack: [] });
const tbody = $(this.dom_).find('.taskmgr-taskarea');
tbody.empty();
return result;
};
for (const data of row_data) {
const row = new TaskManagerRow(data);
row.attach(tbody[0]);
}
});
}
#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 += ' ';
const generate_task_rows = (items, { indent_level, is_last_item_stack }) => {
const svc_process = globalThis.services.get('process');
let rows_html = '';
for (let i = 0; i < items.length; i++) {
const item = items[i];
const is_last_item = i === items.length - 1;
const indentation = calculate_indent_string(indent_level, is_last_item_stack, is_last_item);
// Generate indentation HTML
let indentation_html = '';
for (const c of indentation) {
indentation_html += `<div class="indentcell">`;
switch (c) {
case ' ':
break;
case '|':
indentation_html += `<div class="indentcell-trunk"></div>`;
break;
case '└':
indentation_html += `<div class="indentcell-branch"></div>`;
break;
case '':
indentation_html += `<div class="indentcell-trunk"></div>`;
indentation_html += `<div class="indentcell-branch"></div>`;
break;
}
indentation_html += `</div>`;
}
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({
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(TaskManagerTable);
class TaskManagerRow extends Component {
static ID = 'ui.component.TaskManagerRow';
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 = `
<td>
<div class="task">
<div class="task-indentation"></div>
<div class="task-name"></div>
</div>
</td>
<td><span class="process-type"></span></td>
<td><span class="process-status"></span></td>
rows_html += `
<tr class="task-row" data-uuid="${item.uuid}">
<td>
<div class="task">
<div class="task-indentation">${indentation_html}</div>
<div class="task-name">${item.name}</div>
</div>
</td>
<td><span class="process-type">${i18n('process_type_' + item.type)}</span></td>
<td><span class="process-status">${i18n('process_status_' + item.status.i18n_key)}</span></td>
</tr>
`;
}
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 += `<div class="indentcell">`;
switch (c) {
case ' ':
break;
case '|':
h += `<div class="indentcell-trunk"></div>`;
break;
case '└':
h += `<div class="indentcell-branch"></div>`;
break;
case '├':
h += `<div class="indentcell-trunk"></div>`;
h += `<div class="indentcell-branch"></div>`;
break;
}
h += `</div>`;
}
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);
},
},
],
const children = svc_process.get_children_of(item.uuid);
if (children) {
rows_html += generate_task_rows(children, {
indent_level: indent_level + 1,
is_last_item_stack: [ ...is_last_item_stack, is_last_item ],
});
});
}
}
}
defineComponent(TaskManagerRow);
return rows_html;
};
const UIWindowTaskManager = async function UIWindowTaskManager () {
const svc_process = globalThis.services.get('process');
let task_manager_table = new TaskManagerTable({
tasks: [svc_process.get_init()],
});
let h = '';
const interval = setInterval(() => {
const processes = [svc_process.get_init()];
task_manager_table.set('tasks', processes);
}, 500);
h += `<div class="task-manager-container">`;
h += `<table>`;
h += `<thead>`;
h += `<tr>`;
h += `<th>${i18n('taskmgr_header_name')}</th>`;
h += `<th>${i18n('taskmgr_header_type')}</th>`;
h += `<th>${i18n('taskmgr_header_status')}</th>`;
h += `</tr>`;
h += `</thead>`;
h += `<tbody class="taskmgr-taskarea">`;
h += `</tbody>`;
h += `</table>`;
h += `</div>`;
const w = await UIComponentWindow({
component: task_manager_table,
window_class: 'window-task-manager',
const el_window = await UIWindow({
title: i18n('task_manager'),
icon: globalThis.icons['cog.svg'],
uid: null,
is_dir: false,
message: 'message',
single_instance: true,
app: 'taskmgr',
// body_icon: options.body_icon,
// backdrop: options.backdrop ?? false,
is_resizable: true,
is_droppable: false,
has_head: true,
selectable_body: true,
draggable_body: false,
allow_context_menu: false,
// allow_native_ctxmenu: true,
show_in_taskbar: true,
dominant: true,
body_content: '',
body_content: h,
width: 350,
// parent_uuid: options.parent_uuid,
// ...options.window_options,
window_class: 'window-task-manager',
window_css:{
height: 'initial',
},
@@ -379,17 +190,55 @@ const UIWindowTaskManager = async function UIWindowTaskManager () {
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);
padding: '0',
},
});
const update_tasks = () => {
const processes = [svc_process.get_init()];
const rows_html = generate_task_rows(processes, { indent_level: 0, is_last_item_stack: [] });
$(el_window).find('.taskmgr-taskarea').html(rows_html);
};
// Set up context menu for task rows
$(el_window).on('contextmenu', '.task-row', function(e) {
e.preventDefault();
const uuid = $(this).data('uuid');
UIContextMenu({
items: [
{
html: i18n('close'),
onClick: () => {
end_process(uuid);
},
},
{
html: i18n('force_quit'),
onClick: () => {
end_process(uuid, true);
},
},
],
});
});
// Initial task update
update_tasks();
// Set up interval to refresh tasks
const interval = setInterval(update_tasks, 500);
// Clean up interval when window is closed
$(el_window).on('close', () => {
clearInterval(interval);
});
return el_window;
}
export default UIWindowTaskManager;
+90
View File
@@ -2525,6 +2525,96 @@ label {
height: 40px;
}
/*****************************************************
* Task Manager
*****************************************************/
.task-manager-container {
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;
}
.task-manager-container table {
box-sizing: border-box;
border-collapse: collapse;
width: 100%;
}
.task-manager-container thead th {
box-shadow: 0 1px 4px -2px rgba(0,0,0,0.2);
backdrop-filter: blur(2px);
position: sticky;
z-index: 100;
padding: calc(10 * var(--scale)) calc(2.5 * var(--scale)) calc(5 * var(--scale)) calc(2.5 * var(--scale));
top: 0;
background-color: hsla(0, 0%, 100%, 0.8);
text-align: left;
border-bottom: 1px solid #e0e0e0;
padding: 5px;
}
.task-manager-container thead th:not(:last-of-type) {
border-right: 1px solid #e0e0e0;
}
.task-manager-container tbody > tr > td {
border-bottom: 1px solid #e0e0e0;
padding: 0 calc(2.5 * var(--scale));
vertical-align: middle;
padding-left: 0;
}
.task-manager-container 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));
}
#clock {
display: none;
color: white;