Skip to content

Commit 5723cfc

Browse files
committed
Add back and forward buttons
Add back and forward buttons that allow a user to traverse their API navigation history
1 parent e9e1055 commit 5723cfc

File tree

2 files changed

+131
-20
lines changed

2 files changed

+131
-20
lines changed

src/browser.tsx

Lines changed: 87 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,67 @@
11
import { Accessor, createContext, createSignal, ParentComponent, Setter, useContext } from 'solid-js';
2+
import { createStore } from 'solid-js/store';
23

3-
import { Action, Entity, follow, parse, submit, Target } from '@siren-js/client';
4+
import * as Siren from '@siren-js/client';
5+
6+
interface HistoryItem {
7+
url: string;
8+
content: string;
9+
entity?: Siren.Entity;
10+
error?: Error;
11+
}
412

513
interface BrowserContextValue {
6-
entity: Accessor<Entity | undefined>;
14+
canGoBack: Accessor<boolean>;
15+
canGoForward: Accessor<boolean>;
16+
entity: Accessor<Siren.Entity | undefined>;
17+
error: Accessor<Error | undefined>;
718
loading: Accessor<boolean>;
819
location: Accessor<string>;
920
rawContent: Accessor<string | undefined>;
1021
setLocation: Setter<string>;
11-
error: Accessor<Error | undefined>;
12-
follow: (target: Target) => void;
13-
submit: (action: Action) => void;
22+
follow(target: Siren.Target): void;
23+
goBack(): void;
24+
goForward(): void;
25+
submit(action: Siren.Action): void;
1426
}
1527

1628
const BrowserContext = createContext({} as BrowserContextValue);
1729

1830
export const BrowserProvider: ParentComponent = (props) => {
1931
const [loading, setLoading] = createSignal(false);
2032
const [location, setLocation] = createSignal('');
21-
const [entity, setEntity] = createSignal<Entity>();
33+
const [history, setHistory] = createStore<HistoryItem[]>([]);
34+
const [historyIndex, setHistoryIndex] = createSignal(-1);
2235
const [rawContent, setRawContent] = createSignal<string>();
36+
const [entity, setEntity] = createSignal<Siren.Entity>();
2337
const [error, setError] = createSignal<Error>();
2438

2539
const responseHandler = async (res: Response): Promise<void> => {
2640
setLocation(res.url);
41+
2742
const text = await res.text();
2843
setRawContent(text);
44+
45+
const historyItem: HistoryItem = {
46+
url: res.url,
47+
content: text,
48+
};
49+
2950
const contentType = res.headers.get('Content-Type') ?? '';
3051
if (/^application\/vnd\.siren\+json(;|$)/.test(contentType)) {
52+
const entity = await Siren.parse(text);
3153
setError(undefined);
32-
setEntity(await parse(text));
54+
setEntity(entity);
55+
historyItem.entity = entity;
3356
} else {
57+
const error = new Error(`Unable to parse unrecognized Content-Type: ${contentType}`);
3458
setEntity(undefined);
35-
setError(new Error(`Unable to parse unrecognized Content-Type: ${contentType}`));
59+
setError(error);
60+
historyItem.error = error;
3661
}
62+
63+
setHistory([...history.slice(0, historyIndex() + 1), historyItem]);
64+
setHistoryIndex((value) => value + 1);
3765
};
3866

3967
const navigate = (req: () => Promise<Response>) => {
@@ -44,18 +72,59 @@ export const BrowserProvider: ParentComponent = (props) => {
4472
.finally(() => setLoading(false));
4573
};
4674

47-
const value: BrowserContextValue = {
48-
entity,
49-
error,
50-
loading,
51-
location,
52-
rawContent,
53-
setLocation,
54-
follow: (url) => navigate(() => follow(url)),
55-
submit: (action: Action) => navigate(() => submit(action)),
75+
const follow = (target: Siren.Target) => navigate(() => Siren.follow(target));
76+
const submit = (action: Siren.Action) => navigate(() => Siren.submit(action));
77+
78+
const setStateFrom = (item: HistoryItem) => {
79+
setLocation(item.url);
80+
setRawContent(item.content);
81+
setEntity(item.entity);
82+
setError(item.error);
83+
};
84+
85+
const go = (indexFn: (currentIndex: number) => number) => {
86+
setHistoryIndex((currentIndex) => {
87+
const index = indexFn(currentIndex);
88+
const historyItem = history[index];
89+
setStateFrom(historyItem);
90+
return index;
91+
});
92+
};
93+
94+
const canGoBack = () => historyIndex() > 0;
95+
const goBack = () => {
96+
if (canGoBack()) {
97+
go((i) => i - 1);
98+
}
99+
};
100+
101+
const canGoForward = () => historyIndex() < history.length - 1;
102+
const goForward = () => {
103+
if (canGoForward()) {
104+
go((i) => i + 1);
105+
}
56106
};
57107

58-
return <BrowserContext.Provider value={value}>{props.children}</BrowserContext.Provider>;
108+
return (
109+
<BrowserContext.Provider
110+
value={{
111+
canGoBack,
112+
canGoForward,
113+
entity,
114+
error,
115+
follow,
116+
goBack,
117+
goForward,
118+
loading,
119+
location,
120+
rawContent,
121+
setLocation,
122+
submit,
123+
}}
124+
>
125+
{props.children}
126+
</BrowserContext.Provider>
127+
);
59128
};
60129

61130
export const useBrowserContext = () => useContext(BrowserContext);

src/components/Navigator.tsx

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,16 @@ import { Component } from 'solid-js';
33
import { useBrowserContext } from '../browser';
44

55
export const Navigator: Component = () => {
6-
const { loading, location, setLocation, follow } = useBrowserContext();
6+
const {
7+
loading,
8+
location,
9+
setLocation,
10+
canGoBack,
11+
canGoForward,
12+
goBack: back,
13+
goForward: forward,
14+
follow,
15+
} = useBrowserContext();
716
return (
817
<form
918
onSubmit={(e) => {
@@ -12,6 +21,39 @@ export const Navigator: Component = () => {
1221
}}
1322
>
1423
<div class="field has-addons">
24+
<div class="control">
25+
<button
26+
type="button"
27+
class="button"
28+
classList={{ 'is-loading': loading() }}
29+
disabled={!canGoBack()}
30+
onClick={back}
31+
>
32+
<span class="icon">
33+
<i class="fas fa-arrow-left" />
34+
</span>
35+
</button>
36+
</div>
37+
<div class="control">
38+
<button
39+
type="button"
40+
class="button"
41+
classList={{ 'is-loading': loading() }}
42+
disabled={!canGoForward()}
43+
onClick={forward}
44+
>
45+
<span class="icon">
46+
<i class="fas fa-arrow-right" />
47+
</span>
48+
</button>
49+
</div>
50+
{/* <div class="control">
51+
<button type="button" class="button" classList={{ 'is-loading': loading() }}>
52+
<span class="icon">
53+
<i class="fas fa-redo" />
54+
</span>
55+
</button>
56+
</div> */}
1557
<div class="control is-expanded">
1658
<input
1759
class="input"
@@ -24,7 +66,7 @@ export const Navigator: Component = () => {
2466
/>
2567
</div>
2668
<div class="control">
27-
<button class="button is-primary" classList={{ 'is-loading': loading() }}>
69+
<button type="submit" class="button is-primary" classList={{ 'is-loading': loading() }}>
2870
<span class="icon">
2971
<i class="fas fa-search" />
3072
</span>

0 commit comments

Comments
 (0)