Cleaning up list detail
This commit is contained in:
@@ -1,56 +1,65 @@
|
|||||||
|
import {
|
||||||
|
IonBackButton,
|
||||||
|
IonButtons,
|
||||||
|
IonCheckbox,
|
||||||
|
IonContent,
|
||||||
|
IonHeader,
|
||||||
|
IonItem,
|
||||||
|
IonLabel,
|
||||||
|
IonList,
|
||||||
|
IonPage,
|
||||||
|
IonTitle,
|
||||||
|
IonToolbar,
|
||||||
|
} from '@ionic/react';
|
||||||
import Link from '../../components/Link';
|
import Link from '../../components/Link';
|
||||||
|
|
||||||
import usePage from '../../hooks/usePage';
|
|
||||||
import Store from '../../store';
|
import Store from '../../store';
|
||||||
import * as actions from '../../store/actions';
|
import * as actions from '../../store/actions';
|
||||||
import * as selectors from '../../store/selectors';
|
import * as selectors from '../../store/selectors';
|
||||||
|
|
||||||
import Content from '../ui/Content';
|
|
||||||
import List from '../ui/List';
|
|
||||||
|
|
||||||
const ListItems = ({ list }) => {
|
const ListItems = ({ list }) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<IonList>
|
||||||
<div className="py-2">
|
|
||||||
<Link href="/lists">All Lists</Link>
|
|
||||||
</div>
|
|
||||||
{(list?.items || []).map(item => (
|
{(list?.items || []).map(item => (
|
||||||
<ListItemEntry list={list} item={item} />
|
<ListItemEntry list={list} item={item} />
|
||||||
))}
|
))}
|
||||||
</>
|
</IonList>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const ListItemEntry = ({ list, item }) => (
|
const ListItemEntry = ({ list, item }) => (
|
||||||
<div
|
<IonItem onClick={() => actions.setDone(list, item, !item.done)}>
|
||||||
className="p-4 border-solid border-b cursor-pointer flex select-none"
|
<IonLabel>{item.name}</IonLabel>
|
||||||
onClick={() => actions.setDone(list, item, !item.done)}
|
<IonCheckbox checked={item.done || false} slot="end" />
|
||||||
>
|
</IonItem>
|
||||||
<span className="text-md flex-1">{item.name}</span>
|
|
||||||
<input
|
|
||||||
className="pointer-events-none select-none"
|
|
||||||
type="checkbox"
|
|
||||||
checked={item.done || false}
|
|
||||||
readOnly={true}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const ListDetail = ({ selected, listId, params }) => {
|
const ListDetail = ({ match }) => {
|
||||||
const lists = Store.useState(selectors.getLists);
|
const lists = Store.useState(selectors.getLists);
|
||||||
const actualListId = listId ? listId : params?.listId || null;
|
const {
|
||||||
const loadedList = lists.find(l => l.id === actualListId);
|
params: { listId },
|
||||||
|
} = match;
|
||||||
usePage({
|
const loadedList = lists.find(l => l.id === listId);
|
||||||
title: loadedList.name,
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Content className="p-4">
|
<IonPage>
|
||||||
<List className="h-full w-full">
|
<IonHeader>
|
||||||
|
<IonToolbar>
|
||||||
|
<IonButtons slot="start">
|
||||||
|
<IonBackButton defaultHref="/tabs/lists" />
|
||||||
|
</IonButtons>
|
||||||
|
<IonTitle>{loadedList.name}</IonTitle>
|
||||||
|
</IonToolbar>
|
||||||
|
</IonHeader>
|
||||||
|
<IonContent fullscreen>
|
||||||
|
<IonHeader collapse="condense">
|
||||||
|
<IonToolbar>
|
||||||
|
<IonTitle size="large">{loadedList.name}</IonTitle>
|
||||||
|
</IonToolbar>
|
||||||
|
</IonHeader>
|
||||||
<ListItems list={loadedList} />
|
<ListItems list={loadedList} />
|
||||||
</List>
|
</IonContent>
|
||||||
</Content>
|
</IonPage>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { cog, flash, list } from 'ionicons/icons';
|
|||||||
|
|
||||||
import Home from './Feed';
|
import Home from './Feed';
|
||||||
import Lists from './Lists';
|
import Lists from './Lists';
|
||||||
|
import ListDetail from './ListDetail';
|
||||||
import Settings from './Settings';
|
import Settings from './Settings';
|
||||||
|
|
||||||
const Tabs = () => {
|
const Tabs = () => {
|
||||||
@@ -14,6 +15,7 @@ const Tabs = () => {
|
|||||||
<IonRouterOutlet>
|
<IonRouterOutlet>
|
||||||
<Route path="/tabs/feed" component={Home} exact={true} />
|
<Route path="/tabs/feed" component={Home} exact={true} />
|
||||||
<Route path="/tabs/lists" component={Lists} exact={true} />
|
<Route path="/tabs/lists" component={Lists} exact={true} />
|
||||||
|
<Route path="/tabs/lists/:listId" component={ListDetail} exact={true} />
|
||||||
<Route path="/tabs/settings" component={Settings} exact={true} />
|
<Route path="/tabs/settings" component={Settings} exact={true} />
|
||||||
<Route path="/tabs" render={() => <Redirect to="/tabs/feed" />} exact={true} />
|
<Route path="/tabs" render={() => <Redirect to="/tabs/feed" />} exact={true} />
|
||||||
</IonRouterOutlet>
|
</IonRouterOutlet>
|
||||||
|
|||||||
@@ -1,85 +0,0 @@
|
|||||||
import { useEffect, useRef, useState, useCallback } from 'react';
|
|
||||||
|
|
||||||
// useEffect(() => {
|
|
||||||
// While History API does have `popstate` event, the only
|
|
||||||
// proper way to listen to changes via `push/replaceState`
|
|
||||||
// is to monkey-patch these methods.
|
|
||||||
//
|
|
||||||
// See https://stackoverflow.com/a/4585031
|
|
||||||
if (typeof history !== 'undefined') {
|
|
||||||
for (const type of [eventPushState, eventReplaceState]) {
|
|
||||||
const original = history[type];
|
|
||||||
|
|
||||||
history[type] = function () {
|
|
||||||
const result = original.apply(this, arguments);
|
|
||||||
const event = new Event(type);
|
|
||||||
event.arguments = arguments;
|
|
||||||
|
|
||||||
dispatchEvent(event);
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.log('No history API');
|
|
||||||
}
|
|
||||||
// });
|
|
||||||
|
|
||||||
/**
|
|
||||||
* History API docs @see https://developer.mozilla.org/en-US/docs/Web/API/History
|
|
||||||
*/
|
|
||||||
const eventPopstate = 'popstate';
|
|
||||||
const eventPushState = 'pushState';
|
|
||||||
const eventReplaceState = 'replaceState';
|
|
||||||
export const events = [eventPopstate, eventPushState, eventReplaceState];
|
|
||||||
|
|
||||||
const useLocation = ({ base = '' } = {}) => {
|
|
||||||
const getCurrentPathname = useCallback(base => {
|
|
||||||
const path = typeof location === 'undefined' ? '/' : location.pathname;
|
|
||||||
|
|
||||||
return !path.toLowerCase().indexOf(base.toLowerCase())
|
|
||||||
? path.slice(base.length) || '/'
|
|
||||||
: '~' + path;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const [path, update] = useState(() => getCurrentPathname(base)); // @see https://reactjs.org/docs/hooks-reference.html#lazy-initial-state
|
|
||||||
const prevPath = useRef(path);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// this function checks if the location has been changed since the
|
|
||||||
// last render and updates the state only when needed.
|
|
||||||
// unfortunately, we can't rely on `path` value here, since it can be stale,
|
|
||||||
// that's why we store the last pathname in a ref.
|
|
||||||
const checkForUpdates = () => {
|
|
||||||
const pathname = getCurrentPathname(base);
|
|
||||||
prevPath.current !== pathname && update((prevPath.current = pathname));
|
|
||||||
};
|
|
||||||
|
|
||||||
events.map(e => addEventListener(e, checkForUpdates));
|
|
||||||
|
|
||||||
// it's possible that an update has occurred between render and the effect handler,
|
|
||||||
// so we run additional check on mount to catch these updates. Based on:
|
|
||||||
// https://gist.github.com/bvaughn/e25397f70e8c65b0ae0d7c90b731b189
|
|
||||||
checkForUpdates();
|
|
||||||
|
|
||||||
return () => events.map(e => removeEventListener(e, checkForUpdates));
|
|
||||||
}, [base]);
|
|
||||||
|
|
||||||
// the 2nd argument of the `useLocation` return value is a function
|
|
||||||
// that allows to perform a navigation.
|
|
||||||
//
|
|
||||||
// the function reference should stay the same between re-renders, so that
|
|
||||||
// it can be passed down as an element prop without any performance concerns.
|
|
||||||
const navigate = useCallback(
|
|
||||||
(to, { replace = false } = {}) =>
|
|
||||||
history[replace ? eventReplaceState : eventPushState](
|
|
||||||
null,
|
|
||||||
'',
|
|
||||||
// handle nested routers and absolute paths
|
|
||||||
to[0] === '~' ? to.slice(1) : base + to
|
|
||||||
),
|
|
||||||
[base]
|
|
||||||
);
|
|
||||||
|
|
||||||
return [path, navigate];
|
|
||||||
};
|
|
||||||
export default useLocation;
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
import { useEffect } from 'react';
|
|
||||||
import Store from '../store';
|
|
||||||
|
|
||||||
const usePage = fields => {
|
|
||||||
useEffect(() => {
|
|
||||||
console.log('Use page effect', fields.title);
|
|
||||||
Store.update(s => {
|
|
||||||
s.currentPage = fields;
|
|
||||||
});
|
|
||||||
}, [fields]);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default usePage;
|
|
||||||
Reference in New Issue
Block a user