diff --git a/common/radar.js b/common/radar.js index 3e24f6a..62a2046 100644 --- a/common/radar.js +++ b/common/radar.js @@ -14,7 +14,7 @@ export const createRadar = async tree => { const revisions = await createRevisionsFromFiles(fileNames); const allReleases = getAllReleases(revisions); const items = createItems(revisions); - const flaggedItems = flagWithIsNew(items, allReleases); + const flaggedItems = flagItem(items, allReleases); return { items: flaggedItems, @@ -26,7 +26,9 @@ const checkAttributes = (fileName, attributes) => { const rings = ['adopt', 'trial', 'assess', 'hold']; if (attributes.ring && !rings.includes(attributes.ring)) { throw new Error( - `Error: ${fileName} has an illegal value for 'ring' - must be one of ${rings}`, + `Error: ${fileName} has an illegal value for 'ring' - must be one of ${ + rings + }`, ); } @@ -38,7 +40,9 @@ const checkAttributes = (fileName, attributes) => { ]; if (attributes.quadrant && !quadrants.includes(attributes.quadrant)) { throw new Error( - `Error: ${fileName} has an illegal value for 'quadrant' - must be one of ${quadrants}`, + `Error: ${ + fileName + } has an illegal value for 'quadrant' - must be one of ${quadrants}`, ); } }; @@ -111,7 +115,7 @@ const createItems = revisions => { const addRevisionToItem = ( item = { - isNew: false, + flag: 'default', isFeatured: true, revisions: [], }, @@ -141,15 +145,30 @@ const revisionCreatesNewHistoryEntry = revision => { return revision.body.trim() !== '' || typeof revision.ring !== 'undefined'; }; -const flagWithIsNew = (items, allReleases) => +const flagItem = (items, allReleases) => items.map( item => ({ ...item, - isNew: isNewItem(item, allReleases), + flag: getItemFlag(item, allReleases), }), [], ); -const isNewItem = (item, allReleases) => - item.revisions.length === 0 || +const isInLastRelease = (item, allReleases) => item.revisions[0].release === allReleases[allReleases.length - 1]; + +const isNewItem = (item, allReleases) => + item.revisions.length === 1 && isInLastRelease(item, allReleases); + +const hasItemChanged = (item, allReleases) => + item.revisions.length > 1 && isInLastRelease(item, allReleases); + +const getItemFlag = (item, allReleases) => { + if (isNewItem(item, allReleases)) { + return 'new'; + } + if (hasItemChanged(item, allReleases)) { + return 'changed'; + } + return 'default'; +}; diff --git a/js/components/IsNew.js b/js/components/IsNew.js deleted file mode 100644 index fadb642..0000000 --- a/js/components/IsNew.js +++ /dev/null @@ -1,8 +0,0 @@ -import React from 'react'; - -export default function IsNew({ item }) { - if (item.isNew) { - return NEW; - } - return null; -} diff --git a/js/components/Item.js b/js/components/Item.js index b52bb1b..11d9d95 100644 --- a/js/components/Item.js +++ b/js/components/Item.js @@ -1,9 +1,14 @@ import React from 'react'; import classNames from 'classnames'; import Link from './Link'; -import IsNew from './IsNew'; +import Tag from './Tag'; -export default function Item({ item, noLeadingBorder = false, active = false, style = {}}) { +export default function Item({ + item, + noLeadingBorder = false, + active = false, + style = {}, +}) { return (
{item.title} - +
- { - item.info && ( -
{item.info}
- ) - } + {item.info &&
{item.info}
} ); } diff --git a/js/components/PageItem.js b/js/components/PageItem.js index 121a9f6..8ca264b 100644 --- a/js/components/PageItem.js +++ b/js/components/PageItem.js @@ -5,7 +5,6 @@ import Link from './Link'; import FooterEnd from './FooterEnd'; import SetTitle from './SetTitle'; import ItemRevisions from './ItemRevisions'; -import IsNew from './IsNew'; import { createAnimation, createAnimationRunner } from '../animation'; import { translate } from '../../common/config'; @@ -23,55 +22,67 @@ class PageItem extends React.Component { const itemsInRing = this.getItemsInRing(props); this.animationsIn = { - background: createAnimation({ + background: createAnimation( + { transform: 'translateX(calc((100vw - 1200px) / 2 + 800px))', transition: 'transform 450ms cubic-bezier(0.24, 1.12, 0.71, 0.98)', - }, { + }, + { transition: 'transform 450ms cubic-bezier(0.24, 1.12, 0.71, 0.98)', transform: 'translateX(0)', }, - 0 + 0, ), - navHeader: createAnimation({ + navHeader: createAnimation( + { transform: 'translateX(-40px)', opacity: '0', - }, { + }, + { transition: 'opacity 150ms ease-out, transform 300ms ease-out', transform: 'translateX(0px)', opacity: '1', }, - 300 + 300, ), - text: createAnimation({ + text: createAnimation( + { transform: 'translateY(-20px)', opacity: '0', - }, { + }, + { transition: 'opacity 150ms ease-out, transform 300ms ease-out', transform: 'translateY(0px)', opacity: '1', }, - 600 + 600, ), - items: itemsInRing.map((item, i) => (createAnimation({ + items: itemsInRing.map((item, i) => + createAnimation( + { + transform: 'translateX(-40px)', + opacity: '0', + }, + { + transition: 'opacity 150ms ease-out, transform 300ms ease-out', + transform: 'translateX(0px)', + opacity: '1', + }, + 400 + 100 * i, + ), + ), + footer: createAnimation( + { + transition: 'opacity 150ms ease-out, transform 300ms ease-out', transform: 'translateX(-40px)', opacity: '0', - }, { + }, + { transition: 'opacity 150ms ease-out, transform 300ms ease-out', transform: 'translateX(0px)', opacity: '1', }, - 400 + 100 * i - ))), - footer: createAnimation({ - transition: 'opacity 150ms ease-out, transform 300ms ease-out', - transform: 'translateX(-40px)', - opacity: '0', - }, { - transition: 'opacity 150ms ease-out, transform 300ms ease-out', - transform: 'translateX(0px)', - opacity: '1', - }, - 600 + itemsInRing.length * 100 + 600 + itemsInRing.length * 100, ), }; @@ -79,7 +90,7 @@ class PageItem extends React.Component { background: createAnimation( this.animationsIn.background.stateB, this.animationsIn.background.stateA, - 300 + itemsInRing.length * 50 + 300 + itemsInRing.length * 50, ), navHeader: createAnimation( this.animationsIn.navHeader.stateB, @@ -88,7 +99,7 @@ class PageItem extends React.Component { transform: 'translateX(40px)', opacity: '0', }, - 0 + 0, ), text: createAnimation( this.animationsIn.text.stateB, @@ -97,43 +108,58 @@ class PageItem extends React.Component { transition: 'opacity 150ms ease-out, transform 300ms ease-out', opacity: '0', }, - 0 + 0, ), - items: itemsInRing.map((item, i) => (createAnimation( - this.animationsIn.items[i].stateB, - { - transition: 'opacity 150ms ease-out, transform 300ms ease-out', - transform: 'translateX(40px)', - opacity: '0', - }, - 100 + 50 * i - ))), - footer: createAnimation( - this.animationsIn.text.stateB, + items: itemsInRing.map((item, i) => + createAnimation( + this.animationsIn.items[i].stateB, { transition: 'opacity 150ms ease-out, transform 300ms ease-out', transform: 'translateX(40px)', opacity: '0', }, - 200 + itemsInRing.length * 50 + 100 + 50 * i, + ), + ), + footer: createAnimation( + this.animationsIn.text.stateB, + { + transition: 'opacity 150ms ease-out, transform 300ms ease-out', + transform: 'translateX(40px)', + opacity: '0', + }, + 200 + itemsInRing.length * 50, ), }; - if (props.leaving) { // entering from an other page - this.state = setAnimations({}, createAnimationRunner(this.animationsIn).getState()); - } else { // Hard refresh + if (props.leaving) { + // entering from an other page + this.state = setAnimations( + {}, + createAnimationRunner(this.animationsIn).getState(), + ); + } else { + // Hard refresh this.state = {}; } } componentWillReceiveProps({ leaving }) { - if (!this.props.leaving && leaving) { // page will be left - this.animationRunner = createAnimationRunner(this.animationsOut, this.handleAnimationsUpdate); + if (!this.props.leaving && leaving) { + // page will be left + this.animationRunner = createAnimationRunner( + this.animationsOut, + this.handleAnimationsUpdate, + ); this.animationRunner.run(); this.animationRunner.awaitAnimationComplete(this.props.onLeave); } - if (this.props.leaving && !leaving) { // page is entered - this.animationRunner = createAnimationRunner(this.animationsIn, this.handleAnimationsUpdate); + if (this.props.leaving && !leaving) { + // page is entered + this.animationRunner = createAnimationRunner( + this.animationsIn, + this.handleAnimationsUpdate, + ); this.animationRunner.run(); } } @@ -142,20 +168,22 @@ class PageItem extends React.Component { this.setState(setAnimations(this.state, this.animationRunner.getState())); }; - getAnimationState = (name) => { + getAnimationState = name => { if (!this.state.animations) { return undefined; } return this.state.animations[name]; }; - getItem = (props) => { + getItem = props => { const [quadrantName, itemName] = props.pageName.split('/'); - const item = props.items.filter(item => item.quadrant === quadrantName && item.name === itemName)[0]; + const item = props.items.filter( + item => item.quadrant === quadrantName && item.name === itemName, + )[0]; return item; - } + }; - getItemsInRing = (props) => { + getItemsInRing = props => { const item = this.getItem(props); const itemsInRing = groupByQuadrants(props.items)[item.quadrant][item.ring]; return itemsInRing; @@ -170,7 +198,10 @@ class PageItem extends React.Component {
-
+

{translate(item.quadrant)}

@@ -182,34 +213,55 @@ class PageItem extends React.Component { >
- {item.ring} + + {item.ring} +
- Quadrant Overview + Quadrant + Overview
-
+
-
-
+
+
-

{item.title}

+

+ {item.title} +

- {item.ring} + + {item.ring} +
-
- {item.revisions.length > 1 && } +
+ {item.revisions.length > 1 && ( + + )}
diff --git a/js/components/PageOverview.js b/js/components/PageOverview.js index 474491f..0bc3604 100644 --- a/js/components/PageOverview.js +++ b/js/components/PageOverview.js @@ -7,7 +7,7 @@ import Link from './Link'; import Search from './Search'; import Fadeable from './Fadeable'; import SetTitle from './SetTitle'; -import IsNew from './IsNew'; +import Tag from './Tag'; import { groupByFirstLetter } from '../../common/model'; import { translate } from '../../common/config'; @@ -15,11 +15,15 @@ const rings = ['all', 'assess', 'trial', 'hold', 'adopt']; const containsSearchTerm = (text = '', term = '') => { // TODO search refinement - return text.trim().toLocaleLowerCase().indexOf(term.trim().toLocaleLowerCase()) !== -1; -} + return ( + text + .trim() + .toLocaleLowerCase() + .indexOf(term.trim().toLocaleLowerCase()) !== -1 + ); +}; class PageOverview extends React.Component { - constructor(props, ...args) { super(props, ...args); this.state = { @@ -37,30 +41,32 @@ class PageOverview extends React.Component { } } - handleRingClick = (ring) => (e) => { + handleRingClick = ring => e => { e.preventDefault(); this.setState({ ring, }); - } + }; isRingActive(ringName) { return this.state.ring === ringName; } - itemMatchesRing = (item) => { + itemMatchesRing = item => { return this.state.ring === 'all' || item.ring === this.state.ring; }; - itemMatchesSearch = (item) => { - return this.state.search.trim() === '' || + itemMatchesSearch = item => { + return ( + this.state.search.trim() === '' || containsSearchTerm(item.title, this.state.search) || containsSearchTerm(item.body, this.state.search) || - containsSearchTerm(item.info, this.state.search); + containsSearchTerm(item.info, this.state.search) + ); }; - isItemVisible = (item) => { + isItemVisible = item => { return this.itemMatchesRing(item) && this.itemMatchesSearch(item); }; @@ -70,11 +76,13 @@ class PageOverview extends React.Component { ...group, items: group.items.filter(this.isItemVisible), })); - const nonEmptyGroups = groupsFiltered.filter(group => group.items.length > 0); + const nonEmptyGroups = groupsFiltered.filter( + group => group.items.length > 0, + ); return nonEmptyGroups; } - handleSearchTermChange = (value) => { + handleSearchTermChange = value => { this.setState({ search: value, }); @@ -92,69 +100,67 @@ class PageOverview extends React.Component {
- +
- { - rings.map(ringName => ( -
- - {ringName} - -
- )) - } + {rings.map(ringName => ( +
+ + {ringName} + +
+ ))}
- { - groups.map(({ letter, items }) => ( -
-
{letter}
-
-
-
- { - items.map((item) => ( - -
-
-
- {item.title} - -
+ {groups.map(({ letter, items }) => ( +
+
{letter}
+
+
+
+ {items.map(item => ( + +
+
+
+ {item.title} + +
+
+
+
+
+ {translate(item.quadrant)}
-
-
-
{translate(item.quadrant)}
-
- {item.ring} -
-
+
+ {item.ring}
- - )) - } -
+
+
+ + ))}
- )) - } - +
+ ))}
); diff --git a/js/components/QuadrantSection.js b/js/components/QuadrantSection.js index 95deca8..a753344 100644 --- a/js/components/QuadrantSection.js +++ b/js/components/QuadrantSection.js @@ -3,7 +3,7 @@ import { translate, rings } from '../../common/config'; import Badge from './Badge'; import Link from './Link'; import ItemList from './ItemList'; -import IsNew from './IsNew'; +import Tag from './Tag'; const renderList = (ringName, quadrantName, groups, big) => { const itemsInRing = groups[quadrantName][ringName]; @@ -11,7 +11,9 @@ const renderList = (ringName, quadrantName, groups, big) => { if (big === true) { return ( - {ringName} + + {ringName} + ); } @@ -21,26 +23,24 @@ const renderList = (ringName, quadrantName, groups, big) => {
{ringName}
- { - itemsInRing.map(item => ( - - - {item.title} - - - - )) - } + {itemsInRing.map(item => ( + + + {item.title} + + + + ))}
); -} - +}; const renderRing = (ringName, quadrantName, groups, big) => { - if (!groups[quadrantName] || !groups[quadrantName][ringName] || groups[quadrantName][ringName].length === 0) { + if ( + !groups[quadrantName] || + !groups[quadrantName][ringName] || + groups[quadrantName][ringName].length === 0 + ) { return null; } return ( @@ -48,7 +48,7 @@ const renderRing = (ringName, quadrantName, groups, big) => { {renderList(ringName, quadrantName, groups, big)}
); -} +}; export default function QuadrantSection({ quadrantName, groups, big = false }) { return ( @@ -58,21 +58,18 @@ export default function QuadrantSection({ quadrantName, groups, big = false }) {

{translate(quadrantName)}

- { - !big && ( -
- - Quadrant Overview - -
- ) - } + {!big && ( +
+ + Quadrant + Overview + +
+ )}
- { - rings.map((ringName) => renderRing(ringName, quadrantName, groups, big)) - } + {rings.map(ringName => renderRing(ringName, quadrantName, groups, big))}
); diff --git a/js/components/Tag.js b/js/components/Tag.js new file mode 100644 index 0000000..6a0d849 --- /dev/null +++ b/js/components/Tag.js @@ -0,0 +1,15 @@ +import React from 'react'; + +export default function Tag({ item, short = false }) { + if (item.flag !== 'default') { + let name = item.flag.toUpperCase(); + if (short === true) { + name = { + new: 'N', + changed: 'C', + }[item.flag]; + } + return {name}; + } + return null; +} diff --git a/styles/components/is-new.css b/styles/components/tag.css similarity index 59% rename from styles/components/is-new.css rename to styles/components/tag.css index 927c43f..d1b7882 100644 --- a/styles/components/is-new.css +++ b/styles/components/tag.css @@ -1,6 +1,5 @@ -.is-new { +.tag { font-size: 9px; - background: var(--color-red); display: inline-block; padding: 3px 8px; border-radius: 10px; @@ -8,4 +7,12 @@ vertical-align: top; margin-top: -2px; left: 5px; + + &--new { + background: var(--color-red); + } + + &--changed { + background: var(--color-blue); + } }