feat: Implement profile pictures

This commit is contained in:
jelveh 2024-11-16 19:07:09 -08:00
parent 59fa600f2b
commit 0885937f03
9 changed files with 216 additions and 51 deletions

View File

@ -14,7 +14,8 @@
"/lib/timeago.min.js",
"/lib/iro.min.js",
"/lib/isMobile.min.js",
"/lib/fflate-0.8.2.min.js"
"/lib/fflate-0.8.2.min.js",
"/lib/croppie.min.js"
],
"css_paths": [
"/css/normalize.css",

View File

@ -22,6 +22,7 @@ import UIWindowChangeEmail from './UIWindowChangeEmail.js';
import UIWindowChangeUsername from '../UIWindowChangeUsername.js';
import UIWindowConfirmUserDeletion from './UIWindowConfirmUserDeletion.js';
import UIWindowManageSessions from '../UIWindowManageSessions.js';
import UIWindow from '../UIWindow.js';
// About
export default {
@ -29,7 +30,14 @@ export default {
title_i18n_key: 'account',
icon: 'user.svg',
html: () => {
let h = `<h1>${i18n('account')}</h1>`;
let h = '';
// h += `<h1>${i18n('account')}</h1>`;
// profile picture
h += `<div style="overflow: hidden; display: flex; margin-bottom: 20px; flex-direction: column; align-items: center;">`;
h += `<div class="profile-picture change-profile-picture" style="background-image: url('${html_encode(window.user?.profile?.picture ?? window.icons['profile.svg'])}');">`;
h += `</div>`;
h += `</div>`;
// change password button
if(!window.user.is_temp){
@ -125,5 +133,49 @@ export default {
}
});
});
$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: ['.png', '.jpg', '.jpeg'],
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() {
// resizes the image to 150x150
const img = new Image();
img.src = reader.result;
img.onload = function() {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = 150;
canvas.height = 150;
ctx.drawImage(img, 0, 0, 150, 150);
const base64data = canvas.toDataURL('image/png');
// update profile picture
$el_window.find('.profile-picture').css('background-image', 'url(' + html_encode(base64data) + ')');
$('.profile-image').css('background-image', 'url(' + html_encode(base64data) + ')');
$('.profile-image').addClass('profile-image-has-picture');
// update profile picture
update_profile(window.user.username, {picture: base64data})
}
}
})
},
};

View File

@ -1052,8 +1052,8 @@ async function UIDesktop(options){
ht += `<div class="toolbar-btn search-btn" title="Search" style="background-image:url('${window.icons['search.svg']}')"></div>`;
// user options menu
ht += `<div class="toolbar-btn user-options-menu-btn" style="background-image:url(${window.icons['profile.svg']})">`;
h += `<span class="user-options-menu-username">${window.user.username}</span>`;
ht += `<div class="toolbar-btn user-options-menu-btn profile-pic" style="display:block;">`;
ht += `<div class="profile-image ${window.user?.profile?.picture && 'profile-image-has-picture'}" style="border-radius: 50%; background-image:url(${window.user?.profile?.picture || window.icons['profile.svg']}); box-sizing: border-box; width: 17px !important; height: 17px !important; background-size: contain; background-repeat: no-repeat; background-position: center; background-position: center; background-size: cover;"></div>`;
ht += `</div>`;
ht += `</div>`;

View File

@ -32,10 +32,14 @@ async function UIWindowSessionList(options){
h += `<div class="loading">${i18n('signing_in')}</div>`;
// session list
h += `<div class="hide-scrollbar" style="overflow-y: scroll; max-width: 400px; margin: 0 auto;">`;
h += `<h1 style="text-align: center; font-size: 18px; font-weight: normal; color: #757575;"><img src="${window.icons['logo-white.svg']}" style="padding: 4px; background-color: blue; border-radius: 5px; width: 25px; box-sizing: border-box; margin-bottom: -6px; margin-right: 6px;">${i18n('sign_in_with_puter')}</h1>`
h += `<h1 style="text-align: center; font-size: 18px; font-weight: normal; color: #757575; margin-bottom: 30px;"><img src="${window.icons['logo-white.svg']}" style="padding: 4px; background-color: blue; border-radius: 5px; width: 25px; box-sizing: border-box; margin-bottom: -6px; margin-right: 6px;">${i18n('sign_in_with_puter')}</h1>`
for (let index = 0; index < window.logged_in_users.length; index++) {
const l_user = window.logged_in_users[index];
h += `<div data-uuid="${l_user.uuid}" class="session-entry">${l_user.username}</div>`;
h += `<div data-uuid="${l_user.uuid}" class="session-entry" style="display: flex; padding: 15px 10px;">`;
// profile picture
h += `<div class="profile-picture" style="background-color: #cbced1; width: 30px; height: 30px; margin:0; margin-right: 10px; background-image: url('${l_user.profile.picture ?? window.icons['profile.svg']}');"></div>`;
h += `<div style="display: flex; align-items: center;">${l_user.username}</div>`;
h += `</div>`;
}
h += `</div>`;
// c2a

View File

@ -79,7 +79,7 @@
font-variation-settings: "slnt"0;
}
pre{
pre {
font-family: "Inter", "Helvetica Neue", HelveticaNeue, Helvetica, Arial, sans-serif;
}
@ -611,7 +611,7 @@ span.header-sort-icon img {
}
.item-name, .item-name-editor, .item-name-shadow {
font-size: 13px;
font-size: 12px;
color: white;
text-shadow: 0px 0px 3px #00000082, 0px 0px 3px #00000082, 0px 0px 3px #00000082;
-webkit-font-smoothing: antialiased;
@ -1263,27 +1263,32 @@ span.header-sort-icon img {
background-color: #fefeff;
}
.window-sidebar-item-placeholder{
.window-sidebar-item-placeholder {
height: 27px !important;
}
.window-sidebar-item {
cursor: pointer !important;
user-select: none;
}
.window-sidebar-item:not(.window-sidebar-title):hover {
cursor: grab;
}
.window-sidebar-item.grabbing {
cursor: grabbing !important;
}
.window-sidebar-item-dragging {
background-color: #f5f5f5 !important;
opacity: 0.8;
cursor: grabbing;
}
.ui-sortable-helper {
background: white !important;
box-shadow: 0 2px 8px rgba(0,0,0,0.1) !important;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1) !important;
}
.window-sidebar-item-icon {
@ -1362,16 +1367,17 @@ span.header-sort-icon img {
flex-grow: 1;
}
.window-filedialog-upload-here{
.window-filedialog-upload-here {
-webkit-font-smoothing: antialiased;
opacity: 0.7;
font-size: 14px;
}
.window-filedialog-upload-here:hover{
.window-filedialog-upload-here:hover {
cursor: pointer;
opacity: 1;
}
.savefiledialog-save-btn, .openfiledialog-open-btn {
margin-left: 10px;
}
@ -1590,7 +1596,8 @@ span.header-sort-icon img {
border-bottom: none;
border-top: 1px solid #00000033;
}
.context-menu .context-menu-divider{
.context-menu .context-menu-divider {
padding-top: 5px;
padding-bottom: 5px;
}
@ -2115,11 +2122,11 @@ label {
background-color: #9dacbd;
}
.permission-editor-badge{
.permission-editor-badge {
background-color: #007cff;
}
.permission-viewer-badge{
.permission-viewer-badge {
background-color: #41c95d;
}
@ -2168,20 +2175,24 @@ label {
max-height: 200px;
overflow: hidden;
}
.ui-menu{
.ui-menu {
margin-top: 5px;
border-radius: 5px;
}
.ui-menu .ui-menu-item{
.ui-menu .ui-menu-item {
padding: 5px 10px;
border-radius: 5px;
}
.ui-menu .ui-menu-item .ui-menu-item-wrapper {
background: none;
border: none;
padding: 5px 10px;
font-size: 14px;
}
.ui-menu .ui-menu-item:hover .ui-menu-item-wrapper,
.ui-menu .ui-menu-item:focus .ui-menu-item-wrapper,
.ui-menu .ui-menu-item:active .ui-menu-item-wrapper,
@ -2856,13 +2867,14 @@ fieldset[name=number-code] {
opacity: 1;
}
.welcome-window-close-button{
.welcome-window-close-button {
opacity: 0.7;
font-weight: 300;
top: 5px;
right: 5px;
}
.welcome-window-close-button:hover{
.welcome-window-close-button:hover {
opacity: 1;
}
@ -3690,7 +3702,7 @@ fieldset[name=number-code] {
margin-top: 1px;
}
.settings-sidebar-title{
.settings-sidebar-title {
margin-bottom: 20px;
font-weight: bold;
-webkit-font-smoothing: antialiased;
@ -3835,6 +3847,30 @@ fieldset[name=number-code] {
opacity: 1;
}
.profile-picture {
cursor: pointer;
position: relative;
overflow: hidden;
background-position: center;
background-size: cover;
background-repeat: no-repeat;
border: 1px solid #EEE;
width: 120px;
height: 120px;
border-radius: 50%;
margin-right: 0;
margin-top: 20px;
margin-bottom: 20px;
background-color: #c5cdd4;
}
.profile-picture:hover {
background-color: #a6afb7;
}
.profile-image-has-picture{
border: 1px solid white;
}
.driver-usage {
background-color: white;
bottom: 0;
@ -3982,7 +4018,7 @@ fieldset[name=number-code] {
background-color: #f6f6f6;
}
.language-item .checkmark{
.language-item .checkmark {
width: 15px;
height: 15px;
border-radius: 50%;
@ -3991,9 +4027,11 @@ fieldset[name=number-code] {
position: absolute;
right: 10px;
}
.language-item.active {
background-color: #e0e0e0;
}
.language-item.active .checkmark {
display: inline-block;
}
@ -4011,9 +4049,11 @@ fieldset[name=number-code] {
align-items: center;
height: 45px;
}
.settings-card .button{
.settings-card .button {
box-shadow: none;
}
.thin-card {
padding: 0 15px;
}
@ -4193,6 +4233,7 @@ fieldset[name=number-code] {
.visible-xs {
display: block !important;
}
.settings-sidebar {
display: none;
position: fixed;
@ -4205,6 +4246,7 @@ fieldset[name=number-code] {
.visible-sm {
display: block !important;
}
.settings-sidebar {
display: none;
position: fixed;
@ -4231,46 +4273,49 @@ fieldset[name=number-code] {
}
}
.sidebar-toggle{
position: absolute;
.sidebar-toggle {
position: absolute;
z-index: 9999999999;
left: 2px;
border: 0;
padding-top: 5px;
padding-bottom: 5px;
border: 0;
padding-top: 5px;
padding-bottom: 5px;
top: 3px;
}
.sidebar-toggle .sidebar-toggle-button {
height: 20px;
height: 20px;
width: 20px;
}
.sidebar-toggle span:nth-child(1) {
margin-top: 5px;
margin-top: 5px;
}
.sidebar-toggle span {
border-bottom: 2px solid #858585;
display: block;
margin-bottom: 5px;
width: 100%;
border-bottom: 2px solid #858585;
display: block;
margin-bottom: 5px;
width: 100%;
}
.settings-sidebar.active {
display: block;
}
.welcome-window-footer{
position: absolute; bottom: 20px;
.welcome-window-footer {
position: absolute;
bottom: 20px;
}
.welcome-window-footer a{
.welcome-window-footer a {
color: #727c8d;
text-decoration: none;
font-size: 12px;
-webkit-font-smoothing: antialiased;
}
.welcome-window-footer a:hover{
.welcome-window-footer a:hover {
color: #1d1e23;
}
@ -4279,7 +4324,7 @@ fieldset[name=number-code] {
* Search
* ------------------------------------
*/
.search-input-wrapper{
.search-input-wrapper {
width: 100%;
border-radius: 5px;
padding-bottom: 10px;
@ -4290,13 +4335,15 @@ fieldset[name=number-code] {
box-sizing: border-box;
background: #f1f6fc;
}
.search-input{
.search-input {
padding-left: 33px !important;
background-repeat: no-repeat;
background-position: 5px center;
background-size: 20px;
}
.search-results{
.search-results {
padding-right: 15px;
margin-top: 70px;
padding-left: 15px;
@ -4304,18 +4351,21 @@ fieldset[name=number-code] {
padding-bottom: 5px;
display: none;
}
.search-result{
padding: 10px; cursor: pointer;
.search-result {
padding: 10px;
cursor: pointer;
font-size: 13px;
display: flex;
align-items: center;
}
.search-result-active{
.search-result-active {
background-color: #4092da;
color: #fff;
border-radius: 5px;
}
.search-results .search-result:last-child {
margin-bottom: 0;
}

View File

@ -445,6 +445,35 @@ window.update_auth_data = async (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({}));
}
});
// ----------------------------------------------------
const to_storable_user = user => {
const storable_user = {...user};
delete storable_user.taskbar_items;
@ -2597,3 +2626,29 @@ 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];
// update window.user.profile
window.user.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);
});
}

1
src/gui/src/lib/croppie.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -32,6 +32,7 @@ const lib_paths =[
`/lib/iro.min.js`,
`/lib/isMobile.min.js`,
`/lib/fflate-0.8.2.min.js`,
`/lib/croppie.min.js`
]
// Ordered list of CSS stylesheets

View File

@ -11,5 +11,6 @@ module.exports = [
"timeago.min.js",
"iro.min.js",
"isMobile.min.js",
"fflate-0.8.2.min.js"
"fflate-0.8.2.min.js",
"croppie.min.js"
];