diff --git a/src/UI/Settings/UITabAbout.js b/src/UI/Settings/UITabAbout.js new file mode 100644 index 00000000..8ef41dde --- /dev/null +++ b/src/UI/Settings/UITabAbout.js @@ -0,0 +1,96 @@ +/** + * Copyright (C) 2024 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 . + */ + +// About +function UITabAbout(){ + let h = ``; + + h += `
`; + h += `
` + h += `
+ +

${i18n('puter_description')}

+ + +
+
+ + +
+

${i18n('oss_code_and_content')}

+
+ +
+
+
+ `; + h += `
`; + h += `
`; + + return h; +} +export default UITabAbout; \ No newline at end of file diff --git a/src/UI/Settings/UIWindowConfirmUserDeletion.js b/src/UI/Settings/UIWindowConfirmUserDeletion.js new file mode 100644 index 00000000..b4b85a65 --- /dev/null +++ b/src/UI/Settings/UIWindowConfirmUserDeletion.js @@ -0,0 +1,83 @@ +/** + * Copyright (C) 2024 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 . + */ + +import UIWindow from '../UIWindow.js' +import UIWindowFinalizeUserDeletion from './UIWindowFinalizeUserDeletion.js' + +async function UIWindowConfirmUserDeletion(options){ + return new Promise(async (resolve) => { + options = options ?? {}; + + let h = ''; + h += `
`; + h += `
×
`; + h += ``; + h += ``; + h += ``; + h += ``; + h += `
`; + + const el_window = await UIWindow({ + title: i18n('confirm_delete_user_title'), + icon: null, + uid: null, + is_dir: false, + body_content: h, + has_head: false, + selectable_body: false, + draggable_body: false, + allow_context_menu: false, + is_draggable: true, + is_resizable: false, + is_droppable: false, + init_center: true, + allow_native_ctxmenu: true, + allow_user_select: true, + backdrop: true, + onAppend: function(el_window){ + }, + width: 500, + dominant: true, + window_css: { + height: 'initial', + padding: '0', + border: 'none', + boxShadow: '0 0 10px rgba(0,0,0,.2)', + borderRadius: '5px', + backgroundColor: 'white', + color: 'black', + }, + }); + + $(el_window).find('.generic-close-window-button').on('click', function(){ + $(el_window).close(); + }); + + $(el_window).find('.cancel-user-deletion').on('click', function(){ + $(el_window).close(); + }); + + $(el_window).find('.proceed-with-user-deletion').on('click', function(){ + UIWindowFinalizeUserDeletion(); + $(el_window).close(); + }); + }) +} + +export default UIWindowConfirmUserDeletion; \ No newline at end of file diff --git a/src/UI/Settings/UIWindowFinalizeUserDeletion.js b/src/UI/Settings/UIWindowFinalizeUserDeletion.js new file mode 100644 index 00000000..aec5dbae --- /dev/null +++ b/src/UI/Settings/UIWindowFinalizeUserDeletion.js @@ -0,0 +1,152 @@ +/** + * Copyright (C) 2024 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 . + */ + +import UIWindow from '../UIWindow.js' + +async function UIWindowFinalizeUserDeletion(options){ + return new Promise(async (resolve) => { + options = options ?? {}; + + let h = ''; + + // if user is temporary, ask them to type in 'confirm' to delete their account + if(user.is_temp){ + h += `
`; + h += `
×
`; + h += ``; + h += ``; + // error message + h += `
`; + // input field + h += ``; + h += ``; + h += ``; + h += `
`; + } + // otherwise ask for password + else{ + h += `
`; + h += `
×
`; + h += ``; + h += ``; + // error message + h += `
`; + // input field + h += ``; + h += ``; + h += ``; + h += `
`; + } + + const el_window = await UIWindow({ + title: i18n('confirm_delete_user_title'), + icon: null, + uid: null, + is_dir: false, + body_content: h, + has_head: false, + selectable_body: false, + draggable_body: false, + allow_context_menu: false, + is_draggable: true, + is_resizable: false, + is_droppable: false, + init_center: true, + allow_native_ctxmenu: true, + allow_user_select: true, + backdrop: true, + onAppend: function(el_window){ + }, + width: 500, + dominant: false, + window_css: { + height: 'initial', + padding: '0', + border: 'none', + boxShadow: '0 0 10px rgba(0,0,0,.2)', + } + }); + + $(el_window).find('.generic-close-window-button').on('click', function(){ + $(el_window).close(); + }); + + $(el_window).find('.cancel-user-deletion').on('click', function(){ + $(el_window).close(); + }); + + $(el_window).find('.proceed-with-user-deletion').on('click', function(){ + $(el_window).find('.error-message').hide(); + // if user is temporary, check if they typed 'confirm' + if(user.is_temp){ + if($(el_window).find('.confirm-temporary-user-deletion').val() !== 'confirm'){ + $(el_window).find('.error-message').html(i18n('type_confirm_to_delete_account'), false); + $(el_window).find('.error-message').show(); + return; + } + } + // otherwise, check if password is correct + else{ + if($(el_window).find('.confirm-user-deletion-password').val() === ''){ + $(el_window).find('.error-message').html(i18n('all_fields_required'), false); + $(el_window).find('.error-message').show(); + return; + } + } + + // delete user + $.ajax({ + url: api_origin + "/delete-own-user", + type: 'POST', + async: true, + contentType: "application/json", + headers: { + "Authorization": "Bearer " + auth_token + }, + data: JSON.stringify({ + password: $(el_window).find('.confirm-user-deletion-password').val(), + }), + statusCode: { + 401: function () { + logout(); + }, + 400: function(){ + $(el_window).find('.error-message').html(i18n('incorrect_password')); + $(el_window).find('.error-message').show(); + } + }, + success: function(data){ + if(data.success){ + // mark user as deleted + window.user.deleted = true; + // log user out + logout(); + } + else{ + $(el_window).find('.error-message').html(data.error); + $(el_window).find('.error-message').show(); + + } + } + }); + }); + }) +} + +export default UIWindowFinalizeUserDeletion; \ No newline at end of file diff --git a/src/UI/Settings/UIWindowSettings.js b/src/UI/Settings/UIWindowSettings.js new file mode 100644 index 00000000..b9980190 --- /dev/null +++ b/src/UI/Settings/UIWindowSettings.js @@ -0,0 +1,352 @@ +/** + * Copyright (C) 2024 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 . + */ + +import UIWindow from '../UIWindow.js' +import UIWindowChangePassword from '../UIWindowChangePassword.js' +// import UIWindowChangeEmail from './UIWindowChangeEmail.js' +// import UIWindowDeleteAccount from './UIWindowDeleteAccount.js' +import UIWindowChangeUsername from '../UIWindowChangeUsername.js' +import changeLanguage from "../../i18n/i18nchangeLanguage.js" +import UIWindowConfirmUserDeletion from './UIWindowConfirmUserDeletion.js'; +import UITabAbout from './UITabAbout.js'; + +async function UIWindowSettings(options){ + return new Promise(async (resolve) => { + options = options ?? {}; + + let h = ''; + + h += `
`; + h += `
`; + // side bar + h += `
`; + h += `
${i18n('about')}
`; + h += `
${i18n('usage')}
`; + h += `
${i18n('account')}
`; + h += `
${i18n('language')}
`; + h += `
`; + + // content + h += `
`; + + // About + h += UITabAbout(); + + // Usage + h += `
`; + h += `

