Edit metadata from the feature detail page (#2619)

* metadata field can be edited from feature-detail

* remove unused variables
This commit is contained in:
Daniel Smith 2023-01-03 16:00:01 -08:00 коммит произвёл GitHub
Родитель 21cf744501
Коммит 742a640a31
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
6 изменённых файлов: 231 добавлений и 3 удалений

Просмотреть файл

@ -31,10 +31,10 @@ setBasePath('/static/shoelace');
// Configure shoelace to also find material design 24pt outline icons
// like: <sl-icon-button library="material" name="unfold-more">
// See developer-documentation.md for instructions adding icons.
import { registerIconLibrary } from '@shoelace-style/shoelace/dist/utilities/icon-library.js';
import {registerIconLibrary} from '@shoelace-style/shoelace/dist/utilities/icon-library.js';
registerIconLibrary('material', {
resolver: name => `/static/shoelace/assets/material-icons/${name}.svg`,
mutator: svg => svg.setAttribute('fill', 'currentColor')
resolver: name => `/static/shoelace/assets/material-icons/${name}.svg`,
mutator: svg => svg.setAttribute('fill', 'currentColor'),
});
@ -65,6 +65,7 @@ import './elements/chromedash-guide-editall-page';
import './elements/chromedash-guide-metadata';
import './elements/chromedash-guide-new-page';
import './elements/chromedash-guide-stage-page';
import './elements/chromedash-guide-metadata-page';
import './elements/chromedash-guide-verify-accuracy-page';
import './elements/chromedash-header';
import './elements/chromedash-legend';

Просмотреть файл

@ -215,6 +215,13 @@ class ChromedashApp extends LitElement {
this.pageComponent.appTitle = this.appTitle;
this.hideSidebar();
});
page('/guide/stage/:featureId(\\d+)/metadata', (ctx) => {
this.pageComponent = document.createElement('chromedash-guide-metadata-page');
this.pageComponent.featureId = parseInt(ctx.params.featureId);
this.pageComponent.nextPage = this.currentPage;
this.pageComponent.appTitle = this.appTitle;
this.hideSidebar();
});
page('/settings', (ctx) => {
this.pageComponent = document.createElement('chromedash-settings-page');
this.currentPage = ctx.path;

Просмотреть файл

@ -308,7 +308,16 @@ class ChromedashFeatureDetail extends LitElement {
if (fields === undefined || fields.length === 0) {
return nothing;
}
const editButton = html`
<sl-button size="small" style="float:right"
href="/guide/stage/${this.feature.id}/metadata"
>Edit fields</sl-button>
`;
const content = html`
<p class="description">
${this.canEdit ? editButton : nothing}
</p>
<section class="card">
${this.renderSectionFields(fields, {})}
</section>

Просмотреть файл

@ -0,0 +1,208 @@
import {LitElement, css, html} from 'lit';
import {ref} from 'lit/directives/ref.js';
import {showToastMessage} from './utils.js';
import './chromedash-form-table';
import './chromedash-form-field';
import {FLAT_METADATA_FIELDS, formatFeatureForEdit} from './form-definition';
import {ALL_FIELDS} from './form-field-specs';
import {SHARED_STYLES} from '../sass/shared-css.js';
import {FORM_STYLES} from '../sass/forms-css.js';
export class ChromedashGuideMetadataPage extends LitElement {
static get styles() {
return [
...SHARED_STYLES,
...FORM_STYLES,
css`
`];
}
static get properties() {
return {
featureId: {type: Number},
feature: {type: Object},
loading: {type: Boolean},
appTitle: {type: String},
nextPage: {type: String},
};
}
constructor() {
super();
this.featureId = 0;
this.feature = {};
this.loading = true;
this.appTitle = '';
this.nextPage = '';
}
connectedCallback() {
super.connectedCallback();
this.fetchData();
}
fetchData() {
this.loading = true;
Promise.resolve(window.csClient.getFeature(this.featureId))
.then((feature) => {
this.feature = feature;
if (this.feature.name) {
document.title = `${this.feature.name} - ${this.appTitle}`;
}
this.loading = false;
}).catch(() => {
showToastMessage('Some errors occurred. Please refresh the page or try again later.');
});
}
disconnectedCallback() {
super.disconnectedCallback();
document.title = this.appTitle;
}
async registerHandlers(el) {
if (!el) return;
/* Add the form's event listener after Shoelace event listeners are attached
* see more at https://github.com/GoogleChrome/chromium-dashboard/issues/2014 */
await el.updateComplete;
const hiddenTokenField = this.shadowRoot.querySelector('input[name=token]');
hiddenTokenField.form.addEventListener('submit', (event) => {
this.handleFormSubmit(event, hiddenTokenField);
});
this.addMiscEventListeners();
this.scrollToPosition();
}
handleFormSubmit(event, hiddenTokenField) {
event.preventDefault();
// get the XSRF token and update it if it's expired before submission
window.csClient.ensureTokenIsValid().then(() => {
hiddenTokenField.value = window.csClient.token;
event.target.submit();
});
}
addMiscEventListeners() {
const fields = this.shadowRoot.querySelectorAll('input, textarea');
for (let i = 0; i < fields.length; ++i) {
fields[i].addEventListener('input', (e) => {
e.target.classList.add('interacted');
});
}
}
scrollToPosition() {
if (location.hash) {
const hash = decodeURIComponent(location.hash);
if (hash) {
const el = this.shadowRoot.querySelector(hash);
el.scrollIntoView(true, {behavior: 'smooth'});
}
}
}
handleCancelClick() {
window.location.href = `/guide/edit/${this.featureId}`;
}
// get a comma-spearated list of field names
getFormFields() {
const fields = FLAT_METADATA_FIELDS.sections.reduce(
(combined, section) => [...combined, ...section.fields], []);
return fields.join();
}
renderSkeletons() {
return html`
<h3><sl-skeleton effect="sheen"></sl-skeleton></h3>
<section id="metadata">
<h3><sl-skeleton effect="sheen"></sl-skeleton></h3>
<p>
<sl-skeleton effect="sheen"></sl-skeleton>
<sl-skeleton effect="sheen"></sl-skeleton>
<sl-skeleton effect="sheen"></sl-skeleton>
<sl-skeleton effect="sheen"></sl-skeleton>
<sl-skeleton effect="sheen"></sl-skeleton>
<sl-skeleton effect="sheen"></sl-skeleton>
<sl-skeleton effect="sheen"></sl-skeleton>
<sl-skeleton effect="sheen"></sl-skeleton>
</p>
</section>
`;
}
getNextPage() {
return this.nextPage || `/guide/edit/${this.featureId}`;
}
renderSubheader() {
return html`
<div id="subheader">
<h2 id="breadcrumbs">
<a href=${this.getNextPage()}>
<iron-icon icon="chromestatus:arrow-back"></iron-icon>
Edit feature: ${this.feature.name}
</a>
</h2>
</div>
`;
}
renderFields(formattedFeature, section) {
return section.fields.map(field => {
const featureJSONKey = ALL_FIELDS[field].name || field;
return html`
<chromedash-form-field
name=${field}
value=${formattedFeature[featureJSONKey]}>
</chromedash-form-field>
`;
});
}
renderSections(formattedFeature, sections) {
return sections.map(section => {
return html`
<h3>${section.name}</h3>
<section class="stage_form">
${this.renderFields(formattedFeature, section)}
</section>
`;
});
}
renderForm() {
const formattedFeature = formatFeatureForEdit(this.feature);
return html`
<form name="feature_form" method="POST"
action="/guide/edit/${this.featureId}">
<input type="hidden" name="token">
<input type="hidden" name="form_fields" value=${this.getFormFields()} >
<input type="hidden" name="nextPage" value=${this.getNextPage()} >
<chromedash-form-table ${ref(this.registerHandlers)}>
${this.renderSections(formattedFeature, FLAT_METADATA_FIELDS.sections)}
</chromedash-form-table>
<div class="final_buttons">
<input class="button" type="submit" value="Submit">
<button id="cancel-button" type="reset"
@click=${this.handleCancelClick}>Cancel</button>
</div>
</form>
`;
}
render() {
return html`
${this.renderSubheader()}
${this.loading ? this.renderSkeletons() : this.renderForm()}
`;
}
}
customElements.define('chromedash-guide-metadata-page', ChromedashGuideMetadataPage);

Просмотреть файл

@ -1214,6 +1214,7 @@ function makeDisplaySpecs(fields) {
export const DISPLAY_FIELDS_IN_STAGES = {
'Metadata': makeDisplaySpecs([
'category', 'feature_type', 'accurate_as_of', 'breaking_change',
'search_tags', 'bug_url', 'launch_bug_url',
]),
[INTENT_STAGES.INTENT_INCUBATE[0]]: makeDisplaySpecs([
'initial_public_proposal_url', 'explainer_links',

Просмотреть файл

@ -164,6 +164,8 @@ spa_page_routes = [
defaults={'require_edit_feature': True}),
Route('/guide/verify_accuracy/<int:feature_id>',
defaults={'require_edit_feature': True}),
Route('/guide/stage/<int:feature_id>/metadata',
defaults={'require_edit_feature': True}),
Route('/metrics'),
Route('/metrics/css'),
Route('/metrics/css/popularity'),