mirror of
https://github.com/HeyPuter/puter.git
synced 2025-12-30 09:40:00 -06:00
Enhance login GUI: improve layout and add show/hide password (#1599)
This commit is contained in:
18
package-lock.json
generated
18
package-lock.json
generated
@@ -46,6 +46,9 @@
|
||||
"webpack": "^5.88.2",
|
||||
"webpack-cli": "^5.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"sharp": "^0.34.3",
|
||||
"sharp-bmp": "^0.1.5",
|
||||
@@ -2313,7 +2316,7 @@
|
||||
"resolved": "src/puter-wisp",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@heyputer/puterjs": {
|
||||
"node_modules/@heyputer/puter.js": {
|
||||
"resolved": "src/puter-js",
|
||||
"link": true
|
||||
},
|
||||
@@ -20238,7 +20241,7 @@
|
||||
"@rollup/plugin-node-resolve": "^15.0.2",
|
||||
"@rollup/plugin-replace": "^5.0.2",
|
||||
"mocha": "^10.8.2",
|
||||
"rollup": "^3.21.4",
|
||||
"rollup": "^3.29.5",
|
||||
"rollup-plugin-copy": "^3.4.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
@@ -20373,11 +20376,12 @@
|
||||
"license": "AGPL-3.0-only"
|
||||
},
|
||||
"src/puter-js": {
|
||||
"name": "@heyputer/puterjs",
|
||||
"version": "1.0.0",
|
||||
"name": "@heyputer/puter.js",
|
||||
"version": "2.0.11",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@heyputer/kv.js": "^0.1.92"
|
||||
"@heyputer/kv.js": "^0.1.92",
|
||||
"@heyputer/putility": "^1.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"concurrently": "^8.2.2",
|
||||
@@ -20391,7 +20395,7 @@
|
||||
},
|
||||
"src/putility": {
|
||||
"name": "@heyputer/putility",
|
||||
"version": "1.0.2",
|
||||
"version": "1.0.3",
|
||||
"license": "AGPL-3.0-only"
|
||||
},
|
||||
"src/terminal": {
|
||||
@@ -20408,7 +20412,7 @@
|
||||
"@rollup/plugin-node-resolve": "^15.0.2",
|
||||
"@rollup/plugin-replace": "^5.0.2",
|
||||
"http-server": "^14.1.1",
|
||||
"mocha": "^10.2.0",
|
||||
"mocha": "^10.8.2",
|
||||
"rollup": "^3.29.5",
|
||||
"rollup-plugin-copy": "^3.4.0"
|
||||
}
|
||||
|
||||
@@ -1,23 +1,12 @@
|
||||
/**
|
||||
* 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/>.
|
||||
* Improved PasswordEntry Component for Puter GUI
|
||||
* Features:
|
||||
* - DRY: input styles reused from shared CSS class
|
||||
* - Password strength meter (weak/medium/strong)
|
||||
* - Caps Lock warning
|
||||
* - Accessible show/hide toggle button
|
||||
*/
|
||||
|
||||
|
||||
const Component = use('util.Component');
|
||||
|
||||
export default def(class PasswordEntry extends Component {
|
||||
@@ -36,80 +25,73 @@ export default def(class PasswordEntry extends Component {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
input {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
/* TODO: I'd rather not duplicate this */
|
||||
.error {
|
||||
display: none;
|
||||
color: red;
|
||||
border: 1px solid red;
|
||||
border-radius: 4px;
|
||||
padding: 9px;
|
||||
margin-bottom: 15px;
|
||||
text-align: center;
|
||||
font-size: 13px;
|
||||
}
|
||||
.error-message {
|
||||
display: none;
|
||||
color: rgb(215 2 2);
|
||||
font-size: 14px;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
margin: 10px 0;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid rgb(215 2 2);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.password-and-toggle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.password-and-toggle input {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
|
||||
/* TODO: DRY: This is from style.css */
|
||||
input[type=text], input[type=password], input[type=email], select {
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
border: 1px solid #ccc;
|
||||
.strength-meter {
|
||||
height: 6px;
|
||||
border-radius: 4px;
|
||||
box-sizing: border-box;
|
||||
outline: none;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
color: #393f46;
|
||||
font-size: 14px;
|
||||
margin-top: 6px;
|
||||
background: #eee;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* to prevent auto-zoom on input focus in mobile */
|
||||
.device-phone input[type=text], .device-phone input[type=password], .device-phone input[type=email], .device-phone select {
|
||||
font-size: 17px;
|
||||
.strength-bar {
|
||||
height: 100%;
|
||||
width: 0%;
|
||||
transition: width 0.3s;
|
||||
}
|
||||
|
||||
input[type=text]:focus, input[type=password]:focus, input[type=email]:focus, select:focus {
|
||||
border: 2px solid #01a0fd;
|
||||
padding: 7px;
|
||||
.strength-weak { background: #e74c3c; }
|
||||
.strength-medium { background: #f39c12; }
|
||||
.strength-strong { background: #2ecc71; }
|
||||
|
||||
.caps-warning {
|
||||
display: none;
|
||||
font-size: 12px;
|
||||
margin-top: 5px;
|
||||
color: #e67e22;
|
||||
}
|
||||
`;
|
||||
|
||||
create_template ({ template }) {
|
||||
$(template).html(/*html*/`
|
||||
<form>
|
||||
<div class="error"></div>
|
||||
<div class="error-message"></div>
|
||||
<div class="password-and-toggle">
|
||||
<input type="password" class="value-input" id="password" placeholder="${i18n('password')}" required>
|
||||
<img
|
||||
id="toggle-show-password"
|
||||
src="${this.get('show_password')
|
||||
? window.icons["eye-closed.svg"]
|
||||
: window.icons["eye-open.svg"]}"
|
||||
width="20"
|
||||
height="20">
|
||||
<input type="password"
|
||||
class="value-input"
|
||||
id="password"
|
||||
placeholder="${i18n('password')}"
|
||||
aria-label="Password"
|
||||
required>
|
||||
<button type="button" id="toggle-show-password" aria-label="Show or hide password">
|
||||
<img src="${window.icons["eye-open.svg"]}" width="20" height="20">
|
||||
</button>
|
||||
</div>
|
||||
<div class="strength-meter">
|
||||
<div class="strength-bar"></div>
|
||||
</div>
|
||||
<div class="caps-warning">⚠️ Caps Lock is ON</div>
|
||||
</form>
|
||||
`);
|
||||
}
|
||||
@@ -119,38 +101,73 @@ export default def(class PasswordEntry extends Component {
|
||||
}
|
||||
|
||||
on_ready ({ listen }) {
|
||||
const input = $(this.dom_).find('#password');
|
||||
const strengthBar = $(this.dom_).find('.strength-bar');
|
||||
const capsWarning = $(this.dom_).find('.caps-warning');
|
||||
const errorBox = $(this.dom_).find('.error-message');
|
||||
|
||||
// Show errors
|
||||
listen('error', (error) => {
|
||||
if ( ! error ) return $(this.dom_).find('.error').hide();
|
||||
$(this.dom_).find('.error').text(error).show();
|
||||
if (!error) return errorBox.hide();
|
||||
errorBox.text(error).show();
|
||||
});
|
||||
|
||||
// Reset input value if cleared
|
||||
listen('value', (value) => {
|
||||
// clear input
|
||||
if ( value === undefined ) {
|
||||
$(this.dom_).find('input').val('');
|
||||
}
|
||||
if (value === undefined) input.val('');
|
||||
});
|
||||
|
||||
const input = $(this.dom_).find('input');
|
||||
// Input listener
|
||||
input.on('input', () => {
|
||||
this.set('value', input.val());
|
||||
const value = input.val();
|
||||
this.set('value', value);
|
||||
this.updateStrength(value, strengthBar);
|
||||
});
|
||||
|
||||
// Caps Lock detection
|
||||
input.on('keyup keydown', (e) => {
|
||||
const isCaps = e.getModifierState && e.getModifierState('CapsLock');
|
||||
capsWarning.toggle(isCaps);
|
||||
});
|
||||
|
||||
// Submit on Enter
|
||||
const on_submit = this.get('on_submit');
|
||||
if ( on_submit ) {
|
||||
$(this.dom_).find('input').on('keyup', (e) => {
|
||||
if ( e.key === 'Enter' ) {
|
||||
on_submit();
|
||||
}
|
||||
if (on_submit) {
|
||||
input.on('keyup', (e) => {
|
||||
if (e.key === 'Enter') on_submit();
|
||||
});
|
||||
}
|
||||
|
||||
$(this.dom_).find("#toggle-show-password").on("click", () => {
|
||||
|
||||
// Toggle password visibility
|
||||
$(this.dom_).find('#toggle-show-password').on('click', () => {
|
||||
this.set('show_password', !this.get('show_password'));
|
||||
const show_password = this.get('show_password');
|
||||
// hide/show password and update icon
|
||||
$(this.dom_).find("input").attr("type", show_password ? "text" : "password");
|
||||
$(this.dom_).find("#toggle-show-password").attr("src", show_password ? window.icons["eye-closed.svg"] : window.icons["eye-open.svg"])
|
||||
|
||||
input.attr("type", show_password ? "text" : "password");
|
||||
|
||||
const icon = show_password
|
||||
? window.icons["eye-closed.svg"]
|
||||
: window.icons["eye-open.svg"];
|
||||
$(this.dom_).find("#toggle-show-password img").attr("src", icon);
|
||||
});
|
||||
}
|
||||
|
||||
updateStrength(value, bar) {
|
||||
let strength = 0;
|
||||
if (value.length > 5) strength++;
|
||||
if (/[A-Z]/.test(value)) strength++;
|
||||
if (/[0-9]/.test(value)) strength++;
|
||||
if (/[^A-Za-z0-9]/.test(value)) strength++;
|
||||
|
||||
let width = "0%";
|
||||
let cls = "";
|
||||
|
||||
if (strength === 1) { width = "33%"; cls = "strength-weak"; }
|
||||
if (strength === 2) { width = "66%"; cls = "strength-medium"; }
|
||||
if (strength >= 3) { width = "100%"; cls = "strength-strong"; }
|
||||
|
||||
bar.removeClass("strength-weak strength-medium strength-strong")
|
||||
.addClass(cls)
|
||||
.css("width", width);
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user