Skip to content

Commit 3e681b8

Browse files
committed
clean up the layout system slightly
remove shared folder brought back some of the old camera and isbn code
1 parent cd9ed08 commit 3e681b8

File tree

15 files changed

+290
-78
lines changed

15 files changed

+290
-78
lines changed

dist/main.js

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/main.js.map

+4-4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/service-worker.js

+4-4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/service-worker.js.map

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/main.ts

+12-15
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import m from "@/mithril";
22
import * as Toast from "@/mithril-toast";
33

4+
// import { Camera } from "^/camera";
5+
46
import AddBulk from "./pages/add-bulk";
57
import AddScan from "./pages/add-scan";
68
import AddSearch from "./pages/add-search";
@@ -10,8 +12,6 @@ import { Tags } from "./pages/tags";
1012
import { Wishlist } from "./pages/wishlist";
1113
import { Settings } from "./pages/settings";
1214

13-
import Layout from "./shared/layout";
14-
1515
declare global {
1616
interface Sissix {
1717
gitHash: string | undefined;
@@ -21,14 +21,6 @@ declare global {
2121
const SISSIX: Sissix;
2222
}
2323

24-
const wrapper = (comp: m.ClosureComponent): m.RouteResolver => {
25-
return {
26-
render: () => {
27-
return m(Layout, m(comp));
28-
},
29-
};
30-
};
31-
3224
const registerServiceWorker = async () => {
3325
let registration: ServiceWorkerRegistration | undefined | null = null;
3426
let installingRegistration: ServiceWorker | undefined | null = null;
@@ -78,10 +70,15 @@ window.addEventListener("load", () => {
7870
"/add/bulk": AddBulk,
7971
"/add/scan": AddScan,
8072
"/add/search": AddSearch,
81-
"/bookshelves": wrapper(Bookshelves),
82-
"/bookshelves/:bookshelfId": wrapper(Bookshelf),
83-
"/tags": wrapper(Tags),
84-
"/wishlist": wrapper(Wishlist),
85-
"/settings": wrapper(Settings),
73+
"/bookshelves": Bookshelves,
74+
"/bookshelves/:bookshelfId": Bookshelf,
75+
"/tags": Tags,
76+
"/wishlist": Wishlist,
77+
"/settings": Settings,
8678
});
79+
80+
// TODO: have a setup screen where it walks the user though so they don't get a camera use popup the moment they open the app
81+
(async () => {
82+
// Camera.requestPermission();
83+
})();
8784
});

src/pages/bookshelves/index.ts

+24-23
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ import { Err, type Result } from "../../utils";
99

1010
import BookshelfEntry from "../../components/bookshelf";
1111

12+
import Layout from "pages/layout";
13+
1214
export const Bookshelves: m.ClosureComponent = () => {
13-
let bookshelves: Result<Bookshelf[]> = Err(
14-
new Error("bookshelves didn't load"),
15-
);
15+
let bookshelves: Result<Bookshelf[]> = Err(new Error("bookshelves didn't load"));
1616

1717
let fabOpen = false;
1818

@@ -26,29 +26,30 @@ export const Bookshelves: m.ClosureComponent = () => {
2626
m.redraw();
2727
},
2828
view: (vnode) => {
29-
return bookshelves.match({
30-
ok: (bookshelves) => {
31-
return m("div", { class: "bg-zinc-100 px-4" }, [
32-
m(Fab, { kind: "tertiary", icon: m(Add), onclick: toggle }, [
33-
fabOpen ? [
34-
m("div", { class: "absolute right-6 bottom-40 flex flex-col gap-2" }, [
35-
m(Button, { size: "small", icon: m(Add) }, "Bulk Scan"),
36-
m(Button, { size: "small", icon: m(Add) }, "Scan"),
37-
m(Button, { size: "small", icon: m(Add) }, "Add"),
38-
]),
39-
] : null,
29+
return bookshelves
30+
.map((bookshelves) => {
31+
const fabButtons = [
32+
m("div", { class: "absolute right-6 bottom-40 flex flex-col gap-2" }, [
33+
m(Button, { size: "small", icon: m(Add) }, "Bulk Scan"),
34+
m(Button, { size: "small", icon: m(Add) }, "Scan"),
35+
m(Button, { size: "small", icon: m(Add) }, "Add"),
4036
]),
41-
m("div", { class: "flex flex-col py-4 gap-y-2 md:gap-y-4" }, [
42-
bookshelves.map((bookshelf) => {
43-
return m(BookshelfEntry, { entry: bookshelf }, []);
44-
}),
37+
];
38+
39+
const fab = m(Fab, { kind: "tertiary", icon: m(Add), onclick: toggle }, [fabOpen ? fabButtons : null]);
40+
41+
return m(Layout, [
42+
m("div", { class: "bg-zinc-100 px-4" }, [
43+
fab,
44+
m("div", { class: "flex flex-col py-4 gap-y-2 md:gap-y-4" }, [
45+
bookshelves.map((bookshelf) => {
46+
return m(BookshelfEntry, { entry: bookshelf }, []);
47+
}),
48+
]),
4549
]),
4650
]);
47-
},
48-
err: (err) => {
49-
return null;
50-
},
51-
});
51+
})
52+
.unwrapOr(null);
5253
},
5354
};
5455
};

src/shared/layout.ts src/pages/layout.ts

+12-22
Original file line numberDiff line numberDiff line change
@@ -10,34 +10,24 @@ import { Settings } from "@/mithril-material-3/icon/settings";
1010
export const Layout: m.ClosureComponent = (_vnode) => {
1111
return {
1212
view: (vnode) => {
13+
const create = (href: string, icon: m.ClosureComponent, label: string) => ({
14+
href: href,
15+
icon: icon,
16+
label: label,
17+
});
18+
1319
const items = [
14-
{
15-
href: "bookshelves",
16-
icon: Shelves,
17-
label: "Bookshelves",
18-
},
19-
{
20-
href: "tags",
21-
icon: Label,
22-
label: "Tags",
23-
},
24-
{
25-
href: "wishlist",
26-
icon: ShoppingMode,
27-
label: "Wishlist",
28-
},
29-
{
30-
href: "/settings",
31-
icon: Settings,
32-
label: "Settings",
33-
},
20+
create("/bookshelves", Shelves, "Bookshelves"),
21+
create("/tags", Label, "Tags"),
22+
create("/wishlist", ShoppingMode, "Wishlist"),
23+
create("/settings", Settings, "Settings"),
3424
];
3525

36-
return m.fragment({}, [
26+
return [
3727
m(Toast.Drawer),
3828
m("div", { class: "flex-1 overflow-y-scroll" }, vnode.children),
3929
m(Nav, { items: items }, []),
40-
]);
30+
];
4131
},
4232
};
4333
};

src/pages/settings/index.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@ import m from "@/mithril";
33
import { Label } from "../../components/base/label";
44
import { Select, Option } from "../../components/base/select";
55

6+
import Layout from "pages/layout";
7+
68
export const Settings: m.ClosureComponent = () => {
79
return {
810
view: (_vnode) => {
9-
return [
11+
return m(Layout, [
1012
m("h2", { class: "text-2xl m-2" }, "Camera"),
1113
m("div", { class: "p-2 mx-2 rounded-md shadow-sm" }, [
1214
m(Label, { for: "settings-camera-selection" }, "Camera Selection"),
@@ -29,7 +31,7 @@ export const Settings: m.ClosureComponent = () => {
2931
m(Option, "Hipchat"),
3032
]),
3133
]),
32-
];
34+
]);
3335
},
3436
};
3537
};

src/pages/setup/index.ts

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import m from "@/mithril";
2+
3+
export const Setup: m.ClosureComponent = (vnode) => {
4+
return {
5+
view: (vnode) => {
6+
return m("h1", "Tags!");
7+
},
8+
};
9+
};

src/pages/tags/index.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import m from "@/mithril";
22

3+
import Layout from "pages/layout";
4+
35
export const Tags: m.ClosureComponent = (vnode) => {
46
return {
57
view: (vnode) => {
6-
return m("h1", "Tags!");
8+
return m(Layout, [m("h1", "Tags!")]);
79
},
810
};
911
};

src/pages/wishlist/index.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@ import m from "@/mithril";
22
import { Fab } from "@/mithril-material-3/fab";
33
import { Add } from "@/mithril-material-3/icon/add";
44

5+
import Layout from "pages/layout";
6+
57
export const Wishlist: m.ClosureComponent = (vnode) => {
68
return {
79
view: (vnode) => {
8-
return [
10+
return m(Layout, [
911
m(Fab, { kind: "tertiary", icon: m(Add) }, []),
1012
m("h1", "Wishlist!"),
11-
];
13+
]);
1214
},
1315
};
1416
};

src/shared/state.ts src/state.ts

+9-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import type { RecursivePartial } from "./utils";
2+
13
interface State {
24
settings: Settings;
35
}
@@ -22,7 +24,7 @@ interface SearchSettings {
2224
worldcatToken: string | null;
2325
}
2426

25-
export const state: State = {
27+
const state: State = {
2628
settings: {
2729
camera: {
2830
list: [],
@@ -39,3 +41,9 @@ export const state: State = {
3941
export const loadState = () => {};
4042

4143
export const saveState = () => {};
44+
45+
export const getState = (): State => {
46+
return state;
47+
};
48+
49+
export const setState = (state: RecursivePartial<State>) => {};

src/utils/camera.ts

+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import * as ZXing from "@/zxing";
2+
3+
import { wrapAsyncError } from ".";
4+
5+
const codeReader = new ZXing.BrowserMultiFormatReader();
6+
7+
export const getDevices = () => {
8+
return wrapAsyncError("unable to get list of cameras", async () => await codeReader.listVideoInputDevices());
9+
};
10+
11+
export const getName = (label: string) => {
12+
const clean = label.replace(/\s*\([0-9a-f]+(:[0-9a-f]+)?\)\s*$/, "");
13+
14+
return clean || label || null;
15+
};
16+
17+
const ensureAccess = async () => {
18+
return await wrapAsyncError("unable to ensure camera access", async () => {
19+
const access = await navigator.mediaDevices.getUserMedia({ video: true });
20+
for (const stream of access.getVideoTracks()) {
21+
stream.stop();
22+
}
23+
24+
// https://stackoverflow.com/a/69468263
25+
// Firefox requires getting media devices after stopping all streams
26+
await navigator.mediaDevices.getUserMedia({ video: true });
27+
});
28+
};
29+
30+
export const stopAll = async () => {
31+
const access = await navigator.mediaDevices.getUserMedia({ video: true });
32+
for (const stream of access.getVideoTracks()) {
33+
stream.stop();
34+
}
35+
};
36+
37+
export class Camera {
38+
id: string;
39+
name: string | null;
40+
41+
constructor(id: string, name: string | null) {
42+
this.id = id;
43+
this.name = name;
44+
}
45+
46+
public static async requestPermission() {
47+
const tracks = await navigator.mediaDevices.getUserMedia({ audio: false, video: { facingMode: "environment" } });
48+
for (const track of tracks.getTracks()) {
49+
track.stop();
50+
}
51+
}
52+
53+
public static async getCameras() {
54+
await ensureAccess();
55+
56+
return (await navigator.mediaDevices.enumerateDevices())
57+
.filter((d) => d.kind === "videoinput")
58+
.map((d) => new Camera(d.deviceId, getName(d.label)));
59+
}
60+
61+
public async start(camera: Camera) {
62+
return await wrapAsyncError("unable to start selected camera", async () => {
63+
return await navigator.mediaDevices.getUserMedia({
64+
audio: false,
65+
video: {
66+
deviceId: camera.id,
67+
},
68+
});
69+
});
70+
}
71+
72+
public async stop(stream: MediaStream) {
73+
for (const track of stream.getVideoTracks()) {
74+
track.stop();
75+
}
76+
}
77+
}
78+
79+
export class Scanner {}

0 commit comments

Comments
 (0)