mirror of
https://github.com/HeyPuter/puter
synced 2024-11-14 22:06:00 +00:00
implement public profiles
This commit is contained in:
parent
eae51e945b
commit
2b821c3e73
@ -27,7 +27,7 @@ import UIWindowManageSessions from '../UIWindowManageSessions.js';
|
|||||||
export default {
|
export default {
|
||||||
id: 'account',
|
id: 'account',
|
||||||
title_i18n_key: 'account',
|
title_i18n_key: 'account',
|
||||||
icon: 'user.svg',
|
icon: 'gear.svg',
|
||||||
html: () => {
|
html: () => {
|
||||||
let h = `<h1>${i18n('account')}</h1>`;
|
let h = `<h1>${i18n('account')}</h1>`;
|
||||||
|
|
||||||
|
90
src/gui/src/UI/Settings/UITabProfile.js
Normal file
90
src/gui/src/UI/Settings/UITabProfile.js
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import UIWindow from '../UIWindow.js';
|
||||||
|
|
||||||
|
// About
|
||||||
|
export default {
|
||||||
|
id: 'profile',
|
||||||
|
title_i18n_key: 'profile',
|
||||||
|
icon: 'user.svg',
|
||||||
|
html: () => {
|
||||||
|
let h = ``;
|
||||||
|
|
||||||
|
h += `<div style="overflow:hidden;">`;
|
||||||
|
// profile picture
|
||||||
|
h += `<div class="profile-picture change-profile-picture" style="background-image:url(${window.user.profile.picture ? window.user.profile.picture : window.icons['profile-black.svg']}); width:100px; height:100px; margin: 20px auto 0 auto;"></div>`;
|
||||||
|
|
||||||
|
// name
|
||||||
|
h += `<label for="name" style="display:block;">${i18n('name')}</label>`;
|
||||||
|
h += `<input type="text" id="name" value="${window.user.profile.name ?? ''}" style="width:100%; margin-bottom:10px;"/>`;
|
||||||
|
|
||||||
|
// bio
|
||||||
|
h += `<label for="bio" style="display:block;">${i18n('bio')}</label>`;
|
||||||
|
h += `<textarea id="bio" class="form-input" style="width:100%; height:200px; box-sizing: border-box; margin-bottom:10px; resize: none;">${window.user.profile.bio ?? ''}</textarea>`;
|
||||||
|
|
||||||
|
// save button
|
||||||
|
h += `<button class="button button-primary save-profile" style="margin: 0 auto; display:block;">${i18n('save')}</button>`;
|
||||||
|
|
||||||
|
h += `</div>`;
|
||||||
|
|
||||||
|
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})
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
@ -34,7 +34,7 @@ async function UIWindowSettings(options){
|
|||||||
h += `<div class="settings-container">`;
|
h += `<div class="settings-container">`;
|
||||||
h += `<div class="settings">`;
|
h += `<div class="settings">`;
|
||||||
// sidebar toggle
|
// sidebar toggle
|
||||||
h += `<button class="sidebar-toggle hidden-lg hidden-xl"><div class="sidebar-toggle-button"><span></span><span></span><span></span></div></button>`;
|
h += `<button class="sidebar-toggle hidden-md hidden-lg hidden-xl"><div class="sidebar-toggle-button"><span></span><span></span><span></span></div></button>`;
|
||||||
// sidebar
|
// sidebar
|
||||||
h += `<div class="settings-sidebar disable-user-select disable-context-menu">`;
|
h += `<div class="settings-sidebar disable-user-select disable-context-menu">`;
|
||||||
// sidebar items
|
// sidebar items
|
||||||
|
@ -1005,7 +1005,7 @@ async function UIDesktop(options){
|
|||||||
ht += `<div class="toolbar-btn qr-btn" title="QR code" style="background-image:url(${window.icons['qr.svg']})"></div>`;
|
ht += `<div class="toolbar-btn qr-btn" title="QR code" style="background-image:url(${window.icons['qr.svg']})"></div>`;
|
||||||
|
|
||||||
// user options menu
|
// user options menu
|
||||||
ht += `<div class="toolbar-btn user-options-menu-btn" style="background-image:url(${window.icons['profile.svg']})">`;
|
ht += `<div class="toolbar-btn user-options-menu-btn profile-pic" style="background-image:url(${window.user.profile.picture ? window.user.profile.picture : window.icons['profile.svg']})">`;
|
||||||
h += `<span class="user-options-menu-username">${window.user.username}</span>`;
|
h += `<span class="user-options-menu-username">${window.user.username}</span>`;
|
||||||
ht += `</div>`;
|
ht += `</div>`;
|
||||||
ht += `</div>`;
|
ht += `</div>`;
|
||||||
@ -1226,6 +1226,10 @@ $(document).on('click', '.user-options-menu-btn', async function(e){
|
|||||||
|
|
||||||
// create menu items
|
// create menu items
|
||||||
users_arr.forEach(l_user => {
|
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 = `<img src="${profile_pic}" style="width: 20px; height: 20px; border-radius: 50%; margin-right: 5px;">`;
|
||||||
|
|
||||||
items.push(
|
items.push(
|
||||||
{
|
{
|
||||||
html: l_user.username,
|
html: l_user.username,
|
||||||
|
@ -341,31 +341,6 @@ async function UIWindow(options) {
|
|||||||
// Detail layout header
|
// Detail layout header
|
||||||
h += window.explore_table_headers();
|
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 += `<iframe
|
|
||||||
style="display:block;width:100%"
|
|
||||||
tabindex="-1"
|
|
||||||
frameborder="0"
|
|
||||||
src="http://${username}.at.${window.hosting_domain}"
|
|
||||||
height=150
|
|
||||||
></iframe>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
|
|
||||||
// Add 'This folder is empty' message by default
|
// Add 'This folder is empty' message by default
|
||||||
h += `<div class="explorer-empty-message">This folder is empty</div>`;
|
h += `<div class="explorer-empty-message">This folder is empty</div>`;
|
||||||
|
|
||||||
|
@ -132,6 +132,7 @@ async function UIWindowDesktopBGSettings(options){
|
|||||||
$(el_window).find('.desktop-bg-color-block-palette input').on('change', async function(e){
|
$(el_window).find('.desktop-bg-color-block-palette input').on('change', async function(e){
|
||||||
window.set_desktop_background({color: $(this).val()})
|
window.set_desktop_background({color: $(this).val()})
|
||||||
})
|
})
|
||||||
|
|
||||||
$(el_window).on('file_opened', function(e){
|
$(el_window).on('file_opened', function(e){
|
||||||
let selected_file = Array.isArray(e.detail) ? e.detail[0] : e.detail;
|
let selected_file = Array.isArray(e.detail) ? e.detail[0] : e.detail;
|
||||||
const fit = $(el_window).find('.desktop-bg-fit').val();
|
const fit = $(el_window).find('.desktop-bg-fit').val();
|
||||||
|
@ -158,7 +158,7 @@ body {
|
|||||||
z-index: -9999;
|
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%;
|
width: 100%;
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
border: 1px solid #ccc;
|
border: 1px solid #ccc;
|
||||||
@ -175,7 +175,7 @@ input[type=text], input[type=password], input[type=email], select {
|
|||||||
font-size: 17px;
|
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;
|
border: 2px solid #01a0fd;
|
||||||
padding: 7px;
|
padding: 7px;
|
||||||
}
|
}
|
||||||
@ -4130,3 +4130,11 @@ fieldset[name=number-code] {
|
|||||||
.settings-sidebar.active {
|
.settings-sidebar.active {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
.profile-picture{
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
.change-profile-picture{
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
@ -445,6 +445,37 @@ window.update_auth_data = (auth_token, user)=>{
|
|||||||
$('.user-email').html(html_encode(user.email));
|
$('.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 to_storable_user = user => {
|
||||||
const storable_user = {...user};
|
const storable_user = {...user};
|
||||||
delete storable_user.taskbar_items;
|
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);
|
||||||
|
});
|
||||||
|
}
|
@ -40,7 +40,6 @@ const launch_app = async (options)=>{
|
|||||||
|
|
||||||
// explorer is a special case
|
// explorer is a special case
|
||||||
if(options.name === 'explorer'){
|
if(options.name === 'explorer'){
|
||||||
console.log('explorer');
|
|
||||||
app_info = [];
|
app_info = [];
|
||||||
}
|
}
|
||||||
else if(options.app_obj)
|
else if(options.app_obj)
|
||||||
|
@ -221,6 +221,7 @@ const en = {
|
|||||||
process_type_app: 'App',
|
process_type_app: 'App',
|
||||||
process_type_init: 'Init',
|
process_type_init: 'Init',
|
||||||
process_type_ui: 'UI',
|
process_type_ui: 'UI',
|
||||||
|
profile_picture: "Profile Picture",
|
||||||
properties: "Properties",
|
properties: "Properties",
|
||||||
public: 'Public',
|
public: 'Public',
|
||||||
publish: "Publish",
|
publish: "Publish",
|
||||||
|
3
src/gui/src/icons/gear.svg
Normal file
3
src/gui/src/icons/gear.svg
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-gear-wide-connected" viewBox="0 0 16 16">
|
||||||
|
<path d="M7.068.727c.243-.97 1.62-.97 1.864 0l.071.286a.96.96 0 0 0 1.622.434l.205-.211c.695-.719 1.888-.03 1.613.931l-.08.284a.96.96 0 0 0 1.187 1.187l.283-.081c.96-.275 1.65.918.931 1.613l-.211.205a.96.96 0 0 0 .434 1.622l.286.071c.97.243.97 1.62 0 1.864l-.286.071a.96.96 0 0 0-.434 1.622l.211.205c.719.695.03 1.888-.931 1.613l-.284-.08a.96.96 0 0 0-1.187 1.187l.081.283c.275.96-.918 1.65-1.613.931l-.205-.211a.96.96 0 0 0-1.622.434l-.071.286c-.243.97-1.62.97-1.864 0l-.071-.286a.96.96 0 0 0-1.622-.434l-.205.211c-.695.719-1.888.03-1.613-.931l.08-.284a.96.96 0 0 0-1.186-1.187l-.284.081c-.96.275-1.65-.918-.931-1.613l.211-.205a.96.96 0 0 0-.434-1.622l-.286-.071c-.97-.243-.97-1.62 0-1.864l.286-.071a.96.96 0 0 0 .434-1.622l-.211-.205c-.719-.695-.03-1.888.931-1.613l.284.08a.96.96 0 0 0 1.187-1.186l-.081-.284c-.275-.96.918-1.65 1.613-.931l.205.211a.96.96 0 0 0 1.622-.434zM12.973 8.5H8.25l-2.834 3.779A4.998 4.998 0 0 0 12.973 8.5m0-1a4.998 4.998 0 0 0-7.557-3.779l2.834 3.78zM5.048 3.967l-.087.065zm-.431.355A4.98 4.98 0 0 0 3.002 8c0 1.455.622 2.765 1.615 3.678L7.375 8zm.344 7.646.087.065z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
12
src/gui/src/icons/profile-black.svg
Normal file
12
src/gui/src/icons/profile-black.svg
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="59px" height="59px" stroke-width="0.8" viewBox="0 0 24 24" fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg" color="#3b3e44">
|
||||||
|
<path
|
||||||
|
d="M12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2Z"
|
||||||
|
stroke="#3b3e44" stroke-width="0.8" stroke-linecap="round" stroke-linejoin="round"></path>
|
||||||
|
<path d="M4.271 18.3457C4.271 18.3457 6.50002 15.5 12 15.5C17.5 15.5 19.7291 18.3457 19.7291 18.3457"
|
||||||
|
stroke="#3b3e44" stroke-width="0.8" stroke-linecap="round" stroke-linejoin="round"></path>
|
||||||
|
<path
|
||||||
|
d="M12 12C13.6569 12 15 10.6569 15 9C15 7.34315 13.6569 6 12 6C10.3431 6 9 7.34315 9 9C9 10.6569 10.3431 12 12 12Z"
|
||||||
|
stroke="#3b3e44" stroke-width="0.8" stroke-linecap="round" stroke-linejoin="round"></path>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 856 B |
@ -19,6 +19,7 @@
|
|||||||
import { Service } from "../definitions.js";
|
import { Service } from "../definitions.js";
|
||||||
|
|
||||||
import AboutTab from '../UI/Settings/UITabAbout.js';
|
import AboutTab from '../UI/Settings/UITabAbout.js';
|
||||||
|
import ProfileTab from '../UI/Settings/UITabProfile.js';
|
||||||
import UsageTab from '../UI/Settings/UITabUsage.js';
|
import UsageTab from '../UI/Settings/UITabUsage.js';
|
||||||
import AccountTab from '../UI/Settings/UITabAccount.js';
|
import AccountTab from '../UI/Settings/UITabAccount.js';
|
||||||
import SecurityTab from '../UI/Settings/UITabSecurity.js';
|
import SecurityTab from '../UI/Settings/UITabSecurity.js';
|
||||||
@ -31,6 +32,7 @@ export class SettingsService extends Service {
|
|||||||
async _init () {
|
async _init () {
|
||||||
;[
|
;[
|
||||||
AboutTab,
|
AboutTab,
|
||||||
|
ProfileTab,
|
||||||
UsageTab,
|
UsageTab,
|
||||||
AccountTab,
|
AccountTab,
|
||||||
SecurityTab,
|
SecurityTab,
|
||||||
|
Loading…
Reference in New Issue
Block a user