puter-deploy / src /dev-center /js /dev-center.js
gionuibk's picture
Upload folder using huggingface_hub
61d39e2 verified
/**
* Copyright (C) 2024-present Puter Technologies Inc.
*
* This file is part of Puter.
*
* Puter is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import init_apps from './apps.js';
import init_workers from './workers.js';
import init_websites from './websites.js';
window.url_params = new URLSearchParams(window.location.search);
window.domain = 'puter.com';
window.auth_username = null;
window.dev_center_uid = puter.appID;
window.developer;
window.activeTab = 'apps';
window.user = null;
// auth_username
(async () => {
window.user = await puter.auth.getUser();
if ( user?.username ) {
window.auth_username = user.username;
}
})();
// domain and APIOrigin
if ( window.url_params.has('puter.domain') ) {
window.domain = window.url_params.get('puter.domain');
}
// static hosting domain
window.static_hosting_domain = 'puter.site';
if ( window.domain === 'puter.localhost' ) {
window.static_hosting_domain = 'site.puter.localhost';
}
// add port to static_hosting_domain if provided
if ( window.url_params.has('puter.port') && window.url_params.get('puter.port') ) {
window.static_hosting_domain = `${window.static_hosting_domain }:${ html_encode(window.url_params.get('puter.port'))}`;
}
// protocol
window.protocol = 'https';
if ( window.url_params.has('puter.protocol') && window.url_params.get('puter.protocol') === 'http' )
{
window.protocol = 'http';
}
// port
window.port = '';
if ( window.url_params.has('puter.port') && window.url_params.get('puter.port') ) {
window.port = html_encode(window.url_params.get('puter.port'));
}
// source_path
if ( window.url_params.has('source_path') ) {
window.source_path = window.url_params.get('source_path');
} else {
window.source_path = null;
}
// ---------------------------------------------------------------
// Initialize
// ---------------------------------------------------------------
$(document).ready(async function () {
// initialize assets directory
await initializeAssetsDirectory();
puter.ui.showSpinner();
init_apps();
init_websites();
init_workers();
puter.ui.hideSpinner();
});
// ---------------------------------------------------------------
// Tab Buttons
// ---------------------------------------------------------------
$(document).on('click', '.tab-btn', async function (e) {
puter.ui.showSpinner();
$('section:not(.sidebar)').hide();
$('.tab-btn').removeClass('active');
$(this).addClass('active');
$(`section[data-tab="${ $(this).attr('data-tab') }"]`).show();
// ---------------------------------------------------------------
// Apps tab
// ---------------------------------------------------------------
if ( $(this).attr('data-tab') === 'apps' ) {
refresh_app_list();
activeTab = 'apps';
// Reset apps search when tab is activated
resetAppsSearch();
}
// ---------------------------------------------------------------
// Workers tab
// ---------------------------------------------------------------
else if ( $(this).attr('data-tab') === 'workers' ) {
refresh_worker_list();
activeTab = 'workers';
// Reset workers search when tab is activated
resetWorkersSearch();
}
// ---------------------------------------------------------------
// Websites tab
// ---------------------------------------------------------------
else if ( $(this).attr('data-tab') === 'websites' ) {
refresh_websites_list();
activeTab = 'websites';
// Reset websites search when tab is activated
resetWebsitesSearch();
}
// ---------------------------------------------------------------
// Payout Method tab
// ---------------------------------------------------------------
else if ( $(this).attr('data-tab') === 'payout-method' ) {
activeTab = 'payout-method';
puter.ui.showSpinner();
setTimeout(function () {
puter.apps.getDeveloperProfile(function (dev_profile) {
// show payout method tab if dev has joined incentive program
if ( dev_profile.joined_incentive_program ) {
$('#payout-method-email').html(dev_profile.paypal);
}
puter.ui.hideSpinner();
if ( activeTab === 'payout-method' )
{
$('#tab-payout-method').show();
}
});
}, 1000);
}
});
$('.jip-submit-btn').on('click', async function (e) {
const first_name = $('#jip-first-name').val();
const last_name = $('#jip-last-name').val();
const paypal = $('#jip-paypal').val();
let error;
if ( first_name === '' || last_name === '' || paypal === '' )
{
error = 'All fields are required.';
}
else if ( first_name.length > 100 )
{
error = `<strong>First Name</strong> cannot be longer than ${100}.`;
}
else if ( last_name.length > 100 )
{
error = `<strong>Last Name</strong> cannot be longer than ${100}.`;
}
else if ( paypal.length > 100 )
{
error = `<strong>Paypal</strong> cannot be longer than ${100}.`;
}
// check if email is valid
else if ( ! validateEmail(paypal) )
{
error = 'Paypal email must be a valid email address.';
}
// error?
if ( error ) {
$('#jip-error').show();
$('#jip-error').html(error);
document.body.scrollTop = document.documentElement.scrollTop = 0;
return;
}
// disable submit button
$('.jip-submit-btn').prop('disabled', true);
$.ajax({
url: `${puter.APIOrigin }/jip`,
type: 'POST',
async: true,
contentType: 'application/json',
data: JSON.stringify({
first_name: first_name,
last_name: last_name,
paypal: paypal,
}),
headers: {
'Authorization': `Bearer ${ puter.authToken}`,
},
success: function () {
$('#jip-success').show();
$('#jip-form').hide();
//enable submit button
$('.jip-submit-btn').prop('disabled', false);
// update dev profile
$('#payout-method-email').html(paypal);
// show separator
$('.tab-btn-separator').show();
// show payout method tab
$('.tab-btn[data-tab="payout-method"]').show();
},
error: function (err) {
$('#jip-error').show();
$('#jip-error').html(err.message);
// scroll to top so that user sees error message
document.body.scrollTop = document.documentElement.scrollTop = 0;
// enable submit button
$('.jip-submit-btn').prop('disabled', false);
},
});
});
$('#earn-money-c2a-close').click(async function (e) {
$('#earn-money').get(0).close();
puter.kv.set('earn-money-c2a-closed', 'true');
});
$('#earn-money::backdrop').click(async function (e) {
alert();
$('#earn-money').get(0).close();
puter.kv.set('earn-money-c2a-closed', 'true');
});
// https://stackoverflow.com/a/43467144/1764493
window.is_valid_url = (string) => {
let url;
try {
url = new URL(string);
} catch (_) {
return false;
}
return url.protocol === 'http:' || url.protocol === 'https:';
};
window.getBase64ImageFromUrl = async (imageUrl) => {
var res = await fetch(imageUrl);
var blob = await res.blob();
return new Promise((resolve, reject) => {
var reader = new FileReader();
reader.addEventListener('load', function () {
resolve(reader.result);
}, false);
reader.onerror = () => {
return reject(this);
};
reader.readAsDataURL(blob);
});
};
/**
* Formats a binary-byte integer into the human-readable form with units.
*
* @param {integer} bytes
* @returns
*/
window.byte_format = (bytes) => {
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
if ( bytes === 0 ) return '0 Byte';
const i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
return `${Math.round(bytes / Math.pow(1024, i), 2) } ${ sizes[i]}`;
};
/**
* check if a string is a valid email address
*/
window.validateEmail = (email) => {
var re = /\S+@\S+\.\S+/;
return re.test(email);
};
/**
* Formats a number with grouped thousands.
*
* @param {number|string} number - The number to be formatted. If a string is provided, it must only contain numerical characters, plus and minus signs, and the letter 'E' or 'e' (for scientific notation).
* @param {number} decimals - The number of decimal points. If a non-finite number is provided, it defaults to 0.
* @param {string} [dec_point='.'] - The character used for the decimal point. Defaults to '.' if not provided.
* @param {string} [thousands_sep=','] - The character used for the thousands separator. Defaults to ',' if not provided.
* @returns {string} The formatted number with grouped thousands, using the specified decimal point and thousands separator characters.
* @throws {TypeError} If the `number` parameter cannot be converted to a finite number, or if the `decimals` parameter is non-finite and cannot be converted to an absolute number.
*/
window.number_format = (number, decimals, dec_point, thousands_sep) => {
// Strip all characters but numerical ones.
number = (`${number }`).replace(/[^0-9+\-Ee.]/g, '');
var n = !isFinite(+number) ? 0 : +number,
prec = !isFinite(+decimals) ? 0 : Math.abs(decimals),
sep = (typeof thousands_sep === 'undefined') ? ',' : thousands_sep,
dec = (typeof dec_point === 'undefined') ? '.' : dec_point,
s = '',
toFixedFix = function (n, prec) {
var k = Math.pow(10, prec);
return `${ Math.round(n * k) / k}`;
};
// Fix for IE parseFloat(0.55).toFixed(0) = 0;
s = (prec ? toFixedFix(n, prec) : `${ Math.round(n)}`).split('.');
if ( s[0].length > 3 ) {
s[0] = s[0].replace(/\B(?=(?:\d{3})+(?!\d))/g, sep);
}
if ( (s[1] || '').length < prec ) {
s[1] = s[1] || '';
s[1] += new Array(prec - s[1].length + 1).join('0');
}
return s.join(dec);
};
$(document).on('click', '.close-message', function () {
$($(this).attr('data-target')).fadeOut();
});
$(document).on('click', '.section-tab-btn', function (e) {
// hide all tabs
$('.section-tab').hide();
// show section
$(`.section-tab[data-tab="${$(this).attr('data-tab')}"]`).show();
// remove active class from all tab buttons
$('.section-tab-btn').removeClass('active');
// add active class to clicked tab button
$(this).addClass('active');
});
$(document).on('click', '.close-success-msg', function (e) {
$(this).closest('div').fadeOut();
});
$('body').on('dragover', function (event) {
// skip if the user is dragging something over the drop area
if ( $(event.target).hasClass('drop-area') )
{
return;
}
event.preventDefault(); // Prevent the default behavior
event.stopPropagation(); // Stop the event from propagating
});
// Developers can drop items anywhere on the page to deploy them
$('body').on('drop', async function (event) {
// skip if the user is dragging something over the drop area
if ( $(event.target).hasClass('drop-area') )
{
return;
}
// prevent default behavior
event.preventDefault();
event.stopPropagation();
// retrieve puter items from the event
if ( event.detail?.items?.length > 0 ) {
window.dropped_items = event.detail.items;
window.source_path = window.dropped_items[0].path;
// by deploying an existing Puter folder. So we create the app and deploy it.
if ( window.source_path ) {
// todo if there are no apps, go straight to creating a new app
$('.insta-deploy-modal').get(0).showModal();
// set item name
$('.insta-deploy-item-name').html(html_encode(window.dropped_items[0].name));
}
}
//-----------------------------------------------------------------------------
// Local items dropped
//-----------------------------------------------------------------------------
const e = event.originalEvent;
if ( !e.dataTransfer || !e.dataTransfer.items || e.dataTransfer.items.length === 0 )
{
return;
}
// Get dropped items
window.dropped_items = await puter.ui.getEntriesFromDataTransferItems(e.dataTransfer.items);
// Generate a flat array of full paths from the dropped items
let paths = [];
for ( let item of window.dropped_items ) {
paths.push(`/${ item.fullPath ?? item.filepath}`);
}
// Generate a directory tree from the paths
let tree = generateDirTree(paths);
window.dropped_items = setRootDirTree(tree, window.dropped_items);
// Alert if no index.html in root
if ( ! hasRootIndexHtml(tree) ) {
puter.ui.alert(index_missing_error, [
{
label: 'Ok',
},
]);
$('.drop-area').removeClass('drop-area-ready-to-deploy');
$('.deploy-btn').addClass('disabled');
window.dropped_items = [];
return;
}
// Get all keys (directories and files) in the root
const rootKeys = Object.keys(tree);
// Generate a list of items in the root in the form of a string (e.g. /index.html, /css/style.css) with maximum of 3 items
let rootItems = '';
if ( rootKeys.length === 1 )
{
rootItems = rootKeys[0];
}
else if ( rootKeys.length === 2 )
{
rootItems = `${rootKeys[0] }, ${ rootKeys[1]}`;
}
else if ( rootKeys.length === 3 )
{
rootItems = `${rootKeys[0] }, ${ rootKeys[1] }, and${ rootKeys[1]}`;
}
else if ( rootKeys.length > 3 )
{
rootItems = `${rootKeys[0] }, ${ rootKeys[1] }, and ${ rootKeys.length - 2 } more item${ rootKeys.length - 2 > 1 ? 's' : ''}`;
}
// Show insta-deploy modal
$('.insta-deploy-modal').get(0)?.showModal();
// Set item name
$('.insta-deploy-item-name').html(html_encode(rootItems));
});
/**
* Get the MIME type for a given file extension.
*
* @param {string} extension - The file extension (with or without leading dot).
* @returns {string} The corresponding MIME type, or 'application/octet-stream' if not found.
*/
window.getMimeType = (extension) => {
const mimeTypes = {
jpg: 'image/jpeg',
jpeg: 'image/jpeg',
png: 'image/png',
gif: 'image/gif',
bmp: 'image/bmp',
webp: 'image/webp',
svg: 'image/svg+xml',
tiff: 'image/tiff',
ico: 'image/x-icon',
};
// Remove leading dot if present and convert to lowercase
const cleanExtension = extension.replace(/^\./, '').toLowerCase();
// Return the MIME type if found, otherwise return 'application/octet-stream'
return mimeTypes[cleanExtension] || 'application/octet-stream';
};
$(document).on('click', '.sidebar-toggle', function (e) {
$('.sidebar').toggleClass('open');
$('body').toggleClass('sidebar-open');
});
// ---------------------------------------------------------------
// Search Reset Functions
// ---------------------------------------------------------------
window.resetAppsSearch = () => {
$('.search-apps').val('');
$('.search-clear-apps').hide();
$('.search-apps').removeClass('has-value');
// Reset search query in apps.js scope if search_apps function is available
if ( typeof search_apps === 'function' ) {
search_apps();
}
};
window.resetWorkersSearch = () => {
$('.search-workers').val('');
$('.search-clear-workers').hide();
$('.search-workers').removeClass('has-value');
// Reset search query in workers.js scope if search_workers function is available
if ( typeof search_workers === 'function' ) {
search_workers();
}
};
window.resetWebsitesSearch = () => {
$('.search-websites').val('');
$('.search-clear-websites').hide();
$('.search-websites').removeClass('has-value');
// Reset search query in websites.js scope if search_websites function is available
if ( typeof search_websites === 'function' ) {
search_websites();
}
};
window.activate_tippy = () => {
tippy('.tippy', {
content (reference) {
return reference.getAttribute('title');
},
onMount (instance) {
// Remove the default title to prevent double tooltips
instance.reference.removeAttribute('title');
},
placement: 'top',
arrow: true,
});
};