mirror of
https://github.com/HeyPuter/puter.git
synced 2026-08-25 07:27:04 +00:00
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
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:
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user