Usage

`; + h += `
+

${i18n('storage_usage')}

+
+ + used of + +
+
+ +
+
+
` + h += `
`; + + // Account + h += `
`; + h += `

${i18n('account')}

`; + // change password button + h += `
`; + h += `${i18n('password')}`; + h += `
`; + h += ``; + h += `
`; + h += `
`; + + // change username button + h += `
`; + h += `
`; + h += `${i18n('username')}`; + h += `${user.username}`; + h += `
`; + h += `
`; + h += ``; + h += `
` + h += `
`; + + // change email button + if(user.email){ + h += `
`; + h += `
`; + h += `${i18n('email')}`; + h += `${user.email}`; + h += `
`; + h += `
`; + h += ``; + h += `
`; + h += `
`; + } + + // 'Delete Account' button + h += `
`; + h += `${i18n("delete_account")}`; + h += `
`; + h += ``; + h += `
`; + h += `
`; + + h += `
`; + + // Language + h += `
`; + h += `

${i18n('language')}

`; + // search + h += `
`; + h += ``; + h += `
`; + // list of languages + const available_languages = listSupportedLanguages(); + h += `
`; + for (let lang of available_languages) { + h += `
${lang.name}
`; + } + h += `
`; + + h += `
`; + + h += `
`; + h += `
`; + h += `
`; + + h += ``; + + const el_window = await UIWindow({ + title: 'Settings', + app: 'settings', + single_instance: true, + icon: null, + uid: null, + is_dir: false, + body_content: h, + has_head: true, + selectable_body: false, + allow_context_menu: false, + is_resizable: false, + is_droppable: false, + init_center: true, + allow_native_ctxmenu: true, + allow_user_select: true, + backdrop: false, + width: 800, + height: 500, + height: 'auto', + dominant: true, + show_in_taskbar: false, + draggable_body: false, + onAppend: function(this_window){ + }, + window_class: 'window-settings', + body_css: { + width: 'initial', + height: '100%', + overflow: 'auto' + } + }); + + $.ajax({ + url: api_origin + "/drivers/usage", + type: 'GET', + async: true, + contentType: "application/json", + headers: { + "Authorization": "Bearer " + auth_token + }, + statusCode: { + 401: function () { + logout(); + }, + }, + success: function (res) { + let h = ''; // Initialize HTML string for driver usage bars + + // Loop through user services + res.user.forEach(service => { + const { monthly_limit, monthly_usage } = service; + let usageDisplay = ``; + + if (monthly_limit !== null) { + let usage_percentage = (monthly_usage / monthly_limit * 100).toFixed(0); + usage_percentage = usage_percentage > 100 ? 100 : usage_percentage; // Cap at 100% + usageDisplay = ` +
+

${service.service['driver.interface']} (${service.service['driver.method']}):

+ ${monthly_usage} used of ${monthly_limit} +
+
${usage_percentage}%
+
+
+ `; + } + else { + usageDisplay = ` +
+

${service.service['driver.interface']} (${service.service['driver.method']}):

