diff --git a/src/dev-center/css/style.css b/src/dev-center/css/style.css index b37c23d03..0b90ffff6 100644 --- a/src/dev-center/css/style.css +++ b/src/dev-center/css/style.css @@ -35,7 +35,7 @@ body{ flex: 1; } -h1 .app-count, h1 .worker-count{ +h1 .app-count, h1 .worker-count, h1 .website-count{ font-size: 20px; color: #6d767d; font-weight: 400; @@ -70,10 +70,6 @@ h1 .app-count, h1 .worker-count{ border-style: solid; border-width: 1px; line-height: 35px; - background: -webkit-gradient(linear, left top, left bottom, from(#f6f6f6), to(#e1e1e1)); - background: linear-gradient(#f6f6f6, #e1e1e1); - -webkit-box-shadow: inset 0px 1px 0px rgb(255 255 255 / 30%), 0 1px 2px rgb(0 0 0 / 15%); - box-shadow: inset 0px 1px 0px rgb(255 255 255 / 30%), 0 1px 2px rgb(0 0 0 / 15%); border-radius: 4px; outline: none; @@ -204,7 +200,7 @@ h1 .app-count, h1 .worker-count{ padding: 0 24px; } -.refresh-app-list, .refresh-worker-list { +.refresh-app-list, .refresh-worker-list, .refresh-website-list { width:40px; padding: 10px; float: right; @@ -217,7 +213,7 @@ h1 .app-count, h1 .worker-count{ opacity: 0.9; } -.refresh-app-list:hover, .refresh-worker-list:hover { +.refresh-app-list:hover, .refresh-worker-list:hover, .refresh-website-list:hover { opacity: 1; } @@ -260,7 +256,6 @@ section { background: white; overflow: hidden; position: relative; - } .app-card:hover, .website-card:hover, .worker-card:hover { @@ -457,7 +452,7 @@ label { left: 0; z-index: 1000; display: block; - padding: 30px 16px; + padding: 30px 16px 0; overflow-x: hidden; overflow-y: auto; background-color: rgb(249, 249, 249); @@ -524,15 +519,15 @@ label { background-color: #3a85ff; } -.sidebar-nav > li .app-count, .sidebar-nav > li .worker-count { - font-size: 14px; +.sidebar-nav > li .app-count, .sidebar-nav > li .worker-count, .sidebar-nav > li .website-count { + font-size: 16px; color: #6d767d; font-weight: 400; margin-left: 10px; float: right; } -.tab-btn.active .app-count, .tab-btn.active .worker-count{ +.tab-btn.active .app-count, .tab-btn.active .worker-count, .tab-btn.active .website-count{ color: #f6f6f6; } @@ -753,7 +748,7 @@ th.sorted{ user-select: none !important; } -.my-apps-title, .my-workers-title { +.my-apps-title, .my-workers-title, .my-websites-title { font-size: 27px; margin-top: 0px; float: left; @@ -952,18 +947,19 @@ ol li:before { background-color: rgba(0, 0, 0, 0.4); } -.create-an-app-btn{ - float:right; margin-bottom: 10px; +.create-an-app-btn, .create-a-website-btn{ + float:right; + margin-bottom: 10px; } -.create-an-app-btn img{ +.create-an-app-btn img, .create-a-website-btn img{ width: 20px; margin-bottom: -5px; margin-right: 10px; } -.create-a-worker-btn{ +.create-a-worker-btn, .create-a-website-btn{ float:right; margin-bottom: 10px; } -.create-a-worker-btn img{ +.create-a-worker-btn img, .create-a-website-btn img{ width: 20px; margin-bottom: -5px; margin-right: 5px; @@ -976,7 +972,7 @@ ol li:before { .ip-terms-notice{ font-size: 12px; margin-top: 20px; margin-bottom: 0; } -.app-list-nav, .worker-list-nav{ +.app-list-nav, .worker-list-nav, .website-list-nav{ overflow: hidden; margin-bottom: 40px; margin-top: 10px; } .back-to-main-btn{ @@ -989,7 +985,7 @@ ol li:before { display: flex; align-items: center; } -.app-title{ +.app-title, .website-title{ margin-top:0px; margin-bottom: 0; } @@ -1346,7 +1342,7 @@ dialog{ background-color: #ededed; } -.worker-checkbox, .app-checkbox { +.worker-checkbox, .app-checkbox, .website-checkbox { width: 20px; height: 20px; } diff --git a/src/dev-center/index.html b/src/dev-center/index.html index 7a2a752ae..a87c52662 100644 --- a/src/dev-center/index.html +++ b/src/dev-center/index.html @@ -290,7 +290,6 @@

My Websites

-
@@ -299,6 +298,7 @@
+
diff --git a/src/dev-center/js/apps.js b/src/dev-center/js/apps.js index f30799c03..68ab0ca40 100644 --- a/src/dev-center/js/apps.js +++ b/src/dev-center/js/apps.js @@ -2601,12 +2601,13 @@ function app_context_menu(app_name, app_title, app_uid) { puter.ui.contextMenu({ items: [ { - label: 'Open', + label: 'Open App', type: 'primary', action: () => { puter.ui.launchApp(app_name); }, }, + '-', { label: 'Edit', type: 'primary', diff --git a/src/dev-center/js/dev-center.js b/src/dev-center/js/dev-center.js index c25cfb59c..7eacdc8dd 100644 --- a/src/dev-center/js/dev-center.js +++ b/src/dev-center/js/dev-center.js @@ -115,6 +115,13 @@ $(document).on('click', '.tab-btn', async function (e) { activeTab = 'workers'; } // --------------------------------------------------------------- + // Websites tab + // --------------------------------------------------------------- + else if ($(this).attr('data-tab') === 'websites') { + refresh_websites_list(); + activeTab = 'websites'; + } + // --------------------------------------------------------------- // Payout Method tab // --------------------------------------------------------------- else if ($(this).attr('data-tab') === 'payout-method') { diff --git a/src/dev-center/js/websites.js b/src/dev-center/js/websites.js index 0bb9f1062..c7f0d052b 100644 --- a/src/dev-center/js/websites.js +++ b/src/dev-center/js/websites.js @@ -1,4 +1,349 @@ -async function init_workers() { +let sortBy = 'created_at'; +let sortDirection = 'desc'; +window.websites = []; +let search_query; + +window.create_website = async (name) => { + let website = await puter.hosting.create(name, window.default_website_file); + + return website; } -export default init_workers; \ No newline at end of file +window.refresh_websites_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 websites + 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_websites(); +} + + +async function init_websites() { +} + +$(document).on('click', '.create-a-website-btn', async function (e) { + let name = await puter.ui.prompt('Please enter a name for your website:', 'my-awesome-website'); + + if (name) { + await create_website(name); + } +}) + +$(document).on('change', '.website-checkbox', function (e) { + // determine if select-all checkbox should be checked, indeterminate, or unchecked + if ($('.website-checkbox:checked').length === $('.website-checkbox').length) { + $('.select-all-websites').prop('indeterminate', false); + $('.select-all-websites').prop('checked', true); + } else if ($('.website-checkbox:checked').length > 0) { + $('.select-all-websites').prop('indeterminate', true); + $('.select-all-websites').prop('checked', false); + } + else { + $('.select-all-websites').prop('indeterminate', false); + $('.select-all-websites').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 ($('.website-checkbox:checked').length > 0) + $('.delete-websites-btn').removeClass('disabled'); + else + $('.delete-websites-btn').addClass('disabled'); +}) + +$(document).on('change', '.select-all-websites', function (e) { + if ($(this).is(':checked')) { + $('.website-checkbox').prop('checked', true); + $('.website-card').addClass('active'); + $('.delete-websites-btn').removeClass('disabled'); + } else { + $('.website-checkbox').prop('checked', false); + $('.website-card').removeClass('active'); + $('.delete-websites-btn').addClass('disabled'); + } +}) + +$('.refresh-website-list').on('click', function (e) { + puter.ui.showSpinner(); + refresh_website_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'); + $('#website-list-table').find('th').removeClass('sorted'); + $(this).find('.sort-arrow-' + sortDirection).css('display', 'inline'); + $(this).addClass('sorted'); + + sort_websites(); +}); + +function sort_websites() { + let sorted_websites; + + // sort + if (sortDirection === 'asc'){ + sorted_websites = websites.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_websites = websites.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 website list + $('.website-card').remove(); + sorted_websites.forEach(website => { + $('#website-list-table > tbody').append(generate_website_card(website)); + }); + + count_websites(); + + // show websites that match search_query and hide websites that don't + if (search_query) { + // show websites that match search_query and hide websites that don't + websites.forEach((website) => { + if (website.name.toLowerCase().includes(search_query.toLowerCase())) { + $(`.website-card[data-name="${html_encode(website.name)}"]`).show(); + } else { + $(`.website-card[data-name="${html_encode(website.name)}"]`).hide(); + } + }) + } +} + +function count_websites() { + let count = 0; + $('.website-card').each(function () { + count++; + }) + $('.website-count').html(count); + return count; +} + +function generate_website_card(website) { + return ` + + + + + + + `; +} + +$(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 + $(`.website-card`).show(); + } else { + // show 'clear search' button + $('.search-clear').show(); + // show apps that match search_query and hide apps that don't + websites.forEach((website) => { + if ( + website.name.toLowerCase().includes(search_query.toLowerCase()) + ) + { + $(`.website-card[data-name="${website.name}"]`).show(); + } else { + $(`.website-card[data-name="${website.name}"]`).hide(); + } + }) + } +}) + +$(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?`, [ + { + label: 'Delete', + type: 'danger', + value: 'delete', + }, + { + label: 'Cancel', + }, + ], { + type: 'warning', + }); + + if (resp === 'delete') { + // disable delete button + $('.delete-websites-btn').addClass('disabled'); + + // show 'deleting' modal + puter.ui.showSpinner(); + + let start_ts = Date.now(); + const websites = $('.website-checkbox:checked').toArray(); + + // delete all checked websites + for (let website of websites) { + let website_name = $(website).attr('data-website-name'); + // delete website + 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(); + }); + + try{ + count_websites(); + } catch(err) { + console.log(err); + } + } + + // close 'deleting' modal + setTimeout(() => { + puter.ui.hideSpinner(); + if($('.website-checkbox:checked').length === 0){ + // disable delete button + $('.delete-websites-btn').addClass('disabled'); + // reset the 'select all' checkbox + $('.select-all-websites').prop('indeterminate', false); + $('.select-all-websites').prop('checked', false); + } + }, (start_ts - Date.now()) > 500 ? 0 : 500); + } +}) + +$(document).on('contextmenu', '.website-card', function (e) { + e.preventDefault(); + e.stopPropagation(); + e.stopImmediatePropagation(); + puter.ui.contextMenu({ + items: [ + { + label: 'Delete', + type: 'danger', + action: () => { + attempt_delete_website($(this).attr('data-name')); + }, + }, + ], + }); +}) + +$(document).on('click', '.options-icon-website', function (e) { + e.preventDefault(); + e.stopPropagation(); + e.stopImmediatePropagation(); + puter.ui.contextMenu({ + items: [ + { + label: 'Delete', + type: 'danger', + action: () => { + attempt_delete_website($(this).attr('data-website-name')); + }, + }, + ], + }); +}) + +async function attempt_delete_website(website_name) { + // confirm delete + const alert_resp = await puter.ui.alert(`Are you sure you want to premanently delete ${html_encode(website_name)}.puter.site?`, + [ + { + label: 'Yes, delete permanently', + value: 'delete', + type: 'danger', + }, + { + label: 'Cancel' + }, + ] + ); + + 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(); + }); + + // delete website + puter.hosting.delete(website_name); + } +} + +export default init_websites; \ No newline at end of file diff --git a/src/dev-center/js/workers.js b/src/dev-center/js/workers.js index c2964f2ea..bd244dc1a 100644 --- a/src/dev-center/js/workers.js +++ b/src/dev-center/js/workers.js @@ -205,7 +205,7 @@ function count_workers() { function generate_worker_card(worker) { return ` -
+ + ${website.subdomain}.puter.site${website.created_at}
+ ${worker.name}