Refactor app and worker management UI with context menus and improved delete functionality

This commit is contained in:
Nariman Jelveh
2025-08-03 18:24:01 -07:00
parent 2f30ce3d4d
commit e0d7a4aa17
6 changed files with 225 additions and 518 deletions
+21 -2
View File
@@ -24,7 +24,6 @@
}
html {
width: 100vw;
height: 100vh;
}
@@ -439,7 +438,7 @@ label {
}
.app-card-link:hover{
text-decoration: underline;
text-decoration: underline !important;
}
.edit-app img {
@@ -1334,4 +1333,24 @@ dialog{
color: #000; /* Optional: darken on hover */
}
.options-icon {
cursor: pointer;
opacity: 0.7;
width: 30px;
height: 30px;
border-radius: 3px;
}
.options-icon:hover {
opacity: 1;
background-color: #ededed;
}
.worker-checkbox, .app-checkbox {
width: 20px;
height: 20px;
}
.no-hover:hover {
background-color: transparent !important;
}
+2
View File
@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" fill="none" stroke="#000000" stroke-width="1" stroke-linecap="round" stroke-linejoin="miter"><line x1="11.99" y1="6" x2="12" y2="6" stroke-linecap="round" stroke-width="2"></line><line x1="11.99" y1="12" x2="12" y2="12" stroke-linecap="round" stroke-width="2"></line><line x1="11.99" y1="18" x2="12" y2="18" stroke-linecap="round" stroke-width="2"></line></svg>

After

Width:  |  Height:  |  Size: 569 B

+2 -1
View File
@@ -88,7 +88,7 @@
<!-- Footer -->
<div style="overflow: hidden; position: absolute; bottom: 0; width: 100%;">
<ul class="sidebar-nav">
<li><a href="https://developer.puter.com/" class="link-to-docs" target="_blank">Developer Documentation <img src="img/external-link.svg"></a></li>
<li class="no-hover"><a href="https://developer.puter.com/" class="link-to-docs" target="_blank">Developer Documentation <img src="img/external-link.svg"></a></li>
</ul>
<ul class="sidebar-nav sidebar-nav-social">
@@ -241,6 +241,7 @@
<th class="sort th-opens" data-column="open_count">Opens<span class="sort-arrow sort-arrow-desc"></span><span class="sort-arrow sort-arrow-asc"></span></th>
<th class="sort th-created sorted" data-column="created_at">Created<span class="sort-arrow sort-arrow-desc" style="display:inline;"></span><span class="sort-arrow sort-arrow-asc"></span></th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
+160 -114
View File
@@ -279,85 +279,6 @@ $(document).on('click', '.edit-app, .go-to-edit-app', function (e) {
})
$(document).on('click', '.delete-app', async function (e) {
let app_uid = $(this).attr('data-app-uid');
let app_title = $(this).attr('data-app-title');
let app_name = $(this).attr('data-app-name');
// get app
const app_data = await puter.apps.get(app_name, { icon_size: 16 });
if(app_data.metadata?.locked){
puter.ui.alert(`<strong>${app_data.title}</strong> is locked and cannot be deleted.`, [
{
label: 'Ok',
},
], {
type: 'warning',
});
return;
}
// confirm delete
const alert_resp = await puter.ui.alert(`Are you sure you want to premanently delete <strong>${html_encode(app_title)}</strong>?`,
[
{
label: 'Yes, delete permanently',
value: 'delete',
type: 'danger',
},
{
label: 'Cancel'
},
]
);
if (alert_resp === 'delete') {
let init_ts = Date.now();
puter.ui.showSpinner();
puter.apps.delete(app_name).then(async (app) => {
setTimeout(() => {
puter.ui.hideSpinner();
$(`.app-card[data-uid="${app_uid}"]`).fadeOut(200, function name(params) {
$(this).remove();
if ($(`.app-card`).length === 0) {
$('section:not(.sidebar)').hide();
$('#no-apps-notice').show();
} else {
$('section:not(.sidebar)').hide();
$('#app-list').show();
}
count_apps();
});
},
// make sure the modal was shown for at least 2 seconds
(Date.now() - init_ts) > 2000 ? 1 : 2000 - (Date.now() - init_ts));
// get app directory
puter.fs.stat({
path: `/${auth_username}/AppData/${dev_center_uid}/${app_uid}`,
returnSubdomains: true,
}).then(async (stat) => {
// delete subdomain associated with the app dir
puter.hosting.delete(stat.subdomains[0].subdomain)
// delete app directory
puter.fs.delete(
`/${auth_username}/AppData/${dev_center_uid}/${app_uid}`,
{ recursive: true }
)
})
}).catch(async (err) => {
setTimeout(() => {
puter.ui.hideSpinner();
puter.ui.alert(err?.message, [
{
label: 'Ok',
},
]);
},
// make sure the modal was shown for at least 2 seconds
(Date.now() - init_ts) > 2000 ? 1 : 2000 - (Date.now() - init_ts));
})
}
})
// generate app link
@@ -1532,15 +1453,6 @@ h += `</div>`;
// Toolbar
h += `<div class="app-toolbar-container" style="height: 16px; overflow: visible;">`;
h += `<div class="app-row-toolbar disable-user-select">`; // remove inline styles
h += `<span title="Open app" class="open-app-btn" data-app-uid="${html_encode(app.uid)}" data-app-name="${html_encode(app.name)}">Open</span>`;
h += `<span title="Edit app" class="edit-app" data-app-name="${html_encode(app.name)}" data-app-title="${html_encode(app.title)}" data-app-uid="${html_encode(app.uid)}">Settings</span>`;
h += `<span class="add-app-to-desktop" data-app-uid="${html_encode(app.uid)}" data-app-title="${html_encode(app.title)}">Add Shortcut to Desktop</span>`;
h += `<span title="Delete app" class="delete-app" data-app-name="${html_encode(app.name)}" data-app-title="${html_encode(app.title)}" data-app-uid="${html_encode(app.uid)}">Delete</span>`;
h += `</div>`;
h += `</div>`;
h += `</div>`; // end info column
h += `</div>`; // end row
h += `</td>`;
@@ -1573,6 +1485,10 @@ h += `</td>`;
h += `<span class="tippy approval-badge approval-badge-incentive ${app.approved_for_incentive_program ? 'active' : ''}" title="${app.approved_for_incentive_program ? '✅ Approved for the incentive program' : '❌ Not approved for the incentive program'}"></span>`;
h += `</div>`;
h += `</td>`;
// options
h += `<td style="vertical-align: middle;"><img class="options-icon options-icon-app" data-app-name="${html_encode(app.name)}" data-app-uid="${html_encode(app.uid)}" data-app-title="${html_encode(app.title)}" src="./img/options.svg"></td>`;
h += `</tr>`;
return h;
}
@@ -2161,32 +2077,6 @@ $(document).on('click', '.insta-deploy-existing-app-back', function (e) {
$(document).on('click', '.add-app-to-desktop', function (e) {
let app_title = $(this).attr('data-app-title');
let app_uid = $(this).attr('data-app-uid');
puter.fs.upload(
new File([], app_title),
`/${auth_username}/Desktop`,
{
name: app_title,
dedupeName: true,
overwrite: false,
appUID: app_uid,
}).then(async (uploaded) => {
puter.ui.alert(`<strong>${app_title}</strong> shortcut has been added to your desktop.`, [
{
label: 'Ok',
type: 'primary',
},
], {
type: 'success',
});
})
})
$('.insta-deploy-existing-app-select').on('close', function (e) {
$('.insta-deploy-existing-app-list').html('');
})
@@ -2706,4 +2596,160 @@ async function render_analytics(period){
$(document).on('click', '.stats-cell', function(e) {
edit_app_section($(this).attr('data-app-name'), 'analytics');
})
function app_context_menu(app_name, app_title, app_uid) {
puter.ui.contextMenu({
items: [
{
label: 'Open',
type: 'primary',
action: () => {
puter.ui.launchApp(app_name);
},
},
{
label: 'Edit',
type: 'primary',
action: () => {
edit_app_section(app_name, 'edit');
},
},
{
label: 'Add Shortcut to Desktop',
type: 'primary',
action: () => {
puter.fs.upload(
new File([], app_title),
`/${auth_username}/Desktop`,
{
name: app_title,
dedupeName: true,
overwrite: false,
appUID: app_uid,
}).then(async (uploaded) => {
puter.ui.alert(`<strong>${app_title}</strong> shortcut has been added to your desktop.`, [
{
label: 'Ok',
type: 'primary',
},
], {
type: 'success',
});
})
},
},
'-',
{
label: 'Delete',
type: 'danger',
action: () => {
attempt_delete_app(app_name, app_title, app_uid);
},
},
],
});
}
$(document).on('click', '.options-icon-app', function(e) {
let app_name = $(this).attr('data-app-name');
let app_title = $(this).attr('data-app-title');
let app_uid = $(this).attr('data-app-uid');
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
app_context_menu(app_name, app_title, app_uid);
})
$(document).on('contextmenu', '.app-card', function(e) {
let app_name = $(this).attr('data-name');
let app_title = $(this).attr('data-title');
let app_uid = $(this).attr('data-uid');
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
app_context_menu(app_name, app_title, app_uid);
})
async function attempt_delete_app(app_name, app_title, app_uid) {
// get app
const app_data = await puter.apps.get(app_name, { icon_size: 16 });
if(app_data.metadata?.locked){
puter.ui.alert(`<strong>${app_data.title}</strong> is locked and cannot be deleted.`, [
{
label: 'Ok',
},
], {
type: 'warning',
});
return;
}
// confirm delete
const alert_resp = await puter.ui.alert(`Are you sure you want to premanently delete <strong>${html_encode(app_title)}</strong>?`,
[
{
label: 'Yes, delete permanently',
value: 'delete',
type: 'danger',
},
{
label: 'Cancel'
},
]
);
if (alert_resp === 'delete') {
let init_ts = Date.now();
puter.ui.showSpinner();
puter.apps.delete(app_name).then(async (app) => {
setTimeout(() => {
puter.ui.hideSpinner();
$(`.app-card[data-uid="${app_uid}"]`).fadeOut(200, function name(params) {
$(this).remove();
if ($(`.app-card`).length === 0) {
$('section:not(.sidebar)').hide();
$('#no-apps-notice').show();
} else {
$('section:not(.sidebar)').hide();
$('#app-list').show();
}
count_apps();
});
},
// make sure the modal was shown for at least 2 seconds
(Date.now() - init_ts) > 2000 ? 1 : 2000 - (Date.now() - init_ts));
// get app directory
puter.fs.stat({
path: `/${auth_username}/AppData/${dev_center_uid}/${app_uid}`,
returnSubdomains: true,
}).then(async (stat) => {
// delete subdomain associated with the app dir
puter.hosting.delete(stat.subdomains[0].subdomain)
// delete app directory
puter.fs.delete(
`/${auth_username}/AppData/${dev_center_uid}/${app_uid}`,
{ recursive: true }
)
})
}).catch(async (err) => {
setTimeout(() => {
puter.ui.hideSpinner();
puter.ui.alert(err?.message, [
{
label: 'Ok',
},
]);
},
// make sure the modal was shown for at least 2 seconds
(Date.now() - init_ts) > 2000 ? 1 : 2000 - (Date.now() - init_ts));
})
}
}
export default init_apps;
-382
View File
@@ -1,386 +1,4 @@
let sortBy = 'created_at';
let sortDirection = 'desc';
window.websites = [];
let search_query;
window.create_website = async (subdomain) => {
let website = await puter.hosting.create(subdomain);
return website;
}
window.refresh_website_list = async (show_loading = false) => {
if (show_loading)
puter.ui.showSpinner();
// puter.hosting.list() returns an array of website objects
window.websites = await puter.hosting.list();
// Get workers
if (window.activeTab === 'websites' && window.websites.length > 0) {
$('.website-card').remove();
$('#no-websites-notice').hide();
$('#website-list').show();
window.websites.forEach((website) => {
// append row to website-list-table
$('#website-list-table > tbody').append(generate_website_card(website));
});
} else {
$('#no-websites-notice').show();
$('#website-list').hide();
}
count_workers();
}
async function init_workers() {
}
$(document).on('click', '.create-a-worker-btn', async function (e) {
let name = await puter.ui.prompt('Please enter a name for your worker:', 'my-awesome-worker');
if (name) {
console.log(name);
await create_worker(name);
}
})
window.createDefaultWorkerFile = async () => {
window.default_worker_file = `/${auth_username}/AppData/${dev_center_uid}/default_worker_file.js`;
let existingFile;
try {
// Check if default_worker_file exists
existingFile = await puter.fs.read(default_worker_file);
} catch (err) {
console.error('Error creating default worker file:', err);
}
if (!existingFile) {
// Create default_worker_file
await puter.fs.write(default_worker_file, `// This is an example application for Puter Workers
router.get('/', ({request}) => {
return 'Hello World'; // returns a string
});
router.get('/api/hello', ({request}) => {
return {'msg': 'hello'}; // returns a JSON object
});
router.get('/*page', ({request, params}) => {
return new Response(\`Page \${params.page} not found\`, {status: 404});
});`);
}
}
$(document).on('change', '.worker-checkbox', function (e) {
// determine if select-all checkbox should be checked, indeterminate, or unchecked
if ($('.worker-checkbox:checked').length === $('.worker-checkbox').length) {
$('.select-all-workers').prop('indeterminate', false);
$('.select-all-workers').prop('checked', true);
} else if ($('.worker-checkbox:checked').length > 0) {
$('.select-all-workers').prop('indeterminate', true);
$('.select-all-workers').prop('checked', false);
}
else {
$('.select-all-workers').prop('indeterminate', false);
$('.select-all-workers').prop('checked', false);
}
// activate row
if ($(this).is(':checked'))
$(this).closest('tr').addClass('active');
else
$(this).closest('tr').removeClass('active');
// enable delete button if at least one checkbox is checked
if ($('.worker-checkbox:checked').length > 0)
$('.delete-workers-btn').removeClass('disabled');
else
$('.delete-workers-btn').addClass('disabled');
})
$(document).on('change', '.select-all-workers', function (e) {
if ($(this).is(':checked')) {
$('.worker-checkbox').prop('checked', true);
$('.worker-card').addClass('active');
$('.delete-workers-btn').removeClass('disabled');
} else {
$('.worker-checkbox').prop('checked', false);
$('.worker-card').removeClass('active');
$('.delete-workers-btn').addClass('disabled');
}
})
$('.refresh-worker-list').on('click', function (e) {
puter.ui.showSpinner();
refresh_worker_list();
puter.ui.hideSpinner();
})
$('th.sort').on('click', function (e) {
// determine what column to sort by
const sortByColumn = $(this).attr('data-column');
// toggle sort direction
if (sortByColumn === sortBy) {
if (sortDirection === 'asc')
sortDirection = 'desc';
else
sortDirection = 'asc';
}
else {
sortBy = sortByColumn;
sortDirection = 'desc';
}
// update arrow
$('.sort-arrow').css('display', 'none');
$('#worker-list-table').find('th').removeClass('sorted');
$(this).find('.sort-arrow-' + sortDirection).css('display', 'inline');
$(this).addClass('sorted');
sort_workers();
});
function sort_workers() {
let sorted_workers;
// sort
if (sortDirection === 'asc'){
sorted_workers = workers.sort((a, b) => {
if(sortBy === 'name'){
return a[sortBy].localeCompare(b[sortBy]);
}else if(sortBy === 'created_at'){
return new Date(a[sortBy]) - new Date(b[sortBy]);
} else if(sortBy === 'user_count' || sortBy === 'open_count'){
return a.stats[sortBy] - b.stats[sortBy];
}else{
a[sortBy] > b[sortBy] ? 1 : -1
}
});
}else{
sorted_workers = workers.sort((a, b) => {
if(sortBy === 'name'){
return b[sortBy].localeCompare(a[sortBy]);
}else if(sortBy === 'created_at'){
return new Date(b[sortBy]) - new Date(a[sortBy]);
} else if(sortBy === 'user_count' || sortBy === 'open_count'){
return b.stats[sortBy] - a.stats[sortBy];
}else{
b[sortBy] > a[sortBy] ? 1 : -1
}
});
}
// refresh worker list
$('.worker-card').remove();
sorted_workers.forEach(worker => {
$('#worker-list-table > tbody').append(generate_worker_card(worker));
});
count_workers();
// show workers that match search_query and hide workers that don't
if (search_query) {
// show workers that match search_query and hide workers that don't
workers.forEach((worker) => {
if (worker.name.toLowerCase().includes(search_query.toLowerCase())) {
$(`.worker-card[data-name="${html_encode(worker.name)}"]`).show();
} else {
$(`.worker-card[data-name="${html_encode(worker.name)}"]`).hide();
}
})
}
}
function count_workers() {
let count = 0;
$('.worker-card').each(function () {
count++;
})
$('.worker-count').html(count);
return count;
}
function generate_worker_card(worker) {
return `
<tr class="worker-card" data-name="${html_encode(worker.name)}">
<td style="width:50px; vertical-align: middle;">
<input type="checkbox" class="worker-checkbox" data-worker-name="${worker.name}" style="width: 20px; height: 20px;">
</td>
<td style="font-family: monospace; font-size: 14px; vertical-align: middle;">${worker.name}</td>
<td style="font-size: 14px; vertical-align: middle;">${worker.created_at}</td>
<td style="vertical-align: middle;">
<button class="button button-danger delete-worker-btn" data-worker-name="${worker.name}">Delete</button>
</td>
</tr>
`;
}
$(document).on('input change keyup keypress keydown paste cut', '.search', function (e) {
// search apps for query
search_query = $(this).val().toLowerCase();
if (search_query === '') {
// hide 'clear search' button
$('.search-clear').hide();
// show all apps again
$(`.worker-card`).show();
} else {
// show 'clear search' button
$('.search-clear').show();
// show apps that match search_query and hide apps that don't
workers.forEach((worker) => {
if (
worker.name.toLowerCase().includes(search_query.toLowerCase())
)
{
$(`.worker-card[data-name="${worker.name}"]`).show();
} else {
$(`.worker-card[data-name="${worker.name}"]`).hide();
}
})
}
})
$(document).on('click', '.delete-workers-btn', async function (e) {
// show confirmation alert
let resp = await puter.ui.alert(`Are you sure you want to delete the selected workers?`, [
{
label: 'Delete',
type: 'danger',
value: 'delete',
},
{
label: 'Cancel',
},
], {
type: 'warning',
});
if (resp === 'delete') {
// disable delete button
$('.delete-workers-btn').addClass('disabled');
// show 'deleting' modal
puter.ui.showSpinner();
let start_ts = Date.now();
const workers = $('.worker-checkbox:checked').toArray();
// delete all checked workers
for (let worker of workers) {
let worker_name = $(worker).attr('data-worker-name');
// delete worker
await puter.workers.delete(worker_name)
// remove worker card
$(`.worker-card[data-name="${worker_name}"]`).fadeOut(200, function name(params) {
$(this).remove();
if ($(`.worker-card`).length === 0) {
$('section:not(.sidebar)').hide();
$('#no-workers-notice').show();
} else {
$('section:not(.sidebar)').hide();
$('#worker-list').show();
}
count_workers();
});
try{
count_workers();
} catch(err) {
console.log(err);
}
}
// close 'deleting' modal
setTimeout(() => {
puter.ui.hideSpinner();
if($('.worker-checkbox:checked').length === 0){
// disable delete button
$('.delete-workers-btn').addClass('disabled');
// reset the 'select all' checkbox
$('.select-all-workers').prop('indeterminate', false);
$('.select-all-workers').prop('checked', false);
}
}, (start_ts - Date.now()) > 500 ? 0 : 500);
}
})
$(document).on('click', '.delete-worker-btn', async function (e) {
let worker_name = $(this).attr('data-worker-name');
// get worker
const worker_data = await puter.workers.get(worker_name);
if(worker_data.metadata?.locked){
puter.ui.alert(`<strong>${worker_data.name}</strong> is locked and cannot be deleted.`, [
{
label: 'Ok',
},
], {
type: 'warning',
});
return;
}
// confirm delete
const alert_resp = await puter.ui.alert(`Are you sure you want to premanently delete <strong>${html_encode(worker_data.name)}</strong>?`,
[
{
label: 'Yes, delete permanently',
value: 'delete',
type: 'danger',
},
{
label: 'Cancel'
},
]
);
if (alert_resp === 'delete') {
let init_ts = Date.now();
puter.ui.showSpinner();
puter.workers.delete(worker_name).then(async (worker) => {
setTimeout(() => {
puter.ui.hideSpinner();
$(`.worker-card[data-name="${worker_name}"]`).fadeOut(200, function name(params) {
$(this).remove();
if ($(`.worker-card`).length === 0) {
$('section:not(.sidebar)').hide();
$('#no-workers-notice').show();
} else {
$('section:not(.sidebar)').hide();
$('#worker-list').show();
}
count_workers();
});
},
// make sure the modal was shown for at least 2 seconds
(Date.now() - init_ts) > 2000 ? 1 : 2000 - (Date.now() - init_ts));
// delete worker directory
puter.fs.delete(
`/${auth_username}/AppData/${dev_center_uid}/${worker_name}`,
{ recursive: true }
)
}).catch(async (err) => {
setTimeout(() => {
puter.ui.hideSpinner();
puter.ui.alert(err?.message, [
{
label: 'Ok',
},
]);
},
// make sure the modal was shown for at least 2 seconds
(Date.now() - init_ts) > 2000 ? 1 : 2000 - (Date.now() - init_ts));
})
}
})
export default init_workers;
+40 -19
View File
@@ -41,7 +41,6 @@ $(document).on('click', '.create-a-worker-btn', async function (e) {
let name = await puter.ui.prompt('Please enter a name for your worker:', 'my-awesome-worker');
if (name) {
console.log(name);
await create_worker(name);
}
})
@@ -207,13 +206,11 @@ function generate_worker_card(worker) {
return `
<tr class="worker-card" data-name="${html_encode(worker.name)}">
<td style="width:50px; vertical-align: middle;">
<input type="checkbox" class="worker-checkbox" data-worker-name="${worker.name}" style="width: 20px; height: 20px;">
<input type="checkbox" class="worker-checkbox" data-worker-name="${worker.name}">
</td>
<td style="font-family: monospace; font-size: 14px; vertical-align: middle;">${worker.name}</td>
<td style="font-size: 14px; vertical-align: middle;">${worker.created_at}</td>
<td style="vertical-align: middle;">
<button class="button button-danger delete-worker-btn" data-worker-name="${worker.name}">Delete</button>
</td>
<td style="vertical-align: middle;"><img class="options-icon options-icon-worker" data-worker-name="${worker.name}" src="./img/options.svg"></td>
</tr>
`;
}
@@ -308,8 +305,41 @@ $(document).on('click', '.delete-workers-btn', async function (e) {
}
})
$(document).on('click', '.delete-worker-btn', async function (e) {
let worker_name = $(this).attr('data-worker-name');
$(document).on('contextmenu', '.worker-card', function (e) {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
puter.ui.contextMenu({
items: [
{
label: 'Delete',
type: 'danger',
action: () => {
attempt_delete_worker($(this).attr('data-name'));
},
},
],
});
})
$(document).on('click', '.options-icon-worker', function (e) {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
puter.ui.contextMenu({
items: [
{
label: 'Delete',
type: 'danger',
action: () => {
attempt_delete_worker($(this).attr('data-worker-name'));
},
},
],
});
})
async function attempt_delete_worker(worker_name) {
// get worker
const worker_data = await puter.workers.get(worker_name);
@@ -357,15 +387,8 @@ $(document).on('click', '.delete-worker-btn', async function (e) {
count_workers();
});
},
// make sure the modal was shown for at least 2 seconds
(Date.now() - init_ts) > 2000 ? 1 : 2000 - (Date.now() - init_ts));
// delete worker directory
puter.fs.delete(
`/${auth_username}/AppData/${dev_center_uid}/${worker_name}`,
{ recursive: true }
)
// make sure the modal was shown for at least 2 seconds
(Date.now() - init_ts) > 2000 ? 1 : 2000 - (Date.now() - init_ts));
}).catch(async (err) => {
setTimeout(() => {
puter.ui.hideSpinner();
@@ -379,8 +402,6 @@ $(document).on('click', '.delete-worker-btn', async function (e) {
(Date.now() - init_ts) > 2000 ? 1 : 2000 - (Date.now() - init_ts));
})
}
})
}
export default init_workers;