Add functionality to remove app, website, and worker cards with dedicated functions; streamline deletion process and improve UI updates for empty states.

This commit is contained in:
jelveh
2025-08-05 08:49:30 -07:00
parent 636bf6b8b3
commit 5ccdd627ee
6 changed files with 108 additions and 112 deletions

View File

@@ -340,6 +340,7 @@
<script src="./js/libs/slugify.js"></script>
<script type="module" src="./js/libs/html-entities.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js"></script>
<script src="./js/images.js"></script>
<script type="module" src="./js/apps.js"></script>
<script type="module" src="./js/workers.js"></script>
<script type="module" src="./js/websites.js"></script>

View File

@@ -25,15 +25,6 @@ const APP_CATEGORIES = [
{ id: 'lifestyle', label: 'Lifestyle' },
];
const deploying_spinner = `<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><style>.spinner_P7sC{transform-origin:center;animation:spinner_svv2 .75s infinite linear}@keyframes spinner_svv2{100%{transform:rotate(360deg)}}</style><path d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z" class="spinner_P7sC"/></svg>`;
const loading_spinner = `<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><style>.spinner_P7sC{transform-origin:center;animation:spinner_svv2 .75s infinite linear}@keyframes spinner_svv2{100%{transform:rotate(360deg)}}</style><path d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z" class="spinner_P7sC"/></svg>`;
const drop_area_placeholder = `<p>Drop your app folder and files here to deploy.</p><p style="font-size: 16px; margin-top: 0px;">HTML, JS, CSS, ...</p>`;
const index_missing_error = `Please upload an 'index.html' file or if you're uploading a directory, make sure it contains an 'index.html' file at its root.`;
const lock_svg = `<svg style="opacity: 0.8; margin-bottom: -3px; margin-left: 5px; width: 15px;" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-shield-shaded" viewBox="0 0 16 16"> <path fill-rule="evenodd" d="M8 14.933a1 1 0 0 0 .1-.025q.114-.034.294-.118c.24-.113.547-.29.893-.533a10.7 10.7 0 0 0 2.287-2.233c1.527-1.997 2.807-5.031 2.253-9.188a.48.48 0 0 0-.328-.39c-.651-.213-1.75-.56-2.837-.855C9.552 1.29 8.531 1.067 8 1.067zM5.072.56C6.157.265 7.31 0 8 0s1.843.265 2.928.56c1.11.3 2.229.655 2.887.87a1.54 1.54 0 0 1 1.044 1.262c.596 4.477-.787 7.795-2.465 9.99a11.8 11.8 0 0 1-2.517 2.453 7 7 0 0 1-1.048.625c-.28.132-.581.24-.829.24s-.548-.108-.829-.24a7 7 0 0 1-1.048-.625 11.8 11.8 0 0 1-2.517-2.453C1.928 10.487.545 7.169 1.141 2.692A1.54 1.54 0 0 1 2.185 1.43 63 63 0 0 1 5.072.56"/> </svg>`;
const lock_svg_tippy = `<svg title="Delete Protection enabled." style="opacity: 0.8; margin-bottom: -3px; margin-left: 5px; width: 15px;" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-shield-shaded tippy" viewBox="0 0 16 16"> <path fill-rule="evenodd" d="M8 14.933a1 1 0 0 0 .1-.025q.114-.034.294-.118c.24-.113.547-.29.893-.533a10.7 10.7 0 0 0 2.287-2.233c1.527-1.997 2.807-5.031 2.253-9.188a.48.48 0 0 0-.328-.39c-.651-.213-1.75-.56-2.837-.855C9.552 1.29 8.531 1.067 8 1.067zM5.072.56C6.157.265 7.31 0 8 0s1.843.265 2.928.56c1.11.3 2.229.655 2.887.87a1.54 1.54 0 0 1 1.044 1.262c.596 4.477-.787 7.795-2.465 9.99a11.8 11.8 0 0 1-2.517 2.453 7 7 0 0 1-1.048.625c-.28.132-.581.24-.829.24s-.548-.108-.829-.24a7 7 0 0 1-1.048-.625 11.8 11.8 0 0 1-2.517-2.453C1.928 10.487.545 7.169 1.141 2.692A1.54 1.54 0 0 1 2.185 1.43 63 63 0 0 1 5.072.56"/> </svg>`;
const copy_svg = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-copy" viewBox="0 0 16 16"> <path fill-rule="evenodd" d="M4 2a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2zm2-1a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1zM2 5a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1v-1h1v1a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h1v1z"/> </svg>`;
async function init_apps() {
setTimeout(async function () {
puter.ui.onLaunchedWithItems(async function (items) {
@@ -2220,6 +2211,35 @@ $(document).on('click', '.app-checkbox', function (e) {
window.last_clicked_app_checkbox_index = $('.app-checkbox').index(this);
})
function remove_app_card(app_uid, callback = null) {
$(`.app-card[data-uid="${app_uid}"]`).fadeOut(200, function() {
$(this).remove();
if ($(`.app-card`).length === 0) {
$('section:not(.sidebar)').hide();
$('#no-apps-notice').show();
} else {
$('section:not(.sidebar)').hide();
$('#app-list').show();
}
// update select-all-apps checkbox's state
if($('.app-checkbox:checked').length === 0){
$('.select-all-apps').prop('indeterminate', false);
$('.select-all-apps').prop('checked', false);
}
else if($('.app-checkbox:checked').length === $('.app-card').length){
$('.select-all-apps').prop('indeterminate', false);
$('.select-all-apps').prop('checked', true);
}
else{
$('.select-all-apps').prop('indeterminate', true);
}
count_apps();
if (callback) callback();
});
}
$(document).on('click', '.delete-apps-btn', async function (e) {
// show confirmation alert
let resp = await puter.ui.alert(`Are you sure you want to delete the selected apps?`, [
@@ -2289,17 +2309,7 @@ $(document).on('click', '.delete-apps-btn', async function (e) {
await puter.apps.delete(app_name)
// remove app card
$(`.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();
});
remove_app_card(app_uid);
try{
// get app directory
@@ -2721,17 +2731,7 @@ async function attempt_delete_app(app_name, app_title, app_uid) {
);
if (alert_resp === 'delete') {
$(`.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();
});
remove_app_card(app_uid);
// delete app
puter.apps.delete(app_name).then(async (app) => {

View File

@@ -79,9 +79,6 @@ $(document).ready(async function () {
// initialize assets directory
await initializeAssetsDirectory();
// create default worker file
await createDefaultWorkerFile();
puter.ui.showSpinner();
init_apps();

View File

@@ -0,0 +1,7 @@
window.deploying_spinner = `<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><style>.spinner_P7sC{transform-origin:center;animation:spinner_svv2 .75s infinite linear}@keyframes spinner_svv2{100%{transform:rotate(360deg)}}</style><path d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z" class="spinner_P7sC"/></svg>`;
window.loading_spinner = `<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><style>.spinner_P7sC{transform-origin:center;animation:spinner_svv2 .75s infinite linear}@keyframes spinner_svv2{100%{transform:rotate(360deg)}}</style><path d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z" class="spinner_P7sC"/></svg>`;
window.drop_area_placeholder = `<p>Drop your app folder and files here to deploy.</p><p style="font-size: 16px; margin-top: 0px;">HTML, JS, CSS, ...</p>`;
window.index_missing_error = `Please upload an 'index.html' file or if you're uploading a directory, make sure it contains an 'index.html' file at its root.`;
window.lock_svg = `<svg style="opacity: 0.8; margin-bottom: -3px; margin-left: 5px; width: 15px;" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-shield-shaded" viewBox="0 0 16 16"> <path fill-rule="evenodd" d="M8 14.933a1 1 0 0 0 .1-.025q.114-.034.294-.118c.24-.113.547-.29.893-.533a10.7 10.7 0 0 0 2.287-2.233c1.527-1.997 2.807-5.031 2.253-9.188a.48.48 0 0 0-.328-.39c-.651-.213-1.75-.56-2.837-.855C9.552 1.29 8.531 1.067 8 1.067zM5.072.56C6.157.265 7.31 0 8 0s1.843.265 2.928.56c1.11.3 2.229.655 2.887.87a1.54 1.54 0 0 1 1.044 1.262c.596 4.477-.787 7.795-2.465 9.99a11.8 11.8 0 0 1-2.517 2.453 7 7 0 0 1-1.048.625c-.28.132-.581.24-.829.24s-.548-.108-.829-.24a7 7 0 0 1-1.048-.625 11.8 11.8 0 0 1-2.517-2.453C1.928 10.487.545 7.169 1.141 2.692A1.54 1.54 0 0 1 2.185 1.43 63 63 0 0 1 5.072.56"/> </svg>`;
window.lock_svg_tippy = `<svg title="Delete Protection enabled." style="opacity: 0.8; margin-bottom: -3px; margin-left: 5px; width: 15px;" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-shield-shaded tippy" viewBox="0 0 16 16"> <path fill-rule="evenodd" d="M8 14.933a1 1 0 0 0 .1-.025q.114-.034.294-.118c.24-.113.547-.29.893-.533a10.7 10.7 0 0 0 2.287-2.233c1.527-1.997 2.807-5.031 2.253-9.188a.48.48 0 0 0-.328-.39c-.651-.213-1.75-.56-2.837-.855C9.552 1.29 8.531 1.067 8 1.067zM5.072.56C6.157.265 7.31 0 8 0s1.843.265 2.928.56c1.11.3 2.229.655 2.887.87a1.54 1.54 0 0 1 1.044 1.262c.596 4.477-.787 7.795-2.465 9.99a11.8 11.8 0 0 1-2.517 2.453 7 7 0 0 1-1.048.625c-.28.132-.581.24-.829.24s-.548-.108-.829-.24a7 7 0 0 1-1.048-.625 11.8 11.8 0 0 1-2.517-2.453C1.928 10.487.545 7.169 1.141 2.692A1.54 1.54 0 0 1 2.185 1.43 63 63 0 0 1 5.072.56"/> </svg>`;
window.copy_svg = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-copy" viewBox="0 0 16 16"> <path fill-rule="evenodd" d="M4 2a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2zm2-1a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1zM2 5a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1v-1h1v1a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h1v1z"/> </svg>`;

View File

@@ -4,7 +4,7 @@ window.websites = [];
let search_query;
window.create_website = async (name, directoryPath = null) => {
let website
let website;
// Use provided directory path or default to the default website file
const websiteDir = directoryPath || window.default_website_file;
@@ -42,7 +42,6 @@ window.refresh_websites_list = async (show_loading = false) => {
count_websites();
}
async function init_websites() {
puter.hosting.list().then((websites) => {
window.websites = websites;
@@ -288,6 +287,35 @@ $(document).on('click', '.search-clear-websites', function (e) {
$('.search-websites').removeClass('has-value');
})
function remove_website_card(website_name, callback = null) {
$(`.website-card[data-name="${website_name}"]`).fadeOut(200, function() {
$(this).remove();
if ($(`.website-card`).length === 0) {
$('section:not(.sidebar)').hide();
$('#no-websites-notice').show();
} else {
$('section:not(.sidebar)').hide();
$('#website-list').show();
}
// update select-all-websites checkbox's state
if($('.website-checkbox:checked').length === 0){
$('.select-all-websites').prop('indeterminate', false);
$('.select-all-websites').prop('checked', false);
}
else if($('.website-checkbox:checked').length === $('.website-card').length){
$('.select-all-websites').prop('indeterminate', false);
$('.select-all-websites').prop('checked', true);
}
else{
$('.select-all-websites').prop('indeterminate', true);
}
count_websites();
if (callback) callback();
});
}
$(document).on('click', '.delete-websites-btn', async function (e) {
// show confirmation alert
let resp = await puter.ui.alert(`Are you sure you want to delete the selected websites?`, [
@@ -320,17 +348,7 @@ $(document).on('click', '.delete-websites-btn', async function (e) {
await puter.hosting.delete(website_name)
// remove website card
$(`.website-card[data-name="${website_name}"]`).fadeOut(200, function name(params) {
$(this).remove();
if ($(`.website-card`).length === 0) {
$('section:not(.sidebar)').hide();
$('#no-websites-notice').show();
} else {
$('section:not(.sidebar)').hide();
$('#website-list').show();
}
count_websites();
});
remove_website_card(website_name);
try{
count_websites();
@@ -370,14 +388,14 @@ $(document).on('click', '.options-icon-website', function (e) {
label: 'Delete',
type: 'danger',
action: () => {
attempt_delete_website($(this).attr('data-website-name'));
attempt_website_deletion($(this).attr('data-website-name'));
},
},
],
});
})
async function attempt_delete_website(website_name) {
async function attempt_website_deletion(website_name) {
// confirm delete
const alert_resp = await puter.ui.alert(`Are you sure you want to premanently delete <strong>${html_encode(website_name)}.puter.site</strong>?`,
[
@@ -394,17 +412,7 @@ async function attempt_delete_website(website_name) {
if (alert_resp === 'delete') {
// remove website card and update website count
$(`.website-card[data-name="${website_name}"]`).fadeOut(200, function name(params) {
$(this).remove();
if ($(`.website-card`).length === 0) {
$('section:not(.sidebar)').hide();
$('#no-websites-notice').show();
} else {
$('section:not(.sidebar)').hide();
$('#website-list').show();
}
count_websites();
});
remove_website_card(website_name);
// delete website
puter.hosting.delete(website_name);
@@ -489,4 +497,5 @@ $(document).on('click', '.root-dir-name', function (e) {
});
}
})
export default init_websites;

View File

@@ -7,7 +7,7 @@ window.create_worker = async (name, filePath = null) => {
let worker;
// Use provided file path or default to the default worker file
const workerFile = filePath || window.default_worker_file;
const workerFile = filePath;
try {
worker = await puter.workers.create(name, workerFile);
@@ -89,33 +89,6 @@ $(document).on('click', '.create-a-worker-btn', async function (e) {
}
})
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('click', '.worker-checkbox', function (e) {
// was shift key pressed?
if (e.originalEvent && e.originalEvent.shiftKey) {
@@ -323,6 +296,35 @@ $(document).on('click', '.search-clear-workers', function (e) {
$('.search-workers').removeClass('has-value');
})
function remove_worker_card(worker_name, callback = null) {
$(`.worker-card[data-name="${worker_name}"]`).fadeOut(200, function() {
$(this).remove();
if ($(`.worker-card`).length === 0) {
$('section:not(.sidebar)').hide();
$('#no-workers-notice').show();
} else {
$('section:not(.sidebar)').hide();
$('#worker-list').show();
}
// update select-all-workers checkbox's state
if($('.worker-checkbox:checked').length === 0){
$('.select-all-workers').prop('indeterminate', false);
$('.select-all-workers').prop('checked', false);
}
else if($('.worker-checkbox:checked').length === $('.worker-card').length){
$('.select-all-workers').prop('indeterminate', false);
$('.select-all-workers').prop('checked', true);
}
else{
$('.select-all-workers').prop('indeterminate', true);
}
count_workers();
if (callback) callback();
});
}
$(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?`, [
@@ -355,17 +357,7 @@ $(document).on('click', '.delete-workers-btn', async function (e) {
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();
});
remove_worker_card(worker_name);
try{
count_workers();
@@ -398,14 +390,14 @@ $(document).on('click', '.options-icon-worker', function (e) {
label: 'Delete',
type: 'danger',
action: () => {
attempt_delete_worker($(this).attr('data-worker-name'));
attempt_worker_deletion($(this).attr('data-worker-name'));
},
},
],
});
})
async function attempt_delete_worker(worker_name) {
async function attempt_worker_deletion(worker_name) {
// confirm delete
const alert_resp = await puter.ui.alert(`Are you sure you want to premanently delete <strong>${html_encode(worker_name)}</strong>?`,
[
@@ -422,17 +414,7 @@ async function attempt_delete_worker(worker_name) {
if (alert_resp === 'delete') {
// remove worker card and update worker count
$(`.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();
});
remove_worker_card(worker_name);
// delete worker
puter.workers.delete(worker_name).then().catch(async (err) => {