364 строки
11 KiB
TypeScript
364 строки
11 KiB
TypeScript
import '@polymer/iron-icon';
|
|
import {LitElement, css, html} from 'lit';
|
|
import {customElement, property, state} from 'lit/decorators.js';
|
|
import {SHARED_STYLES} from '../css/shared-css.js';
|
|
import {Channels, Feature} from '../js-src/cs-client.js';
|
|
import {TemplateContent} from './chromedash-roadmap-milestone-card.js';
|
|
import {showToastMessage} from './utils.js';
|
|
|
|
interface MilestoneDetails {
|
|
branch_point: string;
|
|
earliest_beta: string;
|
|
earliest_beta_chromeos: string;
|
|
earliest_beta_ios: string;
|
|
feature_freeze: string;
|
|
final_beta: string;
|
|
final_beta_cut: string;
|
|
late_stable_date: string;
|
|
latest_beta: string;
|
|
ldaps: Record<string, string>;
|
|
ltc_date: string;
|
|
ltr_date: string;
|
|
ltr_last_refresh_date: string;
|
|
mstone: number;
|
|
owners: Record<string, string>;
|
|
stable_cut: string;
|
|
stable_cut_ios: string;
|
|
stable_date: string;
|
|
stable_refresh_first: string;
|
|
stable_refresh_second: string;
|
|
stable_refresh_third: string;
|
|
version: number;
|
|
features: Feature[];
|
|
}
|
|
|
|
interface MilestoneInfo {
|
|
[milestone: number]: MilestoneDetails;
|
|
}
|
|
|
|
const TEMPLATE_CONTENT: Record<string, TemplateContent> = {
|
|
stable_minus_one: {
|
|
channelLabel: 'Released',
|
|
h1Class: '',
|
|
dateText: 'was',
|
|
featureHeader: 'Features in this release',
|
|
},
|
|
stable: {
|
|
channelLabel: 'Stable',
|
|
h1Class: '',
|
|
dateText: 'was',
|
|
featureHeader: 'Features in this release',
|
|
},
|
|
stable_soon: {
|
|
channelLabel: 'Stable soon',
|
|
h1Class: '',
|
|
dateText: 'was',
|
|
featureHeader: 'Features planned in this release',
|
|
},
|
|
beta: {
|
|
channelLabel: 'Next up',
|
|
h1Class: 'chrome_version--beta',
|
|
channelTag: 'BETA',
|
|
dateText: 'between',
|
|
featureHeader: 'Features planned in this release',
|
|
},
|
|
dev: {
|
|
channelLabel: 'Dev',
|
|
h1Class: 'chrome_version--dev',
|
|
channelTag: 'DEV',
|
|
dateText: 'coming',
|
|
featureHeader: 'Features planned in this release',
|
|
},
|
|
dev_plus_one: {
|
|
channelLabel: 'Later',
|
|
h1Class: 'chrome_version--dev_plus_one',
|
|
dateText: 'coming',
|
|
featureHeader: 'Features planned in this release',
|
|
},
|
|
};
|
|
const DEFAULT_CHANNEL_TYPES = ['stable', 'beta', 'dev'];
|
|
const GAPPED_CHANNEL_TYPES = ['stable', 'stable_soon', 'beta', 'dev'];
|
|
const SHOW_DATES = true;
|
|
const compareFeatures = (a, b) =>
|
|
a.name.localeCompare(b.name, 'fr', {ignorePunctuation: true}); // comparator for sorting milestone features
|
|
|
|
@customElement('chromedash-roadmap')
|
|
export class ChromedashRoadmap extends LitElement {
|
|
static get styles() {
|
|
return [
|
|
...SHARED_STYLES,
|
|
css`
|
|
:host {
|
|
display: inline-flex;
|
|
padding: 0 0em var(--content-padding-huge);
|
|
margin-right: var(--content-padding-negative);
|
|
position: relative;
|
|
}
|
|
`,
|
|
];
|
|
}
|
|
@property({type: Boolean})
|
|
signedIn;
|
|
@property({type: Number, attribute: false})
|
|
numColumns = 0;
|
|
@property({type: Number, attribute: false})
|
|
cardWidth = 0;
|
|
@state()
|
|
channels: Channels = {};
|
|
@state()
|
|
starredFeatures = new Set<number>();
|
|
/**
|
|
* The timestamp of the last fetched future milestone.
|
|
*/
|
|
@state()
|
|
lastFutureFetchedOn!: number;
|
|
/**
|
|
* The timestamp of the last fetched past milestone.
|
|
*/
|
|
@state()
|
|
lastPastFetchedOn!: number;
|
|
/**
|
|
* The milestone number currently visible on the screen to the user.
|
|
*/
|
|
@state()
|
|
lastMilestoneVisible!: number;
|
|
/**
|
|
* Array to store the milestone numbers fetched after the dev channel.
|
|
*/
|
|
@state()
|
|
futureMilestoneArray: number[] = [];
|
|
/**
|
|
* Array to store the milestone numbers fetched before the stable channel.
|
|
*/
|
|
@state()
|
|
pastMilestoneArray: number[] = [];
|
|
/**
|
|
* Object to store milestone details (features, version, etc.) fetched after the dev channel.
|
|
*/
|
|
@state()
|
|
milestoneInfo!: MilestoneInfo;
|
|
/**
|
|
* The feature to highlight.
|
|
*/
|
|
@state()
|
|
highlightFeature: number | undefined = undefined;
|
|
/**
|
|
* The left margin value.
|
|
*/
|
|
@state()
|
|
cardOffset = 0;
|
|
@state()
|
|
shownChannelNames: string[] = [];
|
|
|
|
connectedCallback() {
|
|
super.connectedCallback();
|
|
|
|
Promise.all([window.csClient.getChannels(), window.csClient.getStars()])
|
|
.then(([channels, starredFeatures]) => {
|
|
this.fetchFirstBatch(channels);
|
|
this.starredFeatures = new Set(starredFeatures);
|
|
})
|
|
.catch(() => {
|
|
showToastMessage(
|
|
'Some errors occurred. Please refresh the page or try again later.'
|
|
);
|
|
});
|
|
}
|
|
|
|
fetchFirstBatch(channels) {
|
|
this.shownChannelNames = channels['stable_soon']
|
|
? GAPPED_CHANNEL_TYPES
|
|
: DEFAULT_CHANNEL_TYPES;
|
|
const promises = this.shownChannelNames.map(channelType =>
|
|
window.csClient.getFeaturesInMilestone(channels[channelType].version)
|
|
);
|
|
Promise.all(promises).then(allRes => {
|
|
allRes.map((res, idx) => {
|
|
Object.keys(res).forEach(status => {
|
|
res[status].sort(compareFeatures);
|
|
});
|
|
channels[this.shownChannelNames[idx]].features = res;
|
|
});
|
|
this.channels = channels;
|
|
|
|
this.fetchNextBatch(channels['beta'].version, true);
|
|
this.fetchPreviousBatch(channels['stable'].version);
|
|
this.lastMilestoneVisible =
|
|
channels[this.shownChannelNames[this.numColumns - 1]].version;
|
|
});
|
|
}
|
|
|
|
fetchNextBatch(nextVersion, firstTime = false) {
|
|
const fetchInAdvance = 3; // number of milestones to fetch while fetching for the first time
|
|
const fetchStart = firstTime
|
|
? nextVersion + 2
|
|
: nextVersion + fetchInAdvance + 1;
|
|
const fetchEnd = nextVersion + fetchInAdvance + 1;
|
|
const versions = [...Array(fetchEnd - fetchStart + 1).keys()].map(
|
|
x => x + fetchStart
|
|
);
|
|
|
|
// Promises to get the info and features of specified milestone versions
|
|
const milestonePromise = window.csClient.getSpecifiedChannels(
|
|
fetchStart,
|
|
fetchEnd
|
|
);
|
|
const featurePromises = versions.map(ver =>
|
|
window.csClient.getFeaturesInMilestone(ver)
|
|
);
|
|
|
|
this.futureMilestoneArray = [...this.futureMilestoneArray, ...versions];
|
|
this.lastFutureFetchedOn = nextVersion;
|
|
|
|
// Fetch milestones object first
|
|
milestonePromise
|
|
.then(newMilestonesInfo => {
|
|
// Then fetch features for each milestone
|
|
Promise.all(featurePromises).then(allRes => {
|
|
allRes.map((res, idx) => {
|
|
Object.keys(res).forEach(status => {
|
|
res[status].sort(compareFeatures);
|
|
});
|
|
// attach each milestone's feature response to the milestone object
|
|
const version = versions[idx];
|
|
newMilestonesInfo[version].features = res;
|
|
newMilestonesInfo[version].version = version;
|
|
|
|
// update the properties to render the latest milestone cards
|
|
this.milestoneInfo = Object.assign(
|
|
{},
|
|
this.milestoneInfo,
|
|
newMilestonesInfo
|
|
);
|
|
});
|
|
});
|
|
})
|
|
.catch(() => {
|
|
showToastMessage(
|
|
'Some errors occurred. Please refresh the page or try again later.'
|
|
);
|
|
});
|
|
}
|
|
|
|
fetchPreviousBatch(version) {
|
|
const versionToFetch = version - 1;
|
|
// Chrome 1 is the first release. Hence, do not fetch if already fetched Chrome 1.
|
|
if (versionToFetch < 2) return;
|
|
|
|
// Promises to get the info and features of specified milestone versions
|
|
const milestonePromise = window.csClient.getSpecifiedChannels(
|
|
versionToFetch,
|
|
versionToFetch
|
|
);
|
|
const featurePromise =
|
|
window.csClient.getFeaturesInMilestone(versionToFetch);
|
|
|
|
// add the newly fetched milestone to the starting of the list and shift the element horizontally
|
|
const margin = 16;
|
|
this.cardOffset -= 1;
|
|
this.style.left = this.cardOffset * (this.cardWidth + margin) + 'px';
|
|
|
|
this.pastMilestoneArray = [versionToFetch, ...this.pastMilestoneArray];
|
|
this.lastPastFetchedOn = version;
|
|
|
|
Promise.all([milestonePromise, featurePromise])
|
|
.then(([newMilestonesInfo, features]) => {
|
|
// sort the feature lists
|
|
Object.keys(features).forEach(status => {
|
|
features[status].sort(compareFeatures);
|
|
});
|
|
// attach the milestone's feature response to the milestone object
|
|
newMilestonesInfo[versionToFetch].features = features;
|
|
newMilestonesInfo[versionToFetch].version = versionToFetch;
|
|
|
|
// update the properties to render the newly fetched milestones
|
|
this.milestoneInfo = Object.assign(
|
|
{},
|
|
this.milestoneInfo,
|
|
newMilestonesInfo
|
|
);
|
|
})
|
|
.catch(() => {
|
|
showToastMessage(
|
|
'Some errors occurred. Please refresh the page or try again later.'
|
|
);
|
|
});
|
|
}
|
|
|
|
// Handles the Star-Toggle event fired by any one of the child components
|
|
handleStarToggle(e) {
|
|
const newStarredFeatures = new Set(this.starredFeatures);
|
|
window.csClient
|
|
.setStar(e.detail.feature, e.detail.doStar)
|
|
.then(() => {
|
|
if (e.detail.doStar) {
|
|
newStarredFeatures.add(e.detail.feature);
|
|
} else {
|
|
newStarredFeatures.delete(e.detail.feature);
|
|
}
|
|
this.starredFeatures = newStarredFeatures;
|
|
})
|
|
.catch(() => {
|
|
showToastMessage('Unable to star the Feature. Please Try Again.');
|
|
});
|
|
}
|
|
|
|
handleHighlightEvent(e) {
|
|
this.highlightFeature = e.detail.feature;
|
|
}
|
|
|
|
render() {
|
|
return html`
|
|
${this.pastMilestoneArray.map(
|
|
milestone => html`
|
|
<chromedash-roadmap-milestone-card
|
|
style="width:${this.cardWidth}px;"
|
|
.channel=${this.milestoneInfo?.[milestone]}
|
|
.templateContent=${TEMPLATE_CONTENT['stable_minus_one']}
|
|
?showdates=${SHOW_DATES}
|
|
.starredFeatures=${this.starredFeatures}
|
|
.highlightFeature=${this.highlightFeature}
|
|
?signedin=${this.signedIn}
|
|
@star-toggle-event=${this.handleStarToggle}
|
|
@highlight-feature-event=${this.handleHighlightEvent}
|
|
>
|
|
</chromedash-roadmap-milestone-card>
|
|
`
|
|
)}
|
|
${this.shownChannelNames.map(
|
|
type => html`
|
|
<chromedash-roadmap-milestone-card
|
|
style="width:${this.cardWidth}px;"
|
|
.channel=${this.channels?.[type]}
|
|
.templateContent=${TEMPLATE_CONTENT[type]}
|
|
?showdates=${SHOW_DATES}
|
|
.starredFeatures=${this.starredFeatures}
|
|
.highlightFeature=${this.highlightFeature}
|
|
?signedin=${this.signedIn}
|
|
.stableMilestone=${this.channels?.['stable']?.version}
|
|
@star-toggle-event=${this.handleStarToggle}
|
|
@highlight-feature-event=${this.handleHighlightEvent}
|
|
>
|
|
</chromedash-roadmap-milestone-card>
|
|
`
|
|
)}
|
|
${this.futureMilestoneArray.map(
|
|
milestone => html`
|
|
<chromedash-roadmap-milestone-card
|
|
style="width:${this.cardWidth}px;"
|
|
.channel=${this.milestoneInfo?.[milestone]}
|
|
.templateContent=${TEMPLATE_CONTENT['dev_plus_one']}
|
|
?showdates=${SHOW_DATES}
|
|
.starredFeatures=${this.starredFeatures}
|
|
.highlightFeature=${this.highlightFeature}
|
|
?signedin=${this.signedIn}
|
|
@star-toggle-event=${this.handleStarToggle}
|
|
@highlight-feature-event=${this.handleHighlightEvent}
|
|
>
|
|
</chromedash-roadmap-milestone-card>
|
|
`
|
|
)}
|
|
`;
|
|
}
|
|
}
|