From 2b821c3e734f901da426a45670cbd31a87eda07b Mon Sep 17 00:00:00 2001 From: Nariman Jelveh Date: Fri, 19 Jul 2024 17:03:56 -0700 Subject: [PATCH] implement public profiles --- src/gui/src/UI/Settings/UITabAccount.js | 2 +- src/gui/src/UI/Settings/UITabProfile.js | 90 +++++++++++++++++++++ src/gui/src/UI/Settings/UIWindowSettings.js | 2 +- src/gui/src/UI/UIDesktop.js | 6 +- src/gui/src/UI/UIWindow.js | 25 ------ src/gui/src/UI/UIWindowDesktopBGSettings.js | 1 + src/gui/src/css/style.css | 12 ++- src/gui/src/helpers.js | 56 +++++++++++++ src/gui/src/helpers/launch_app.js | 1 - src/gui/src/i18n/translations/en.js | 1 + src/gui/src/icons/gear.svg | 3 + src/gui/src/icons/profile-black.svg | 12 +++ src/gui/src/services/SettingsService.js | 2 + 13 files changed, 182 insertions(+), 31 deletions(-) create mode 100644 src/gui/src/UI/Settings/UITabProfile.js create mode 100644 src/gui/src/icons/gear.svg create mode 100644 src/gui/src/icons/profile-black.svg diff --git a/src/gui/src/UI/Settings/UITabAccount.js b/src/gui/src/UI/Settings/UITabAccount.js index 47a027d7..d53f4664 100644 --- a/src/gui/src/UI/Settings/UITabAccount.js +++ b/src/gui/src/UI/Settings/UITabAccount.js @@ -27,7 +27,7 @@ import UIWindowManageSessions from '../UIWindowManageSessions.js'; export default { id: 'account', title_i18n_key: 'account', - icon: 'user.svg', + icon: 'gear.svg', html: () => { let h = `

${i18n('account')}

`; diff --git a/src/gui/src/UI/Settings/UITabProfile.js b/src/gui/src/UI/Settings/UITabProfile.js new file mode 100644 index 00000000..ba8dc50a --- /dev/null +++ b/src/gui/src/UI/Settings/UITabProfile.js @@ -0,0 +1,90 @@ +/** + * 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'; + +// About +export default { + id: 'profile', + title_i18n_key: 'profile', + icon: 'user.svg', + html: () => { + let h = ``; + + h += `
`; + // profile picture + h += `
`; + + // name + h += ``; + h += ``; + + // bio + h += ``; + h += ``; + + // save button + h += ``; + + h += `
`; + + return h; + }, + init: ($el_window) => { + + $el_window.find('.change-profile-picture').on('click', async function (e) { + // open dialog + UIWindow({ + path: '/' + window.user.username + '/Desktop', + // this is the uuid of the window to which this dialog will return + parent_uuid: $el_window.attr('data-element_uuid'), + allowed_file_types: ['image/*'], + show_maximize_button: false, + show_minimize_button: false, + title: 'Open', + is_dir: true, + is_openFileDialog: true, + selectable_body: false, + }); + }) + + $el_window.on('file_opened', async function(e){ + let selected_file = Array.isArray(e.detail) ? e.detail[0] : e.detail; + // set profile picture + const profile_pic = await puter.fs.read(selected_file.path) + // blob to base64 + const reader = new FileReader(); + reader.readAsDataURL(profile_pic); + reader.onloadend = function() { + const base64data = reader.result; + console.log(base64data) + // update profile picture + $el_window.find('.profile-picture').css('background-image', 'url(' + html_encode(base64data) + ')'); + // update profile picture + update_profile(window.user.username, {picture: base64data}) + } + }) + + $el_window.find('.save-profile').on('click', async function (e) { + const name = $el_window.find('#name').val(); + const bio = $el_window.find('#bio').val(); + update_profile(window.user.username, {name, bio}) + }); + }, +}; diff --git a/src/gui/src/UI/Settings/UIWindowSettings.js b/src/gui/src/UI/Settings/UIWindowSettings.js index fe80bf57..f1ecf88f 100644 --- a/src/gui/src/UI/Settings/UIWindowSettings.js +++ b/src/gui/src/UI/Settings/UIWindowSettings.js @@ -34,7 +34,7 @@ async function UIWindowSettings(options){ h += `
`; h += `
`; // sidebar toggle - h += ``; + h += ``; // sidebar h += `
`; // sidebar items diff --git a/src/gui/src/UI/UIDesktop.js b/src/gui/src/UI/UIDesktop.js index fcad9b24..2cc81f16 100644 --- a/src/gui/src/UI/UIDesktop.js +++ b/src/gui/src/UI/UIDesktop.js @@ -1005,7 +1005,7 @@ async function UIDesktop(options){ ht += `
`; // user options menu - ht += `
`; + ht += `
`; h += `${window.user.username}`; ht += `
`; ht += `
`; @@ -1226,6 +1226,10 @@ $(document).on('click', '.user-options-menu-btn', async function(e){ // create menu items users_arr.forEach(l_user => { + // construct profile picture image + let profile_pic = l_user.profile?.picture ? l_user.profile.picture : window.icons['profile.svg']; + profile_pic = ``; + items.push( { html: l_user.username, diff --git a/src/gui/src/UI/UIWindow.js b/src/gui/src/UI/UIWindow.js index 223a6d6a..3d265360 100644 --- a/src/gui/src/UI/UIWindow.js +++ b/src/gui/src/UI/UIWindow.js @@ -340,31 +340,6 @@ async function UIWindow(options) { if(options.is_dir){ // Detail layout header h += window.explore_table_headers(); - - // Maybe render iframe for users public directory - (() => { - if ( options.is_saveFileDialog || options.is_openFileDialog || options.is_directoryPicker ) { - return false; - } - - if ( ! options.path || ! options.path.startsWith('/') ) { // sus - return false; - } - - const components = options.path.slice(1).split('/'); - - if ( components.length === 2 && components[1] === 'Public' ) { - const username = components[0]; - h += ` - `; - } - })(); // Add 'This folder is empty' message by default h += `
This folder is empty
`; diff --git a/src/gui/src/UI/UIWindowDesktopBGSettings.js b/src/gui/src/UI/UIWindowDesktopBGSettings.js index 45a253f5..440c35c4 100644 --- a/src/gui/src/UI/UIWindowDesktopBGSettings.js +++ b/src/gui/src/UI/UIWindowDesktopBGSettings.js @@ -132,6 +132,7 @@ async function UIWindowDesktopBGSettings(options){ $(el_window).find('.desktop-bg-color-block-palette input').on('change', async function(e){ window.set_desktop_background({color: $(this).val()}) }) + $(el_window).on('file_opened', function(e){ let selected_file = Array.isArray(e.detail) ? e.detail[0] : e.detail; const fit = $(el_window).find('.desktop-bg-fit').val(); diff --git a/src/gui/src/css/style.css b/src/gui/src/css/style.css index 1948ee6f..86486968 100644 --- a/src/gui/src/css/style.css +++ b/src/gui/src/css/style.css @@ -158,7 +158,7 @@ body { z-index: -9999; } -input[type=text], input[type=password], input[type=email], select { +input[type=text], input[type=password], input[type=email], select, .form-input { width: 100%; padding: 8px; border: 1px solid #ccc; @@ -175,7 +175,7 @@ input[type=text], input[type=password], input[type=email], select { font-size: 17px; } -input[type=text]:focus, input[type=password]:focus, input[type=email]:focus, select:focus { +input[type=text]:focus, input[type=password]:focus, input[type=email]:focus, select:focus, .form-input:focus { border: 2px solid #01a0fd; padding: 7px; } @@ -4129,4 +4129,12 @@ fieldset[name=number-code] { } .settings-sidebar.active { display: block; +} +.profile-picture{ + background-size: cover; + background-position: center; + border-radius: 50%; +} +.change-profile-picture{ + cursor: pointer; } \ No newline at end of file diff --git a/src/gui/src/helpers.js b/src/gui/src/helpers.js index a85be51b..ce2b8c9c 100644 --- a/src/gui/src/helpers.js +++ b/src/gui/src/helpers.js @@ -445,6 +445,37 @@ window.update_auth_data = (auth_token, user)=>{ $('.user-email').html(html_encode(user.email)); } + // ---------------------------------------------------- + // get .profile file and update user profile + // ---------------------------------------------------- + user.profile = {}; + puter.fs.read('/'+user.username+'/Public/.profile').then((blob)=>{ + blob.text() + .then(text => { + const profile = JSON.parse(text); + if(profile.picture){ + window.user.profile.picture = html_encode(profile.picture); + } + + // update profile picture in GUI + if(window.user.profile.picture){ + $('.profile-pic').css('background-image', 'url('+window.user.profile.picture+')'); + } + }) + .catch(error => { + console.error('Error converting Blob to JSON:', error); + }); + }).catch((e)=>{ + if(e?.code === "subject_does_not_exist"){ + // create .profile file + puter.fs.write('/'+user.username+'/Public/.profile', JSON.stringify({})); + } + // Ignored + console.log(e); + }); + + // ---------------------------------------------------- + const to_storable_user = user => { const storable_user = {...user}; delete storable_user.taskbar_items; @@ -2419,3 +2450,28 @@ window.detectHostOS = function(){ } } + +window.update_profile = function(username, key_vals){ + puter.fs.read('/'+username+'/Public/.profile').then((blob)=>{ + blob.text() + .then(text => { + const profile = JSON.parse(text); + + for (const key in key_vals) { + profile[key] = key_vals[key]; + } + + puter.fs.write('/'+username+'/Public/.profile', JSON.stringify(profile)); + }) + .catch(error => { + console.error('Error converting Blob to JSON:', error); + }); + }).catch((e)=>{ + if(e?.code === "subject_does_not_exist"){ + // create .profile file + puter.fs.write('/'+username+'/Public/.profile', JSON.stringify({})); + } + // Ignored + console.log(e); + }); +} \ No newline at end of file diff --git a/src/gui/src/helpers/launch_app.js b/src/gui/src/helpers/launch_app.js index 607d9a30..5ee77a98 100644 --- a/src/gui/src/helpers/launch_app.js +++ b/src/gui/src/helpers/launch_app.js @@ -40,7 +40,6 @@ const launch_app = async (options)=>{ // explorer is a special case if(options.name === 'explorer'){ - console.log('explorer'); app_info = []; } else if(options.app_obj) diff --git a/src/gui/src/i18n/translations/en.js b/src/gui/src/i18n/translations/en.js index d653bb4e..590fa349 100644 --- a/src/gui/src/i18n/translations/en.js +++ b/src/gui/src/i18n/translations/en.js @@ -221,6 +221,7 @@ const en = { process_type_app: 'App', process_type_init: 'Init', process_type_ui: 'UI', + profile_picture: "Profile Picture", properties: "Properties", public: 'Public', publish: "Publish", diff --git a/src/gui/src/icons/gear.svg b/src/gui/src/icons/gear.svg new file mode 100644 index 00000000..19ddda9a --- /dev/null +++ b/src/gui/src/icons/gear.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/gui/src/icons/profile-black.svg b/src/gui/src/icons/profile-black.svg new file mode 100644 index 00000000..e26f4c85 --- /dev/null +++ b/src/gui/src/icons/profile-black.svg @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/src/gui/src/services/SettingsService.js b/src/gui/src/services/SettingsService.js index 0a743949..1eea89d7 100644 --- a/src/gui/src/services/SettingsService.js +++ b/src/gui/src/services/SettingsService.js @@ -19,6 +19,7 @@ import { Service } from "../definitions.js"; import AboutTab from '../UI/Settings/UITabAbout.js'; +import ProfileTab from '../UI/Settings/UITabProfile.js'; import UsageTab from '../UI/Settings/UITabUsage.js'; import AccountTab from '../UI/Settings/UITabAccount.js'; import SecurityTab from '../UI/Settings/UITabSecurity.js'; @@ -31,6 +32,7 @@ export class SettingsService extends Service { async _init () { ;[ AboutTab, + ProfileTab, UsageTab, AccountTab, SecurityTab,