mirror of
https://github.com/chinchang/web-maker.git
synced 2025-04-04 19:02:27 +02:00
Migrate SavedItemPane to functional component 🥳
This commit is contained in:
parent
e21dbdf70f
commit
1a333036af
@ -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 { trackEvent } from '../analytics';
|
||||
import { ItemTile } from './ItemTile';
|
||||
import { Trans, NumberFormat, t } from '@lingui/macro';
|
||||
import { Trans, t } from '@lingui/macro';
|
||||
import { I18n } from '@lingui/react';
|
||||
|
||||
export default class SavedItemPane extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
// this.items = [];
|
||||
}
|
||||
export default function SavedItemPane({
|
||||
itemsMap,
|
||||
isOpen,
|
||||
closeHandler,
|
||||
onItemSelect,
|
||||
onitemRemove,
|
||||
onItemFork,
|
||||
onExport,
|
||||
mergeImportedItems
|
||||
}) {
|
||||
const [items, setItems] = useState([]);
|
||||
const [filteredItems, setFilteredItems] = useState([]);
|
||||
const searchInputRef = useRef();
|
||||
|
||||
static getDerivedStateFromProps({ items = {} }, state) {
|
||||
const newItems = Object.values(items);
|
||||
useEffect(() => {
|
||||
if (!itemsMap) return;
|
||||
const newItems = Object.values(itemsMap);
|
||||
newItems.sort(function(a, b) {
|
||||
return b.updatedOn - a.updatedOn;
|
||||
});
|
||||
return {
|
||||
items: newItems
|
||||
};
|
||||
}
|
||||
shouldComponentUpdate(nextProps, nextState) {
|
||||
return (
|
||||
nextProps.items !== this.props.items ||
|
||||
nextProps.isOpen !== this.props.isOpen ||
|
||||
nextState.filteredItems !== this.state.filteredItems
|
||||
);
|
||||
}
|
||||
setItems(newItems);
|
||||
setFilteredItems(newItems);
|
||||
}, [itemsMap]);
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
useEffect(() => {
|
||||
// Opening
|
||||
if (this.props.isOpen && !prevProps.isOpen) {
|
||||
window.searchInput.value = '';
|
||||
window.searchInput.focus();
|
||||
if (isOpen) {
|
||||
searchInputRef.current.value = '';
|
||||
searchInputRef.current.focus();
|
||||
}
|
||||
// Closing
|
||||
if (!this.props.isOpen && prevProps.isOpen) {
|
||||
this.setState({
|
||||
filteredItems: undefined
|
||||
});
|
||||
if (!isOpen) {
|
||||
setFilteredItems([]);
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
function onCloseIntent() {
|
||||
closeHandler();
|
||||
}
|
||||
onCloseIntent() {
|
||||
this.props.closeHandler();
|
||||
function itemClickHandler(item) {
|
||||
onItemSelect(item);
|
||||
}
|
||||
itemClickHandler(item) {
|
||||
this.props.itemClickHandler(item);
|
||||
}
|
||||
itemRemoveBtnClickHandler(item, e) {
|
||||
function itemRemoveBtnClickHandler(item, e) {
|
||||
e.stopPropagation();
|
||||
this.props.itemRemoveBtnClickHandler(item);
|
||||
onitemRemove(item);
|
||||
}
|
||||
itemForkBtnClickHandler(item, e) {
|
||||
function itemForkBtnClickHandler(item, e) {
|
||||
e.stopPropagation();
|
||||
this.props.itemForkBtnClickHandler(item);
|
||||
onItemFork(item);
|
||||
}
|
||||
keyDownHandler(event) {
|
||||
if (!this.props.isOpen) {
|
||||
function keyDownHandler(event) {
|
||||
if (!isOpen) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -84,22 +85,21 @@ export default class SavedItemPane extends Component {
|
||||
}
|
||||
|
||||
if (isEnterKeyPressed && selectedItemElement) {
|
||||
const item = this.props.items[selectedItemElement.dataset.itemId];
|
||||
console.log('opening', item);
|
||||
this.props.itemClickHandler(item);
|
||||
const item = itemsMap[selectedItemElement.dataset.itemId];
|
||||
onItemSelect(item);
|
||||
trackEvent('ui', 'openItemKeyboardShortcut');
|
||||
}
|
||||
|
||||
// Fork shortcut inside saved creations panel with Ctrl/⌘ + F
|
||||
if (isForkKeyPressed) {
|
||||
event.preventDefault();
|
||||
const item = this.props.items[selectedItemElement.dataset.itemId];
|
||||
this.props.itemForkBtnClickHandler(item);
|
||||
const item = itemsMap[selectedItemElement.dataset.itemId];
|
||||
itemForkBtnClickHandler(item);
|
||||
trackEvent('ui', 'forkKeyboardShortcut');
|
||||
}
|
||||
}
|
||||
|
||||
importFileChangeHandler(e) {
|
||||
function importFileChangeHandler(e) {
|
||||
var file = e.target.files[0];
|
||||
|
||||
var reader = new FileReader();
|
||||
@ -108,7 +108,7 @@ export default class SavedItemPane extends Component {
|
||||
try {
|
||||
items = JSON.parse(progressEvent.target.result);
|
||||
log(items);
|
||||
this.props.mergeImportedItems(items);
|
||||
mergeImportedItems(items);
|
||||
} catch (exception) {
|
||||
log(exception);
|
||||
alert(
|
||||
@ -122,125 +122,115 @@ export default class SavedItemPane extends Component {
|
||||
reader.readAsText(file, 'utf-8');
|
||||
}
|
||||
|
||||
importBtnClickHandler(e) {
|
||||
function importBtnClickHandler(e) {
|
||||
var input = document.createElement('input');
|
||||
input.type = 'file';
|
||||
input.style.display = 'none';
|
||||
input.accept = 'accept="application/json';
|
||||
document.body.appendChild(input);
|
||||
input.addEventListener('change', this.importFileChangeHandler.bind(this));
|
||||
input.addEventListener('change', importFileChangeHandler);
|
||||
input.click();
|
||||
trackEvent('ui', 'importBtnClicked');
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
searchInputHandler(e) {
|
||||
const text = e.target.value.toLowerCase();
|
||||
function searchInputHandler(e) {
|
||||
const text = e.target.value.toLowerCase().trim();
|
||||
if (!text) {
|
||||
this.setState({
|
||||
filteredItems: this.state.items
|
||||
});
|
||||
setFilteredItems(items);
|
||||
} else {
|
||||
this.setState({
|
||||
filteredItems: this.state.items.filter(
|
||||
item => item.title.toLowerCase().indexOf(text) !== -1
|
||||
)
|
||||
});
|
||||
setFilteredItems(
|
||||
items.filter(item => item.title.toLowerCase().indexOf(text) !== -1)
|
||||
);
|
||||
}
|
||||
trackEvent('ui', 'searchInputType');
|
||||
}
|
||||
|
||||
render(
|
||||
{ isOpen, exportBtnClickHandler },
|
||||
{ filteredItems = this.state.items, items = [] }
|
||||
) {
|
||||
return (
|
||||
<I18n>
|
||||
{({ i18n }) => (
|
||||
<div
|
||||
id="js-saved-items-pane"
|
||||
class={`saved-items-pane ${isOpen ? 'is-open' : ''}`}
|
||||
onKeyDown={this.keyDownHandler.bind(this)}
|
||||
aria-hidden={isOpen}
|
||||
return (
|
||||
<I18n>
|
||||
{({ i18n }) => (
|
||||
<div
|
||||
id="js-saved-items-pane"
|
||||
class={`saved-items-pane ${isOpen ? 'is-open' : ''}`}
|
||||
onKeyDown={keyDownHandler}
|
||||
aria-hidden={isOpen}
|
||||
>
|
||||
<button
|
||||
onClick={onCloseIntent}
|
||||
class="btn saved-items-pane__close-btn"
|
||||
id="js-saved-items-pane-close-btn"
|
||||
aria-label={i18n._(t`Close saved creations pane`)}
|
||||
>
|
||||
<button
|
||||
onClick={this.onCloseIntent.bind(this)}
|
||||
class="btn saved-items-pane__close-btn"
|
||||
id="js-saved-items-pane-close-btn"
|
||||
aria-label={i18n._(t`Close saved creations pane`)}
|
||||
>
|
||||
X
|
||||
</button>
|
||||
<div
|
||||
class="flex flex-v-center"
|
||||
style="justify-content: space-between;"
|
||||
>
|
||||
<h3>
|
||||
<Trans>My Library ({filteredItems.length})</Trans>
|
||||
</h3>
|
||||
X
|
||||
</button>
|
||||
<div
|
||||
class="flex flex-v-center"
|
||||
style="justify-content: space-between;"
|
||||
>
|
||||
<h3>
|
||||
<Trans>My Library ({filteredItems.length})</Trans>
|
||||
</h3>
|
||||
|
||||
<div>
|
||||
<button
|
||||
onClick={exportBtnClickHandler}
|
||||
class="btn--dark hint--bottom-left hint--rounded hint--medium"
|
||||
aria-label={i18n._(
|
||||
t`Export all your creations into a single importable file.`
|
||||
)}
|
||||
>
|
||||
<Trans>Export</Trans>
|
||||
</button>
|
||||
<button
|
||||
onClick={this.importBtnClickHandler.bind(this)}
|
||||
class="btn--dark hint--bottom-left hint--rounded hint--medium"
|
||||
aria-label={i18n._(
|
||||
t`Import your creations. Only the file that you export through the 'Export' button can be imported.`
|
||||
)}
|
||||
>
|
||||
<Trans>Import</Trans>
|
||||
</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>
|
||||
<button
|
||||
onClick={onExport}
|
||||
class="btn--dark hint--bottom-left hint--rounded hint--medium"
|
||||
aria-label={i18n._(
|
||||
t`Export all your creations into a single importable file.`
|
||||
)}
|
||||
>
|
||||
<Trans>Export</Trans>
|
||||
</button>
|
||||
<button
|
||||
onClick={importBtnClickHandler}
|
||||
class="btn--dark hint--bottom-left hint--rounded hint--medium"
|
||||
aria-label={i18n._(
|
||||
t`Import your creations. Only the file that you export through the 'Export' button can be imported.`
|
||||
)}
|
||||
>
|
||||
<Trans>Import</Trans>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</I18n>
|
||||
);
|
||||
}
|
||||
<form autoComplete="off" onSubmit={e => e.preventDefault()}>
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
@ -1665,15 +1665,13 @@ export default class App extends Component {
|
||||
</div>
|
||||
|
||||
<SavedItemPane
|
||||
items={this.state.savedItems}
|
||||
itemsMap={this.state.savedItems}
|
||||
isOpen={this.state.isSavedItemPaneOpen}
|
||||
closeHandler={this.closeSavedItemsPane.bind(this)}
|
||||
itemClickHandler={this.itemClickHandler.bind(this)}
|
||||
itemRemoveBtnClickHandler={this.itemRemoveBtnClickHandler.bind(
|
||||
this
|
||||
)}
|
||||
itemForkBtnClickHandler={this.itemForkBtnClickHandler.bind(this)}
|
||||
exportBtnClickHandler={this.exportBtnClickHandler.bind(this)}
|
||||
onItemSelect={this.itemClickHandler.bind(this)}
|
||||
onItemRemove={this.itemRemoveBtnClickHandler.bind(this)}
|
||||
onItemFork={this.itemForkBtnClickHandler.bind(this)}
|
||||
onExport={this.exportBtnClickHandler.bind(this)}
|
||||
mergeImportedItems={this.mergeImportedItems.bind(this)}
|
||||
/>
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user