1
0
mirror of https://github.com/chinchang/web-maker.git synced 2025-07-25 07:51:12 +02:00

Migrate SavedItemPane to functional component 🥳

This commit is contained in:
Kushagra Gour
2019-07-17 14:02:44 +05:30
parent e21dbdf70f
commit 1a333036af
2 changed files with 142 additions and 154 deletions

View File

@@ -1,62 +1,63 @@
import { h, Component } from 'preact'; import { h } from 'preact';
import { useState, useEffect, useRef } from 'preact/hooks';
import { log } from '../utils'; import { log } from '../utils';
import { trackEvent } from '../analytics'; import { trackEvent } from '../analytics';
import { ItemTile } from './ItemTile'; import { ItemTile } from './ItemTile';
import { Trans, NumberFormat, t } from '@lingui/macro'; import { Trans, t } from '@lingui/macro';
import { I18n } from '@lingui/react'; import { I18n } from '@lingui/react';
export default class SavedItemPane extends Component { export default function SavedItemPane({
constructor(props) { itemsMap,
super(props); isOpen,
// this.items = []; closeHandler,
} onItemSelect,
onitemRemove,
onItemFork,
onExport,
mergeImportedItems
}) {
const [items, setItems] = useState([]);
const [filteredItems, setFilteredItems] = useState([]);
const searchInputRef = useRef();
static getDerivedStateFromProps({ items = {} }, state) { useEffect(() => {
const newItems = Object.values(items); if (!itemsMap) return;
const newItems = Object.values(itemsMap);
newItems.sort(function(a, b) { newItems.sort(function(a, b) {
return b.updatedOn - a.updatedOn; return b.updatedOn - a.updatedOn;
}); });
return { setItems(newItems);
items: newItems setFilteredItems(newItems);
}; }, [itemsMap]);
}
shouldComponentUpdate(nextProps, nextState) {
return (
nextProps.items !== this.props.items ||
nextProps.isOpen !== this.props.isOpen ||
nextState.filteredItems !== this.state.filteredItems
);
}
componentDidUpdate(prevProps) { useEffect(() => {
// Opening // Opening
if (this.props.isOpen && !prevProps.isOpen) { if (isOpen) {
window.searchInput.value = ''; searchInputRef.current.value = '';
window.searchInput.focus(); searchInputRef.current.focus();
} }
// Closing // Closing
if (!this.props.isOpen && prevProps.isOpen) { if (!isOpen) {
this.setState({ setFilteredItems([]);
filteredItems: undefined
});
} }
}, [isOpen]);
function onCloseIntent() {
closeHandler();
} }
onCloseIntent() { function itemClickHandler(item) {
this.props.closeHandler(); onItemSelect(item);
} }
itemClickHandler(item) { function itemRemoveBtnClickHandler(item, e) {
this.props.itemClickHandler(item);
}
itemRemoveBtnClickHandler(item, e) {
e.stopPropagation(); e.stopPropagation();
this.props.itemRemoveBtnClickHandler(item); onitemRemove(item);
} }
itemForkBtnClickHandler(item, e) { function itemForkBtnClickHandler(item, e) {
e.stopPropagation(); e.stopPropagation();
this.props.itemForkBtnClickHandler(item); onItemFork(item);
} }
keyDownHandler(event) { function keyDownHandler(event) {
if (!this.props.isOpen) { if (!isOpen) {
return; return;
} }
@@ -84,22 +85,21 @@ export default class SavedItemPane extends Component {
} }
if (isEnterKeyPressed && selectedItemElement) { if (isEnterKeyPressed && selectedItemElement) {
const item = this.props.items[selectedItemElement.dataset.itemId]; const item = itemsMap[selectedItemElement.dataset.itemId];
console.log('opening', item); onItemSelect(item);
this.props.itemClickHandler(item);
trackEvent('ui', 'openItemKeyboardShortcut'); trackEvent('ui', 'openItemKeyboardShortcut');
} }
// Fork shortcut inside saved creations panel with Ctrl/⌘ + F // Fork shortcut inside saved creations panel with Ctrl/⌘ + F
if (isForkKeyPressed) { if (isForkKeyPressed) {
event.preventDefault(); event.preventDefault();
const item = this.props.items[selectedItemElement.dataset.itemId]; const item = itemsMap[selectedItemElement.dataset.itemId];
this.props.itemForkBtnClickHandler(item); itemForkBtnClickHandler(item);
trackEvent('ui', 'forkKeyboardShortcut'); trackEvent('ui', 'forkKeyboardShortcut');
} }
} }
importFileChangeHandler(e) { function importFileChangeHandler(e) {
var file = e.target.files[0]; var file = e.target.files[0];
var reader = new FileReader(); var reader = new FileReader();
@@ -108,7 +108,7 @@ export default class SavedItemPane extends Component {
try { try {
items = JSON.parse(progressEvent.target.result); items = JSON.parse(progressEvent.target.result);
log(items); log(items);
this.props.mergeImportedItems(items); mergeImportedItems(items);
} catch (exception) { } catch (exception) {
log(exception); log(exception);
alert( alert(
@@ -122,125 +122,115 @@ export default class SavedItemPane extends Component {
reader.readAsText(file, 'utf-8'); reader.readAsText(file, 'utf-8');
} }
importBtnClickHandler(e) { function importBtnClickHandler(e) {
var input = document.createElement('input'); var input = document.createElement('input');
input.type = 'file'; input.type = 'file';
input.style.display = 'none'; input.style.display = 'none';
input.accept = 'accept="application/json'; input.accept = 'accept="application/json';
document.body.appendChild(input); document.body.appendChild(input);
input.addEventListener('change', this.importFileChangeHandler.bind(this)); input.addEventListener('change', importFileChangeHandler);
input.click(); input.click();
trackEvent('ui', 'importBtnClicked'); trackEvent('ui', 'importBtnClicked');
e.preventDefault(); e.preventDefault();
} }
searchInputHandler(e) { function searchInputHandler(e) {
const text = e.target.value.toLowerCase(); const text = e.target.value.toLowerCase().trim();
if (!text) { if (!text) {
this.setState({ setFilteredItems(items);
filteredItems: this.state.items
});
} else { } else {
this.setState({ setFilteredItems(
filteredItems: this.state.items.filter( items.filter(item => item.title.toLowerCase().indexOf(text) !== -1)
item => item.title.toLowerCase().indexOf(text) !== -1 );
)
});
} }
trackEvent('ui', 'searchInputType'); trackEvent('ui', 'searchInputType');
} }
render( return (
{ isOpen, exportBtnClickHandler }, <I18n>
{ filteredItems = this.state.items, items = [] } {({ i18n }) => (
) { <div
return ( id="js-saved-items-pane"
<I18n> class={`saved-items-pane ${isOpen ? 'is-open' : ''}`}
{({ i18n }) => ( onKeyDown={keyDownHandler}
<div aria-hidden={isOpen}
id="js-saved-items-pane" >
class={`saved-items-pane ${isOpen ? 'is-open' : ''}`} <button
onKeyDown={this.keyDownHandler.bind(this)} onClick={onCloseIntent}
aria-hidden={isOpen} class="btn saved-items-pane__close-btn"
id="js-saved-items-pane-close-btn"
aria-label={i18n._(t`Close saved creations pane`)}
> >
<button X
onClick={this.onCloseIntent.bind(this)} </button>
class="btn saved-items-pane__close-btn" <div
id="js-saved-items-pane-close-btn" class="flex flex-v-center"
aria-label={i18n._(t`Close saved creations pane`)} style="justify-content: space-between;"
> >
X <h3>
</button> <Trans>My Library ({filteredItems.length})</Trans>
<div </h3>
class="flex flex-v-center"
style="justify-content: space-between;"
>
<h3>
<Trans>My Library ({filteredItems.length})</Trans>
</h3>
<div> <div>
<button <button
onClick={exportBtnClickHandler} onClick={onExport}
class="btn--dark hint--bottom-left hint--rounded hint--medium" class="btn--dark hint--bottom-left hint--rounded hint--medium"
aria-label={i18n._( aria-label={i18n._(
t`Export all your creations into a single importable file.` t`Export all your creations into a single importable file.`
)} )}
> >
<Trans>Export</Trans> <Trans>Export</Trans>
</button> </button>
<button <button
onClick={this.importBtnClickHandler.bind(this)} onClick={importBtnClickHandler}
class="btn--dark hint--bottom-left hint--rounded hint--medium" class="btn--dark hint--bottom-left hint--rounded hint--medium"
aria-label={i18n._( aria-label={i18n._(
t`Import your creations. Only the file that you export through the 'Export' button can be imported.` t`Import your creations. Only the file that you export through the 'Export' button can be imported.`
)} )}
> >
<Trans>Import</Trans> <Trans>Import</Trans>
</button> </button>
</div>
</div>
<input
autocomplete="off"
type="search"
id="searchInput"
class="search-input"
onInput={this.searchInputHandler.bind(this)}
placeholder={i18n._(t`Search your creations here...`)}
/>
<div id="js-saved-items-wrap" class="saved-items-pane__container">
{!filteredItems.length && items.length ? (
<div class="mt-1">
<Trans>No match found.</Trans>
</div>
) : null}
{filteredItems.map(item => (
<ItemTile
item={item}
onClick={this.itemClickHandler.bind(this, item)}
onForkBtnClick={this.itemForkBtnClickHandler.bind(this, item)}
onRemoveBtnClick={this.itemRemoveBtnClickHandler.bind(
this,
item
)}
/>
))}
{!items.length ? (
<div class="tac">
<h2 class="opacity--30">
<Trans>Nothing saved here.</Trans>
</h2>
<img
style="max-width: 80%; opacity:0.4"
src="assets/empty.svg"
/>
</div>
) : null}
</div> </div>
</div> </div>
)} <form autoComplete="off" onSubmit={e => e.preventDefault()}>
</I18n> <input
); type="search"
} id="searchInput"
ref={searchInputRef}
class="search-input"
onInput={searchInputHandler}
placeholder={i18n._(t`Search your creations here...`)}
/>
</form>
<div id="js-saved-items-wrap" class="saved-items-pane__container">
{!filteredItems.length && items.length ? (
<div class="mt-1">
<Trans>No match found.</Trans>
</div>
) : null}
{filteredItems.map(item => (
<ItemTile
item={item}
onClick={() => itemClickHandler(item)}
onForkBtnClick={() => itemForkBtnClickHandler(item)}
onRemoveBtnClick={() => itemRemoveBtnClickHandler(item)}
/>
))}
{!items.length ? (
<div class="tac">
<h2 class="opacity--30">
<Trans>Nothing saved here.</Trans>
</h2>
<img
style="max-width: 80%; opacity:0.4"
src="assets/empty.svg"
/>
</div>
) : null}
</div>
</div>
)}
</I18n>
);
} }

View File

@@ -1665,15 +1665,13 @@ export default class App extends Component {
</div> </div>
<SavedItemPane <SavedItemPane
items={this.state.savedItems} itemsMap={this.state.savedItems}
isOpen={this.state.isSavedItemPaneOpen} isOpen={this.state.isSavedItemPaneOpen}
closeHandler={this.closeSavedItemsPane.bind(this)} closeHandler={this.closeSavedItemsPane.bind(this)}
itemClickHandler={this.itemClickHandler.bind(this)} onItemSelect={this.itemClickHandler.bind(this)}
itemRemoveBtnClickHandler={this.itemRemoveBtnClickHandler.bind( onItemRemove={this.itemRemoveBtnClickHandler.bind(this)}
this onItemFork={this.itemForkBtnClickHandler.bind(this)}
)} onExport={this.exportBtnClickHandler.bind(this)}
itemForkBtnClickHandler={this.itemForkBtnClickHandler.bind(this)}
exportBtnClickHandler={this.exportBtnClickHandler.bind(this)}
mergeImportedItems={this.mergeImportedItems.bind(this)} mergeImportedItems={this.mergeImportedItems.bind(this)}
/> />