+ ${i18n('usage')}: ${monthly_usage} (${i18n('unlimited')}) +
+ `; + } + h += usageDisplay; + }); + + // Append driver usage bars to the container + $('.settings-content[data-settings="usage"]').append(`
${h}
`); + } + }) + + // df + $.ajax({ + url: api_origin + "/df", + type: 'GET', + async: true, + contentType: "application/json", + headers: { + "Authorization": "Bearer " + auth_token + }, + statusCode: { + 401: function () { + logout(); + }, + }, + success: function (res) { + let usage_percentage = (res.used / res.capacity * 100).toFixed(0); + usage_percentage = usage_percentage > 100 ? 100 : usage_percentage; + + $('#storage-used').html(byte_format(res.used)); + $('#storage-capacity').html(byte_format(res.capacity)); + $('#storage-used-percent').html(usage_percentage + '%'); + $('#storage-bar').css('width', usage_percentage + '%'); + if (usage_percentage >= 100) { + $('#storage-bar').css({ + 'border-top-right-radius': '3px', + 'border-bottom-right-radius': '3px', + }); + } + } + }) + + // version + $.ajax({ + url: api_origin + "/version", + type: 'GET', + async: true, + contentType: "application/json", + headers: { + "Authorization": "Bearer " + auth_token + }, + statusCode: { + 401: function () { + logout(); + }, + }, + success: function (res) { + var d = new Date(0); + $('.version').html('Version: ' + res.version + ' • ' + 'Server: ' + res.location + ' • ' + 'Deployed: ' + new Date(res.deploy_timestamp)); + } + }) + + $(el_window).find('.credits').on('click', function (e) { + if($(e.target).hasClass('credits')){ + $('.credits').get(0).close(); + } + }); + + $(el_window).find('.show-credits').on('click', function (e) { + $('.credits').get(0).showModal(); + }) + + $(el_window).find('.change-password').on('click', function (e) { + UIWindowChangePassword(); + }) + + $(el_window).find('.change-email').on('click', function (e) { + UIWindowChangeEmail(); + }) + + $(el_window).find('.delete-account').on('click', function (e) { + UIWindowConfirmUserDeletion(); + }) + + $(el_window).find('.change-username').on('click', function (e) { + UIWindowChangeUsername(); + }) + + $(el_window).on('click', '.settings-sidebar-item', function(){ + const $this = $(this); + const settings = $this.attr('data-settings'); + const $container = $this.closest('.settings').find('.settings-content-container'); + const $content = $container.find(`.settings-content[data-settings="${settings}"]`); + // add active class to sidebar item + $this.siblings().removeClass('active'); + $this.addClass('active'); + // add active class to content + $container.find('.settings-content').removeClass('active'); + $content.addClass('active'); + // if language, focus on search + if(settings === 'language'){ + $content.find('.search').first().focus(); + // make sure all language items are visible + $content.find('.language-item').show(); + // empty search + $content.find('.search').val(''); + } + }) + + $(el_window).on('click', '.language-item', function(){ + const $this = $(this); + const lang = $this.attr('data-lang'); + changeLanguage(lang); + $this.siblings().removeClass('active'); + $this.addClass('active'); + // make sure all other language items are visible + $this.closest('.language-list').find('.language-item').show(); + }) + + $(el_window).on('input', '.search', function(){ + const $this = $(this); + const search = $this.val().toLowerCase(); + const $container = $this.closest('.settings').find('.settings-content-container'); + const $content = $container.find('.settings-content.active'); + const $list = $content.find('.language-list'); + const $items = $list.find('.language-item'); + $items.each(function(){ + const $item = $(this); + const lang = $item.attr('data-lang'); + const name = $item.text().toLowerCase(); + const english_name = $item.attr('data-english-name').toLowerCase(); + if(name.includes(search) || lang.includes(search) || english_name.includes(search)){ + $item.show(); + }else{ + $item.hide(); + } + }) + }); + + resolve(el_window); + }); +} + + +export default UIWindowSettings \ No newline at end of file diff --git a/src/UI/UIContextMenu.js b/src/UI/UIContextMenu.js index 20491ed3..62701aa4 100644 --- a/src/UI/UIContextMenu.js +++ b/src/UI/UIContextMenu.js @@ -148,12 +148,13 @@ function UIContextMenu(options){ return false; }); - // initialize menuAim plugin (../libs/jquery.menu-aim.js) + // Initialize the menuAim plugin (../libs/jquery.menu-aim.js) $(contextMenu).menuAim({ + submenuSelector: ".context-menu-item-submenu", submenuDirection: function(){ - //if not submenu + // If not submenu if(!options.is_submenu){ - // if submenu left postiton is greater than main menu left position + // if submenu's left postiton is greater than main menu's left position if($(contextMenu).offset().left + 2 * $(contextMenu).width() + 15 < window.innerWidth ){ return "right"; } else { @@ -161,12 +162,10 @@ function UIContextMenu(options){ } } }, - //activates item when mouse enters depending in mouse position and direction + // activates item when mouse enters depending on mouse position and direction activate: function (e) { - //activate items let item = $(e).closest('.context-menu-item'); - // mark other items as inactive $(contextMenu).find('.context-menu-item').removeClass('context-menu-item-active'); // mark this item as active @@ -176,7 +175,6 @@ function UIContextMenu(options){ // mark this context menu as active $(contextMenu).addClass('context-menu-active'); - // activate submenu // open submenu if applicable if($(e).hasClass('context-menu-item-submenu')){ diff --git a/src/UI/UIDesktop.js b/src/UI/UIDesktop.js index 18ad3251..ced9cc40 100644 --- a/src/UI/UIDesktop.js +++ b/src/UI/UIDesktop.js @@ -34,7 +34,8 @@ import UIWindowQR from "./UIWindowQR.js" import UIWindowRefer from "./UIWindowRefer.js" import UITaskbar from "./UITaskbar.js" import new_context_menu_item from "../helpers/new_context_menu_item.js" -import ChangeLanguage from "../i18n/i18nChangeLanguage.js" +import changeLanguage from "../i18n/i18nchangeLanguage.js" +import UIWindowSettings from "./Settings/UIWindowSettings.js" async function UIDesktop(options){ let h = ''; @@ -1156,12 +1157,12 @@ $(document).on('click', '.user-options-menu-btn', async function(e){ // ------------------------------------------- // Load available languages // ------------------------------------------- - const supportedLanguagesItems = ListSupportedLanguages().map(lang => { + const supportedLanguagesItems = listSupportedLanguages().map(lang => { return { html: lang.name, icon: window.locale === lang.code ? '✓' : '', onClick: async function(){ - ChangeLanguage(lang.code); + changeLanguage(lang.code); } } }); @@ -1181,33 +1182,15 @@ $(document).on('click', '.user-options-menu-btn', async function(e){ UIWindowMyWebsites(); } }, - //-------------------------------------------------- - // Change Username - //-------------------------------------------------- - { - html: i18n('change_username'), - onClick: async function(){ - UIWindowChangeUsername(); - } - }, - //-------------------------------------------------- // Change Password //-------------------------------------------------- { - html: i18n('change_password'), + html: i18n('settings'), onClick: async function(){ - UIWindowChangePassword(); + UIWindowSettings(); } }, - - //-------------------------------------------------- - // Change Language - //-------------------------------------------------- - { - html: i18n('change_language'), - items: supportedLanguagesItems - }, //-------------------------------------------------- // Contact Us //-------------------------------------------------- @@ -1288,10 +1271,7 @@ $(document).on('click', '.close-launch-popover', function(){ }); $(document).on('click', '.toolbar-puter-logo', function(){ - // launch the about app - launch_app({name: 'about', window_options:{ - single_instance: true, - }}); + UIWindowSettings(); }) $(document).on('click', '.user-options-create-account-btn', async function(e){ diff --git a/src/css/style.css b/src/css/style.css index 5dd97c1e..1f331b35 100644 --- a/src/css/style.css +++ b/src/css/style.css @@ -2138,7 +2138,7 @@ label { font: 14px "Helvetica Neue", Sans-Serif; border: none !important; backdrop-filter: blur(3px); - filter: drop-shadow(0 0 3px rgba(0,0,0,.455)); + filter: drop-shadow(0 0 3px rgba(0, 0, 0, .455)); } .arrow { @@ -3254,4 +3254,359 @@ label { justify-content: center; align-items: center; display: none; +} + +/*! + * ================================================== + * Settings + * ================================================== + */ + +.settings-container { + display: flex; + flex-direction: column; + height: 100%; +} + +.settings { + display: flex; + flex-direction: row; + -webkit-font-smoothing: antialiased; + flex-grow: 1; +} + +.settings-sidebar { + width: 200px; + background-color: #f9f9f9; + border-right: 1px solid #e0e0e0; + padding: 20px; + +} + +.settings-sidebar-item { + cursor: pointer; + border-radius: 4px; + padding: 10px; + margin-bottom: 15px; + background-repeat: no-repeat; + background-position: 10px center; + background-size: 25px; + padding-left: 45px; + font-size: 15px; +} + +.settings-sidebar-item:hover { + background-color: #e8e8e8; +} + +.settings-sidebar-item.active { + background-color: #e0e0e0; +} + +.settings-content-container { + flex: 1; + padding: 20px 30px; + height: 500px; + overflow-y: scroll; +} + +.settings-content { + display: none; +} + +.settings-content[data-settings="about"] { + height: 100%; +} + +.settings-content h1 { + font-size: 24px; + margin-bottom: 20px; + border-bottom: 1px solid #e0e0e0; + padding-bottom: 10px; + padding-left: 5px; + font-weight: 500; +} + +.settings-content.active { + display: block; +} + +.settings-content .about-container { + height: 100%; + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; +} + +.settings-content[data-settings="about"] a { + color: #1663d4; + text-decoration: none; + font-size: 12px; +} + +.settings-content[data-settings="about"] a:hover { + text-decoration: underline; +} + +.settings-content .logo, +.settings-content .logo img { + display: block; + width: 55px; + height: 55px; + margin: 0 auto; + border-radius: 4px; +} + +.settings-content .links { + text-align: center; + font-size: 14px; + margin-top: 10px; +} + +.settings-content .social-links { + text-align: center; + /* margin-top: 10px; */ +} + +.settings-content .social-links a { + opacity: 0.7; + transition: opacity 0.1s ease-in-out; +} + +.settings-content .social-links a, +.settings-content .social-links a:hover { + text-decoration: none; + margin: 0 10px; + +} + +.settings-content .social-links a:hover { + opacity: 1; +} + +.settings-content .social-links svg { + width: 20px; + height: 20px; +} + +.settings-content .about { + text-align: center; + display: flex; + flex-direction: column; + justify-content: center; + padding: 20px 40px; + max-width: 500px; +} + +.about-container .about{ + text-align: center; +} + +.settings-content .version { + font-size: 9px; + color: #343c4f; + text-align: center; + margin-bottom: 10px; + opacity: 0.3; + transition: opacity 0.1s ease-in-out; + height: 12px; +} + +.settings-content .version:hover { + opacity: 1; +} + +.driver-usage { + background-color: white; + bottom: 0; + width: 100%; + box-sizing: border-box; + color: #3c4963; + height: 85px; +} + +.credits { + padding: 0; + border: 1px solid #bfbfbf; + box-shadow: 1px 1px 10px 0px #8a8a8a; + width: 400px; +} + +.credit-content a { + font-size: 15px; +} + +.credits .credit-content { + padding: 20px; +} + +.credit-content { + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.credit-content ul { + max-height: 300px; + overflow-y: scroll; + background: #f4f4f4; + padding: 10px; + box-shadow: 2px 2px 5px 2px inset #CCC; + +} + +.credit-content li { + margin-bottom: 10px; +} + +#storage-bar-wrapper { + width: 100%; + height: 15px; + border: 1px solid #8a9096; + border-radius: 3px; + background-color: #fff; + position: relative; +} + +#storage-bar { + height: 15px; + background-color: #dbe3ef; + border-top-left-radius: 3px; + border-bottom-left-radius: 3px; + width: 0; +} + +#storage-used-percent { + position: absolute; + left: calc(50% - 20px); + text-align: center; + display: inline-block; + width: 40px; + font-size: 13px; +} + +.usage-progbar-wrapper { + width: 100%; + height: 15px; + border: 1px solid #8a9096; + border-radius: 3px; + background-color: #fff; + position: relative; +} + +.usage-progbar { + height: 15px; + background-color: #dbe3ef; + border-top-left-radius: 3px; + border-bottom-left-radius: 3px; + width: 0; +} + +.usage-progbar-percent { + position: absolute; + left: calc(50% - 20px); + text-align: center; + display: inline-block; + width: 40px; + font-size: 13px; +} + + +.version { + font-size: 9px; + color: #343c4f; + text-align: center; + margin-bottom: 10px; + opacity: 0.3; + transition: opacity 0.1s ease-in-out; + height: 12px; +} + +.version:hover { + opacity: 1; +} + +.language-list { + display: grid; + grid-template-columns: 33.333333333% 33.333333333% 33.333333333%; +} + +.language-item { + cursor: pointer; + padding: 10px; + border-radius: 4px; + margin-bottom: 10px; + margin-right: 10px; +} + +.language-item:hover { + background-color: #f6f6f6; +} + +.language-item.active { + background-color: #e0e0e0; +} + +.settings-card { + overflow: hidden; + padding: 10px 15px; + border: 1px solid; + border-radius: 4px; + background: #f7f7f7a1; + border: 1px solid #cccccc8f; + margin-bottom: 20px; + display: flex; + flex-direction: row; + align-items: center; + height: 45px; +} + +.settings-card strong { + font-weight: 500; +} + +.settings-card-danger { + border-color: #f0080866; + background: #ffecec; + color: rgb(215 2 2); +} + +.error-message { + display: none; + color: rgb(215 2 2); + font-size: 14px; + margin-top: 10px; + margin-bottom: 10px; + padding: 10px; + border-radius: 4px; + border: 1px solid rgb(215 2 2); + text-align: center; +} + +.account-deletion-confirmation-prompt { + text-align: center; + font-size: 16px; + padding: 20px; + font-weight: 400; + margin: -10px 10px 20px 10px; + -webkit-font-smoothing: antialiased; + color: #5f626d; +} +.account-deletion-confirmation-icon{ + width: 70px; + margin: 20px auto 20px; + display: block; + margin-bottom: 20px; +} +.proceed-with-user-deletion{ + margin-bottom: 20px; +} +.confirm-temporary-user-deletion{ + width: 100%; + margin-bottom: 20px; +} + +.confirm-user-deletion-password{ + width: 100%; + margin-bottom: 20px; } \ No newline at end of file diff --git a/src/globals.js b/src/globals.js index fb69918b..97d1251a 100644 --- a/src/globals.js +++ b/src/globals.js @@ -220,4 +220,7 @@ window.feature_flags = { window.is_auto_arrange_enabled = true; window.desktop_item_positions = {}; -window.reset_item_positions = true; // The variable decides if the item positions should be reset when the user enabled auto arrange \ No newline at end of file +window.reset_item_positions = true; // The variable decides if the item positions should be reset when the user enabled auto arrange + +// default language +window.locale = 'en'; \ No newline at end of file diff --git a/src/helpers.js b/src/helpers.js index 96b13a23..cf352459 100644 --- a/src/helpers.js +++ b/src/helpers.js @@ -722,7 +722,7 @@ window.mutate_user_preferences = function(user_preferences_delta) { window.update_user_preferences = function(user_preferences) { window.user_preferences = user_preferences; localStorage.setItem('user_preferences', JSON.stringify(user_preferences)); - window.locale = user_preferences.language; + window.locale = user_preferences.language ?? 'en'; } window.sendWindowWillCloseMsg = function(iframe_element) { diff --git a/src/helpers/update_username_in_gui.js b/src/helpers/update_username_in_gui.js index 1a763439..78c2f353 100644 --- a/src/helpers/update_username_in_gui.js +++ b/src/helpers/update_username_in_gui.js @@ -67,6 +67,9 @@ const update_username_in_gui = function(new_username){ else if (attr_item_path.startsWith('/' + window.user.username + '/')) $el.attr('data-item-path', attr_item_path.replace('/' + window.user.username + '/', '/' + new_username + '/')); } + + // any element with username class + $('.username').text(new_username); }) // todo update all window paths diff --git a/src/i18n/i18n.js b/src/i18n/i18n.js index 14b46873..5503a293 100644 --- a/src/i18n/i18n.js +++ b/src/i18n/i18n.js @@ -18,7 +18,7 @@ */ import translations from './translations/translations.js'; -window.ListSupportedLanguages = () => Object.keys(translations).map(lang => translations[lang]); +window.listSupportedLanguages = () => Object.keys(translations).map(lang => translations[lang]); window.i18n = function (key, replacements = [], encode_html = true) { if(typeof replacements === 'boolean' && encode_html === undefined){ diff --git a/src/i18n/i18nChangeLanguage.js b/src/i18n/i18nChangeLanguage.js index e3482daf..44cfa335 100644 --- a/src/i18n/i18nChangeLanguage.js +++ b/src/i18n/i18nChangeLanguage.js @@ -17,11 +17,11 @@ * along with this program. If not, see . */ -function ChangeLanguage(lang) { +function changeLanguage(lang) { window.locale = lang; window.mutate_user_preferences({ language : lang, }); } -export default ChangeLanguage; \ No newline at end of file +export default changeLanguage; \ No newline at end of file diff --git a/src/i18n/translations/ar.js b/src/i18n/translations/ar.js index ea51c9d4..279d8119 100644 --- a/src/i18n/translations/ar.js +++ b/src/i18n/translations/ar.js @@ -18,6 +18,7 @@ */ const ar = { name: "العربية", + english_name: "Arabic", code: "ar", dictionary: { access_granted_to: "دخول مسموح به", diff --git a/src/i18n/translations/bn.js b/src/i18n/translations/bn.js index 2bd82522..8cc68092 100644 --- a/src/i18n/translations/bn.js +++ b/src/i18n/translations/bn.js @@ -18,6 +18,7 @@ */ const bn = { name: "বাংলা", + english_name: "Bengali", code: "bn", dictionary: { access_granted_to: "অ্যাক্সেস মঞ্জুর করা হয়েছে", diff --git a/src/i18n/translations/da.js b/src/i18n/translations/da.js index ca72769e..21f9ab30 100644 --- a/src/i18n/translations/da.js +++ b/src/i18n/translations/da.js @@ -18,6 +18,7 @@ */ const da = { name: "Dansk", + english_name: "Danish", code: "da", dictionary: { access_granted_to: "Adgang givet til", diff --git a/src/i18n/translations/de.js b/src/i18n/translations/de.js index 7a750370..5f665b8c 100644 --- a/src/i18n/translations/de.js +++ b/src/i18n/translations/de.js @@ -18,6 +18,7 @@ */ const de = { name: "Deutsch", + english_name: "German", code: "de", dictionary: { access_granted_to: "Erlaubt zugriff auf", diff --git a/src/i18n/translations/emoji.js b/src/i18n/translations/emoji.js index 0ec22b53..dfa41ee4 100644 --- a/src/i18n/translations/emoji.js +++ b/src/i18n/translations/emoji.js @@ -18,6 +18,7 @@ */ const emoji = { name: "🌍", + english_name: "Emoji", code: "emoji", dictionary: { access_granted_to: "🔓✅", diff --git a/src/i18n/translations/en.js b/src/i18n/translations/en.js index 706d765d..fa8c35f4 100644 --- a/src/i18n/translations/en.js +++ b/src/i18n/translations/en.js @@ -18,8 +18,11 @@ */ const en = { name: "English", + english_name: "English", code: "en", dictionary: { + about: "About", + account: "Account", access_granted_to: "Access Granted To", add_existing_account: "Add Existing Account", all_fields_required: 'All fields are required.', @@ -43,6 +46,7 @@ const en = { confirm_delete_single_item: 'Do you want to permanently delete this item?', confirm_open_apps_log_out: 'You have open apps. Are you sure you want to log out?', confirm_new_password: "Confirm New Password", + confirm_delete_user: "Are you sure you want to delete your account? All your files and data will be permanently deleted. This action cannot be undone.", contact_us: "Contact Us", contain: 'Contain', continue: "Continue", @@ -53,14 +57,17 @@ const en = { create_account: "Create Account", create_free_account: "Create Free Account", create_shortcut: "Create Shortcut", + credits: "Credits", current_password: "Current Password", cut: 'Cut', date_modified: 'Date modified', delete: 'Delete', + delete_account: "Delete Account", delete_permanently: "Delete Permanently", deploy_as_app: 'Deploy as app', descending: 'Descending', desktop_background_fit: "Fit", + developers: "Developers", dir_published_as_website: `%strong% has been published to:`, disassociate_dir: "Disassociate Directory", download: 'Download', @@ -71,6 +78,7 @@ const en = { empty_trash: 'Empty Trash', empty_trash_confirmation: `Are you sure you want to permanently delete the items in Trash?`, emptying_trash: 'Emptying Trash…', + enter_password_to_confirm_delete_user: "Enter your password to confirm account deletion", feedback: "Feedback", feedback_c2a: "Please use the form below to send us your feedback, comments, and bug reports.", feedback_sent_confirmation: "Thank you for contacting us. If you have an email associated with your account, you will hear back from us as soon as possible.", @@ -87,6 +95,8 @@ const en = { items_in_trash_cannot_be_renamed: `This item can't be renamed because it's in the trash. To rename this item, first drag it out of the Trash.`, jpeg_image: 'JPEG image', keep_in_taskbar: 'Keep in Taskbar', + language: "Language", + license: "License", loading: 'Loading', log_in: "Log In", log_into_another_account_anyway: 'Log into another account anyway', @@ -113,6 +123,7 @@ const en = { open_in_new_tab: "Open in New Tab", open_in_new_window: "Open in New Window", open_with: "Open With", + oss_code_and_content: "Open Source Software and Content", password: "Password", password_changed: "Password changed.", passwords_do_not_match: '`New Password` and `Confirm New Password` do not match.', @@ -120,14 +131,18 @@ const en = { paste_into_folder: "Paste Into Folder", pick_name_for_website: "Pick a name for your website:", picture: "Picture", + plural_suffix: 's', powered_by_puter_js: `Powered by Puter.js`, preparing: "Preparing...", preparing_for_upload: "Preparing for upload...", + privacy: "Privacy", proceed_to_login: 'Proceed to login', + proceed_with_account_deletion: "Proceed with Account Deletion", properties: "Properties", publish: "Publish", publish_as_website: 'Publish as website', - plural_suffix: 's', + puter_description: `Puter is a privacy-first personal cloud to keep all your files, apps, and games in one + secure place, accessible from anywhere at any time.`, recent: "Recent", recover_password: "Recover Password", refer_friends_c2a: "Get 1 GB for every friend who creates and confirms an account on Puter. Your friend will get 1 GB too!", @@ -153,6 +168,7 @@ const en = { send: "Send", send_password_recovery_email: "Send Password Recovery Email", session_saved: "Thank you for creating an account. This session has been saved.", + settings: "Settings", set_new_password: "Set New Password", share_to: "Share to", show_all_windows: "Show All Windows", @@ -164,15 +180,21 @@ const en = { skip: 'Skip', sort_by: 'Sort by', start: 'Start', + status: "Status", + storage_usage: "Storage Usage", taking_longer_than_usual: 'Taking a little longer than usual. Please wait...', + terms: "Terms", text_document: 'Text document', tos_fineprint: `By clicking 'Create Free Account' you agree to Puter's Terms of Service and Privacy Policy.`, trash: 'Trash', type: 'Type', + type_confirm_to_delete_account: "Type 'confirm' to delete your account.", undo: 'Undo', + unlimited: 'Unlimited', unzip: "Unzip", upload: 'Upload', upload_here: 'Upload here', + usage: 'Usage', username: "Username", username_changed: 'Username updated successfully.', versions: "Versions", diff --git a/src/i18n/translations/es.js b/src/i18n/translations/es.js index 82005c4e..e6ccca5b 100644 --- a/src/i18n/translations/es.js +++ b/src/i18n/translations/es.js @@ -18,6 +18,7 @@ */ const es = { name: "Español", + english_name: "Spanish", code: "es", dictionary: { access_granted_to: "Acceso Permitido A", diff --git a/src/i18n/translations/fa.js b/src/i18n/translations/fa.js index 10068094..e2d3b94d 100644 --- a/src/i18n/translations/fa.js +++ b/src/i18n/translations/fa.js @@ -18,6 +18,7 @@ */ const fa = { name: "فارسی", + english_name: "Farsi", code: "fa", dictionary: { access_granted_to: "دسترسی داده شده به", diff --git a/src/i18n/translations/fi.js b/src/i18n/translations/fi.js index d43d0753..9950f10f 100644 --- a/src/i18n/translations/fi.js +++ b/src/i18n/translations/fi.js @@ -18,6 +18,7 @@ */ const fi = { name: "Suomi", + english_name: "Finnish", code: "fi", dictionary: { access_granted_to: "Käyttöoikeus Myönnetty", diff --git a/src/i18n/translations/fr.js b/src/i18n/translations/fr.js index 1ce604af..bff28b64 100644 --- a/src/i18n/translations/fr.js +++ b/src/i18n/translations/fr.js @@ -18,6 +18,7 @@ */ const fr = { name: "Français", + english_name: "French", code: "fr", dictionary: { access_granted_to: "Accès accordé à", diff --git a/src/i18n/translations/hy.js b/src/i18n/translations/hy.js index 1388753b..01c4d6cc 100644 --- a/src/i18n/translations/hy.js +++ b/src/i18n/translations/hy.js @@ -18,6 +18,7 @@ */ const hy = { name: "Հայերեն", + english_name: "Armenian", code: "hy", dictionary: { access_granted_to: "Մուտքը տրված է՝", diff --git a/src/i18n/translations/it.js b/src/i18n/translations/it.js index c967671b..abace233 100644 --- a/src/i18n/translations/it.js +++ b/src/i18n/translations/it.js @@ -18,6 +18,7 @@ */ const it = { name: "Italiano", + english_name: "Italian", code: "it", dictionary: { access_granted_to: "Accesso garantito a", diff --git a/src/i18n/translations/ko.js b/src/i18n/translations/ko.js index 517b264e..6ec427e2 100644 --- a/src/i18n/translations/ko.js +++ b/src/i18n/translations/ko.js @@ -18,6 +18,7 @@ */ const ko = { name: "한국어", + english_name: "Korean", code: "ko", dictionary: { access_granted_to: "접근 권한 부여", diff --git a/src/i18n/translations/nb.js b/src/i18n/translations/nb.js index b5e309ff..50bfbe83 100644 --- a/src/i18n/translations/nb.js +++ b/src/i18n/translations/nb.js @@ -18,6 +18,7 @@ */ const nb = { name: "Norsk Bokmål", + english_name: "Norwegian Bokmål", code: "nb", dictionary: { access_granted_to: "Tilgang gitt til", diff --git a/src/i18n/translations/nn.js b/src/i18n/translations/nn.js index a4298e5b..471762ea 100644 --- a/src/i18n/translations/nn.js +++ b/src/i18n/translations/nn.js @@ -18,6 +18,7 @@ */ const nn = { name: "Norsk Nynorsk", + english_name: "Norwegian Nynorsk", code: "nn", dictionary: { access_granted_to: "Tilgang gjeven til", diff --git a/src/i18n/translations/ro.js b/src/i18n/translations/ro.js index 14adabba..feaa56b7 100644 --- a/src/i18n/translations/ro.js +++ b/src/i18n/translations/ro.js @@ -18,6 +18,7 @@ */ const ro = { name: "Română", + english_name: "Romanian", code: "ro", dictionary: { access_granted_to: "Acces acordat pentru", diff --git a/src/i18n/translations/sv.js b/src/i18n/translations/sv.js index 30f3c700..6136cdfb 100644 --- a/src/i18n/translations/sv.js +++ b/src/i18n/translations/sv.js @@ -18,6 +18,7 @@ */ const sv = { name: "Svenska", + english_name: "Swedish", code: "sv", dictionary: { access_granted_to: "Tillgång beviljad till", diff --git a/src/i18n/translations/th.js b/src/i18n/translations/th.js index f369143f..99383292 100644 --- a/src/i18n/translations/th.js +++ b/src/i18n/translations/th.js @@ -18,6 +18,7 @@ */ const th = { name: "ไทย", + english_name: "Thai", code: "th", dictionary: { access_granted_to: "อนุญาตให้เข้าถึง", diff --git a/src/i18n/translations/ur.js b/src/i18n/translations/ur.js index cfa51332..65c79eaf 100644 --- a/src/i18n/translations/ur.js +++ b/src/i18n/translations/ur.js @@ -18,6 +18,7 @@ */ const ur = { name: "اردو", + english_name: "Urdu", code: "ur", dictionary: { access_granted_to: "رسائی مسموح ہے", diff --git a/src/i18n/translations/zh.js b/src/i18n/translations/zh.js index 7066a4d0..82e44ab0 100644 --- a/src/i18n/translations/zh.js +++ b/src/i18n/translations/zh.js @@ -18,6 +18,7 @@ */ const zh = { name: "中文", + english_name: "Chinese", code: "zh", dictionary: { access_granted_to: "访问授权给", diff --git a/src/icons/cube-outline.svg b/src/icons/cube-outline.svg new file mode 100644 index 00000000..77faf2a5 --- /dev/null +++ b/src/icons/cube-outline.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/icons/danger.svg b/src/icons/danger.svg new file mode 100644 index 00000000..ce0d5592 --- /dev/null +++ b/src/icons/danger.svg @@ -0,0 +1,144 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/src/icons/language.svg b/src/icons/language.svg new file mode 100644 index 00000000..67a1c09f --- /dev/null +++ b/src/icons/language.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/icons/logo-outline.svg b/src/icons/logo-outline.svg new file mode 100644 index 00000000..397bb720 --- /dev/null +++ b/src/icons/logo-outline.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/icons/palette-fill.svg b/src/icons/palette-fill.svg new file mode 100644 index 00000000..d7a6a3bc --- /dev/null +++ b/src/icons/palette-fill.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/icons/palette-outline.svg b/src/icons/palette-outline.svg new file mode 100644 index 00000000..0ab25d65 --- /dev/null +++ b/src/icons/palette-outline.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/icons/speedometer-outline.svg b/src/icons/speedometer-outline.svg new file mode 100644 index 00000000..75e79c85 --- /dev/null +++ b/src/icons/speedometer-outline.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/icons/user.svg b/src/icons/user.svg new file mode 100644 index 00000000..a7b32245 --- /dev/null +++ b/src/icons/user.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/images/logo.png b/src/images/logo.png new file mode 100644 index 00000000..56603cba Binary files /dev/null and b/src/images/logo.png differ diff --git a/src/initgui.js b/src/initgui.js index b59307b7..280215b1 100644 --- a/src/initgui.js +++ b/src/initgui.js @@ -1870,7 +1870,7 @@ window.initgui = async function(){ */ $(document).on("logout", async function(event) { // is temp user? - if(window.user && window.user.is_temp){ + if(window.user && window.user.is_temp && !window.user.deleted){ const alert_resp = await UIAlert({ message: `Save account before logging out!

You are using a temporary account and logging out will erase all your data.

`, buttons:[