Spaces:
Sleeping
Sleeping
| let sortBy = 'created_at'; | |
| let sortDirection = 'desc'; | |
| window.websites = []; | |
| let search_query; | |
| window.create_website = async (name, directoryPath = null) => { | |
| let website; | |
| // Use provided directory path or default to the default website file | |
| const websiteDir = directoryPath || window.default_website_file; | |
| try { | |
| website = await puter.hosting.create(name, websiteDir); | |
| } catch ( error ) { | |
| puter.ui.alert(`Error creating website: ${error.error.message}`); | |
| } | |
| return website; | |
| }; | |
| 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(); | |
| for ( let i = 0; i < window.websites.length; i++ ) { | |
| const website = window.websites[i]; | |
| // 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(); | |
| puter.ui.hideSpinner(); | |
| }; | |
| async function init_websites () { | |
| puter.hosting.list().then((websites) => { | |
| window.websites = websites; | |
| count_websites(); | |
| }); | |
| } | |
| $(document).on('click', '.create-a-website-btn', async function (e) { | |
| // Step 1: Show directory picker | |
| let selectedDirectory; | |
| try { | |
| selectedDirectory = await puter.ui.showDirectoryPicker(); | |
| } catch ( err ) { | |
| // User cancelled directory picker or there was an error | |
| console.log('Directory picker cancelled or error:', err); | |
| return; | |
| } | |
| // Step 2: Ask for website name | |
| if ( selectedDirectory && selectedDirectory.path ) { | |
| let name = await puter.ui.prompt('Please enter a name for your website:', 'my-awesome-website'); | |
| // Step 3: Create website with selected directory | |
| if ( name ) { | |
| await create_website(name, selectedDirectory.path); | |
| refresh_websites_list(); | |
| } | |
| } | |
| }); | |
| $(document).on('click', '.website-checkbox', function (e) { | |
| // was shift key pressed? | |
| if ( e.originalEvent && e.originalEvent.shiftKey ) { | |
| // select all checkboxes in range | |
| const currentIndex = $('.website-checkbox').index(this); | |
| const startIndex = Math.min(window.last_clicked_website_checkbox_index, currentIndex); | |
| const endIndex = Math.max(window.last_clicked_website_checkbox_index, currentIndex); | |
| // set all checkboxes in range to the same state as current checkbox | |
| for ( let i = startIndex; i <= endIndex; i++ ) { | |
| const checkbox = $('.website-checkbox').eq(i); | |
| checkbox.prop('checked', $(this).is(':checked')); | |
| // activate row | |
| if ( $(checkbox).is(':checked') ) | |
| { | |
| $(checkbox).closest('tr').addClass('active'); | |
| } | |
| else | |
| { | |
| $(checkbox).closest('tr').removeClass('active'); | |
| } | |
| } | |
| } | |
| // 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'); | |
| } | |
| // store the index of the last clicked checkbox | |
| window.last_clicked_website_checkbox_index = $('.website-checkbox').index(this); | |
| }); | |
| $(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_websites_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.subdomain.localeCompare(b.subdomain); | |
| } 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 if ( sortBy === 'root_dir' ) { | |
| const aRootDir = a.root_dir?.name || ''; | |
| const bRootDir = b.root_dir?.name || ''; | |
| return aRootDir.localeCompare(bRootDir); | |
| } else { | |
| return a[sortBy] > b[sortBy] ? 1 : -1; | |
| } | |
| }); | |
| } else { | |
| sorted_websites = websites.sort((a, b) => { | |
| if ( sortBy === 'name' ) { | |
| return b.subdomain.localeCompare(a.subdomain); | |
| } 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 if ( sortBy === 'root_dir' ) { | |
| const aRootDir = a.root_dir?.name || ''; | |
| const bRootDir = b.root_dir?.name || ''; | |
| return bRootDir.localeCompare(aRootDir); | |
| } else { | |
| return 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.subdomain.toLowerCase().includes(search_query.toLowerCase()) ) { | |
| $(`.website-card[data-name="${html_encode(website.subdomain)}"]`).show(); | |
| } else { | |
| $(`.website-card[data-name="${html_encode(website.subdomain)}"]`).hide(); | |
| } | |
| }); | |
| } | |
| } | |
| function count_websites () { | |
| let count = window.websites.length; | |
| $('.website-count').html(count ? count : ''); | |
| return count; | |
| } | |
| function generate_website_card (website) { | |
| return ` | |
| <tr class="website-card" data-name="${html_encode(website.subdomain)}"> | |
| <td style="width:30px; vertical-align: middle; line-height: 1;"> | |
| <input type="checkbox" class="website-checkbox" data-website-name="${website.subdomain}"> | |
| </td> | |
| <td style="font-family: monospace; font-size: 14px; vertical-align: middle;"><a href="https://${website.subdomain}.puter.site" target="_blank">${website.subdomain}.puter.site</a></td> | |
| <td style="font-size: 14px; vertical-align: middle;"> <span class="root-dir-name" data-root-dir-path="${website.root_dir ? html_encode(website.root_dir.path) : ''}">${website.root_dir ? website.root_dir.name : ''}</span></td> | |
| <td style="font-size: 14px; vertical-align: middle;">${website.created_at}</td> | |
| <td style="vertical-align: middle;"><img class="options-icon options-icon-website" data-website-name="${website.subdomain}" src="./img/options.svg"></td> | |
| </tr> | |
| `; | |
| } | |
| $(document).on('input change keyup keypress keydown paste cut', '.search-websites', function (e) { | |
| search_websites(); | |
| }); | |
| window.search_websites = function () { | |
| // search websites for query | |
| search_query = $('.search-websites').val().toLowerCase(); | |
| if ( search_query === '' ) { | |
| // hide 'clear search' button | |
| $('.search-clear-websites').hide(); | |
| // show all websites again | |
| $('.website-card').show(); | |
| // remove 'has-value' class from search input | |
| $('.search-websites').removeClass('has-value'); | |
| } else { | |
| // show 'clear search' button | |
| $('.search-clear-websites').show(); | |
| // show websites that match search_query and hide websites that don't | |
| websites.forEach((website) => { | |
| if ( | |
| website.subdomain.toLowerCase().includes(search_query.toLowerCase()) || | |
| website.root_dir?.name?.toLowerCase().includes(search_query.toLowerCase()) | |
| ) | |
| { | |
| $(`.website-card[data-name="${website.subdomain}"]`).show(); | |
| } else { | |
| $(`.website-card[data-name="${website.subdomain}"]`).hide(); | |
| } | |
| }); | |
| // add 'has-value' class to search input | |
| $('.search-websites').addClass('has-value'); | |
| } | |
| }; | |
| $(document).on('click', '.search-clear-websites', function (e) { | |
| $('.search-websites').val(''); | |
| $('.search-websites').trigger('change'); | |
| $('.search-websites').focus(); | |
| search_query = ''; | |
| // remove 'has-value' class from search input | |
| $('.search-websites').removeClass('has-value'); | |
| }); | |
| function remove_website_card (website_name, callback = null) { | |
| $(`.website-card[data-name="${website_name}"]`).fadeOut(200, function () { | |
| $(this).remove(); | |
| // Update the global websites array to remove the deleted website | |
| window.websites = window.websites.filter(website => website.subdomain !== website_name); | |
| 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?', [ | |
| { | |
| 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 | |
| remove_website_card(website_name); | |
| 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('click', '.options-icon-website', function (e) { | |
| e.preventDefault(); | |
| e.stopPropagation(); | |
| e.stopImmediatePropagation(); | |
| puter.ui.contextMenu({ | |
| items: [ | |
| { | |
| label: 'Change Directory', | |
| action: () => { | |
| change_website_directory($(this).attr('data-website-name')); | |
| }, | |
| }, | |
| '-', | |
| { | |
| label: 'Delete', | |
| type: 'danger', | |
| action: () => { | |
| attempt_website_deletion($(this).attr('data-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>?`, | |
| [ | |
| { | |
| label: 'Yes, delete permanently', | |
| value: 'delete', | |
| type: 'danger', | |
| }, | |
| { | |
| label: 'Cancel', | |
| }, | |
| ]); | |
| if ( alert_resp === 'delete' ) { | |
| // remove website card and update website count | |
| remove_website_card(website_name); | |
| // delete website | |
| puter.hosting.delete(website_name); | |
| } | |
| } | |
| async function change_website_directory (website_name) { | |
| try { | |
| // Step 1: Show directory picker | |
| const selectedDirectory = await puter.ui.showDirectoryPicker(); | |
| if ( !selectedDirectory || !selectedDirectory.path ) { | |
| return; // User cancelled | |
| } | |
| // Step 2: Confirm the change since it will replace the current website | |
| const confirmResp = await puter.ui.alert(`Are you sure you want to change the directory for <strong>${html_encode(website_name)}.puter.site</strong>?<br><br>This will update the website to serve files from the new directory.`, | |
| [ | |
| { | |
| label: 'Yes, change directory', | |
| value: 'change', | |
| type: 'primary', | |
| }, | |
| { | |
| label: 'Cancel', | |
| }, | |
| ], | |
| { | |
| type: 'info', | |
| }); | |
| if ( confirmResp !== 'change' ) { | |
| return; | |
| } | |
| // Step 3: Show loading spinner | |
| puter.ui.showSpinner(); | |
| try { | |
| // Step 4: Delete the existing website | |
| await puter.hosting.delete(website_name); | |
| // Step 5: Create a new website with the same name but new directory | |
| await puter.hosting.create(website_name, selectedDirectory.path); | |
| // Step 6: Refresh the websites list to show the updated directory | |
| await refresh_websites_list(); | |
| // Step 7: Show success message | |
| puter.ui.alert(`Website directory changed successfully! <strong>${html_encode(website_name)}.puter.site</strong> now serves files from the new directory.`, [], { | |
| type: 'success', | |
| }); | |
| } catch ( error ) { | |
| // If there's an error, show error message | |
| puter.ui.alert(`Error changing website directory: ${error.error?.message || error.message || 'Unknown error'}`, [], { | |
| type: 'error', | |
| }); | |
| } finally { | |
| // Hide loading spinner | |
| puter.ui.hideSpinner(); | |
| } | |
| } catch ( error ) { | |
| // Handle directory picker error | |
| console.log('Directory picker cancelled or error:', error); | |
| } | |
| } | |
| $(document).on('click', '.root-dir-name', function (e) { | |
| e.preventDefault(); | |
| e.stopPropagation(); | |
| e.stopImmediatePropagation(); | |
| const root_dir_path = $(this).attr('data-root-dir-path'); | |
| if ( root_dir_path ) { | |
| puter.ui.launchApp('explorer', { | |
| path: root_dir_path, | |
| }); | |
| } | |
| }); | |
| export default init_websites; |