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:
@@ -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>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
@@ -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)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user