Commit d517ec31 authored by Monica Basta's avatar Monica Basta Committed by Chromium LUCI CQ

[ProfilePicker]: Make local profile name editable in place.

This CL enables editing the local profile name from the picker's main
view. A hover state is added to guide the user of the possibility of the
inling editing. On click, the local profile name is editable.

Bug: 1159740
Change-Id: I2fba0602944b9e7334ee32663c081b5c308428b3
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2599869Reviewed-by: default avatarEsmael Elmoslimany <aee@chromium.org>
Commit-Queue: Monica Basta <msalama@chromium.org>
Cr-Commit-Position: refs/heads/master@{#839597}
parent 2a2bb87e
......@@ -122,6 +122,13 @@ export class ManageProfilesBrowserProxy {
*/
createProfile(
profileName, profileColor, avatarUrl, isGeneric, createShortcut) {}
/**
* Sets the local profile name.
* @param {string} profilePath
* @param {string} profileName
*/
setProfileName(profilePath, profileName) {}
}
/** @implements {ManageProfilesBrowserProxy} */
......@@ -183,6 +190,11 @@ export class ManageProfilesBrowserProxyImpl {
'createProfile',
[profileName, profileColor, avatarUrl, isGeneric, createShortcut]);
}
/** @override */
setProfileName(profilePath, profileName) {
chrome.send('setProfileName', [profilePath, profileName]);
}
}
addSingletonGetter(ManageProfilesBrowserProxyImpl);
<style include="profile-picker-shared cr-hidden-style">
#profileCardContainer {
border-radius: inherit;
height: 100%;
/* Allows descendants to be absolute positioned relatively to the
container */
position: relative
position: relative;
width: 100%;
}
cr-button {
......@@ -45,8 +47,8 @@
border-radius: 50%;
box-shadow: 0 0 2px rgba(60, 64, 67, 0.12), 0 0 6px rgba(60, 64, 67, 0.15);
display: flex;
justify-content: center;
height: var(--domain-icon-size);
justify-content: center;
position: absolute;
right: -6px;
top: calc(var(--avatar-icon-size) - var(--domain-icon-size)
......@@ -75,6 +77,41 @@
width: 16px;
}
div.profile-card-info {
bottom: 0;
font-weight: normal;
}
cr-input {
--cr-input-background-color: none;
--cr-input-padding-top: 0;
font-weight: 500;
top: 0;
}
#hoverUnderline {
border-bottom: 2px solid var(--google-grey-refresh-300);
border-radius: 0;
height: 0;
left: 0;
margin: auto;
opacity: 0;
position: absolute;
right: 0;
top: 38px;
width: 0;
}
cr-input[focused_] + #hoverUnderline {
visibility: hidden;
}
#profileNameInputWrapper:hover #hoverUnderline {
opacity: 1;
transition: opacity 120ms ease-in, width 180ms ease-out;
width: 130px;
}
@media (prefers-color-scheme: dark) {
cr-button {
--card-background-color: var(--google-grey-800);
......@@ -89,14 +126,16 @@
iron-icon {
--iron-icon-fill-color: var(--google-grey-500);
}
#hoverUnderline {
border-color: var(--google-grey-refresh-700);
}
}
</style>
<div id="profileCardContainer">
<cr-button on-click="onProfileClick_"
aria-label="[[profileState.localProfileName]]">
<!-- TODO(msalama): Implement editing local profile name in place -->
<div class="profile-card-info">[[profileState.localProfileName]]</div>
<div id="avatarContainer">
<img class="profile-avatar" alt="" src="[[profileState.avatarIcon]]">
<div id="iconContainer" hidden="[[!profileState.isManaged]]">
......@@ -104,12 +143,23 @@
</div>
</div>
<div class="profile-card-info">
<div hidden="[[profileState.needsSignin]]">[[profileState.gaiaName]]</div>
<div id="gaiaName" hidden="[[profileState.needsSignin]]">
[[profileState.gaiaName]]
</div>
<div id="forceSigninContainer" hidden="[[!profileState.needsSignin]]">
<div>$i18n{needsSigninPrompt}</div>
<iron-icon id="forceSigninIcon" icon="profiles:lock"></iron-icon>
</div>
</div>
</cr-button>
<div id="profileNameInputWrapper">
<cr-input class="profile-card-info" id="nameInput"
value="[[profileState.localProfileName]]"
on-change="onProfileNameChanged_" on-keydown="onProfileNameKeydown_"
on-blur="onProfileNameInputBlur_" pattern="[[pattern_]]"
auto-validate spellcheck="false" required>
</cr-input>
<div id="hoverUnderline"></div>
</div>
<profile-card-menu profile-state="[[profileState]]"></profile-card-menu>
</div>
......@@ -6,6 +6,7 @@ import 'chrome://resources/cr_elements/cr_button/cr_button.m.js';
import 'chrome://resources/cr_elements/icons.m.js';
import './profile_card_menu.js';
import './profile_picker_shared_css.js';
import 'chrome://resources/cr_elements/cr_input/cr_input.m.js';
import {html, Polymer} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
import {ManageProfilesBrowserProxy, ManageProfilesBrowserProxyImpl, ProfileState} from './manage_profiles_browser_proxy.js';
......@@ -20,6 +21,11 @@ Polymer({
profileState: {
type: Object,
},
pattern_: {
type: String,
value: '.*\\S.*',
},
},
/** @private {ManageProfilesBrowserProxy} */
......@@ -36,4 +42,41 @@ Polymer({
this.manageProfilesBrowserProxy_.launchSelectedProfile(
this.profileState.profilePath);
},
/**
* Handler for when the profile name field is changed, then blurred.
* @param {!Event} event
* @private
*/
onProfileNameChanged_(event) {
if (event.target.invalid) {
return;
}
this.manageProfilesBrowserProxy_.setProfileName(
this.profileState.profilePath, event.target.value);
event.target.blur();
},
/**
* Handler for profile name keydowns.
* @param {!Event} event
* @private
*/
onProfileNameKeydown_(event) {
if (event.key === 'Escape' || event.key === 'Enter') {
event.target.blur();
}
},
/**
* Handler for profile name blur.
* @private
*/
onProfileNameInputBlur_() {
if (this.$.nameInput.invalid) {
this.$.nameInput.value = this.profileState.localProfileName;
}
},
});
......@@ -85,6 +85,12 @@
#addProfile {
border: 1px dashed;
border-color: var(--google-grey-400);
position: relative;
}
#addProfileButtonLabel {
font-weight: 500;
top: 0;
}
cr-icon-button[iron-icon='profiles:add'] {
......@@ -125,6 +131,7 @@
#addProfile {
border-color: var(--google-grey-refresh-700);
position: relative;
}
cr-icon-button[iron-icon='profiles:add'] {
......@@ -158,8 +165,6 @@
<cr-icon-button iron-icon="profiles:add"
on-click="onAddProfileClick_" aria-labelledby="addProfileButtonLabel">
</cr-icon-button>
<!-- Empty div to maintain alignment with other profile cards. -->
<div class="profile-card-info"></div>
</div>
</div>
</div>
......
......@@ -30,12 +30,13 @@
}
.profile-card-info {
--profile-card-info-height: 52px;
color: var(--cr-primary-text-color);
font-size: var(--text-font-size);
font-weight: 500;
height: 20px;
overflow: hidden;
padding: 16px;
position: absolute;
text-align: center;
text-overflow: ellipsis;
white-space: nowrap;
......
......@@ -209,6 +209,10 @@ void ProfilePickerHandler::RegisterMessages() {
"createProfile",
base::BindRepeating(&ProfilePickerHandler::HandleCreateProfile,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"setProfileName",
base::BindRepeating(&ProfilePickerHandler::HandleSetProfileName,
base::Unretained(this)));
}
void ProfilePickerHandler::OnJavascriptAllowed() {
......@@ -447,6 +451,27 @@ void ProfilePickerHandler::OnProfileCreationSuccess(
profile, Profile::CREATE_STATUS_INITIALIZED);
}
void ProfilePickerHandler::HandleSetProfileName(const base::ListValue* args) {
CHECK_EQ(2U, args->GetSize());
const base::Value& profile_path_value = args->GetList()[0];
base::Optional<base::FilePath> profile_path =
util::ValueToFilePath(profile_path_value);
if (!profile_path) {
NOTREACHED();
return;
}
base::string16 profile_name =
base::UTF8ToUTF16(args->GetList()[1].GetString());
base::TrimWhitespace(profile_name, base::TRIM_ALL, &profile_name);
CHECK(!profile_name.empty());
ProfileAttributesEntry* entry;
CHECK(g_browser_process->profile_manager()
->GetProfileAttributesStorage()
.GetProfileAttributesWithPath(profile_path.value(), &entry));
entry->SetLocalProfileName(profile_name);
}
void ProfilePickerHandler::HandleRemoveProfile(const base::ListValue* args) {
CHECK_EQ(1U, args->GetSize());
const base::Value& profile_path_value = args->GetList()[0];
......
......@@ -39,6 +39,7 @@ class ProfilePickerHandler : public content::WebUIMessageHandler,
void HandleAskOnStartupChanged(const base::ListValue* args);
void HandleRemoveProfile(const base::ListValue* args);
void HandleGetProfileStatistics(const base::ListValue* args);
void HandleSetProfileName(const base::ListValue* args);
// TODO(crbug.com/1115056): Move to new handler for profile creation.
void HandleLoadSignInProfileCreationFlow(const base::ListValue* args);
......
......@@ -103,18 +103,17 @@ suite('ProfilePickerMainViewTest', function() {
assertTrue(!!profiles[i].$$('profile-card-menu'));
profiles[i].$$('cr-button').click();
await browserProxy.whenCalled('launchSelectedProfile');
const profileCardInfo =
profiles[i].shadowRoot.querySelectorAll('.profile-card-info');
assertEquals(profileCardInfo.length, 2);
assertEquals(
profileCardInfo[0].innerText, expectedProfiles[i].localProfileName);
assertEquals(
profiles[i].$$('#forceSigninContainer').hidden,
!expectedProfiles[i].needsSignin);
if (!expectedProfiles[i].needsSignin) {
assertEquals(
profileCardInfo[1].innerText, expectedProfiles[i].gaiaName);
}
const gaiaName = profiles[i].$$('#gaiaName');
assertEquals(gaiaName.hidden, expectedProfiles[i].needsSignin);
assertEquals(gaiaName.innerText.trim(), expectedProfiles[i].gaiaName);
assertEquals(
profiles[i].$$('#nameInput').value,
expectedProfiles[i].localProfileName);
assertEquals(
profiles[i].$$('#iconContainer').hidden,
!expectedProfiles[i].isManaged);
......@@ -163,6 +162,29 @@ suite('ProfilePickerMainViewTest', function() {
profiles, mainViewElement.shadowRoot.querySelectorAll('profile-card'));
});
test('EditLocalProfileName', async function() {
await browserProxy.whenCalled('initializeMainView');
const profiles = generateProfilesList(1);
webUIListenerCallback('profiles-list-changed', [...profiles]);
flushTasks();
const localProfileName =
mainViewElement.shadowRoot.querySelector('profile-card')
.$$('#nameInput');
assertEquals(localProfileName.value, profiles[0].localProfileName);
// Set to valid profile name.
localProfileName.value = 'Alice';
localProfileName.fire('change');
const args = await browserProxy.whenCalled('setProfileName');
assertEquals(args[0], profiles[0].profilePath);
assertEquals(args[1], 'Alice');
assertEquals(localProfileName.value, 'Alice');
// Set to invalid profile name
localProfileName.value = '';
assertTrue(localProfileName.invalid);
});
test('GuestModeDisabled', async function() {
loadTimeData.overrideValues({
isGuestModeEnabled: false,
......
......@@ -21,6 +21,7 @@ export class TestManageProfilesBrowserProxy extends TestBrowserProxy {
'getProfileStatistics',
'loadSignInProfileCreationFlow',
'createProfile',
'setProfileName',
]);
/** @type {!AutogeneratedThemeColorInfo} */
......@@ -110,4 +111,9 @@ export class TestManageProfilesBrowserProxy extends TestBrowserProxy {
'createProfile',
[profileName, profileColor, avatarUrl, isGeneric, createShortcut]);
}
/** @override */
setProfileName(profilePath, profileName) {
this.methodCalled('setProfileName', [profilePath, profileName]);
}
}
......@@ -55,6 +55,7 @@
color: var(--cr-input-color);
font-family: inherit;
font-size: inherit;
font-weight: inherit;
line-height: inherit;
min-height: var(--cr-input-min-height, auto);
outline: none;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment