Add settings, cards list, card preview views #6
9
public/icons/file-plus.svg
Normal file
9
public/icons/file-plus.svg
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-file-plus" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||||
|
<path d="M14 3v4a1 1 0 0 0 1 1h4" />
|
||||||
|
<path d="M17 21h-10a2 2 0 0 1 -2 -2v-14a2 2 0 0 1 2 -2h7l5 5v11a2 2 0 0 1 -2 2z" />
|
||||||
|
<line x1="12" y1="11" x2="12" y2="17" />
|
||||||
|
<line x1="9" y1="14" x2="15" y2="14" />
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
|
After Width: | Height: | Size: 502 B |
11
public/icons/list.svg
Normal file
11
public/icons/list.svg
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-list" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||||
|
<line x1="9" y1="6" x2="20" y2="6" />
|
||||||
|
<line x1="9" y1="12" x2="20" y2="12" />
|
||||||
|
<line x1="9" y1="18" x2="20" y2="18" />
|
||||||
|
<line x1="5" y1="6" x2="5" y2="6.01" />
|
||||||
|
<line x1="5" y1="12" x2="5" y2="12.01" />
|
||||||
|
<line x1="5" y1="18" x2="5" y2="18.01" />
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
|
After Width: | Height: | Size: 541 B |
@ -6,7 +6,10 @@ import {
|
|||||||
ExpressionSetListView,
|
ExpressionSetListView,
|
||||||
} from "../../views";
|
} from "../../views";
|
||||||
import { AddExpressionView } from "../../views/AddExpressionView";
|
import { AddExpressionView } from "../../views/AddExpressionView";
|
||||||
|
import { ExpressionCardListView } from "../../views/ExpressionCardListView";
|
||||||
|
import { ExpressionCardView } from "../../views/ExpressionCardView";
|
||||||
import { HomeView } from "../../views/HomeView";
|
import { HomeView } from "../../views/HomeView";
|
||||||
|
import { SettingsView } from "../../views/SettingsView";
|
||||||
|
|
||||||
export function Page() {
|
export function Page() {
|
||||||
const { route } = useContext(AppRouting);
|
const { route } = useContext(AppRouting);
|
||||||
@ -22,7 +25,12 @@ export function Page() {
|
|||||||
case AppPath.ExpressionSetsPractice:
|
case AppPath.ExpressionSetsPractice:
|
||||||
return <ExpressionPracticeView />;
|
return <ExpressionPracticeView />;
|
||||||
case AppPath.Settings:
|
case AppPath.Settings:
|
||||||
// TODO this should split onto more views
|
return <SettingsView />;
|
||||||
|
case AppPath.CreateCards:
|
||||||
return <AddExpressionView />;
|
return <AddExpressionView />;
|
||||||
|
case AppPath.CardsList:
|
||||||
|
return <ExpressionCardListView />;
|
||||||
|
case AppPath.CardView:
|
||||||
|
return <ExpressionCardView />;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
26
src/components/SettingsSection/SettingsSectionLink.tsx
Normal file
26
src/components/SettingsSection/SettingsSectionLink.tsx
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { useContext } from "react";
|
||||||
|
import { AppPath, AppRouting } from "../../model/routing";
|
||||||
|
|
||||||
|
export interface SettingsSectionLinkProps {
|
||||||
|
text: string;
|
||||||
|
iconUrl?: string;
|
||||||
|
page: AppPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SettingsSectionLink({
|
||||||
|
text,
|
||||||
|
iconUrl,
|
||||||
|
page,
|
||||||
|
}: SettingsSectionLinkProps) {
|
||||||
|
const { setRoute } = useContext(AppRouting);
|
||||||
|
return (
|
||||||
|
<li onClick={() => setRoute({ path: page })}>
|
||||||
|
<div className="content-card">
|
||||||
|
<div className="content-row">
|
||||||
|
{iconUrl && <img src={iconUrl} width="24" height="24" alt="" />}
|
||||||
|
<h2 className="text-title padding-small">{text}</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
}
|
1
src/components/SettingsSection/index.ts
Normal file
1
src/components/SettingsSection/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from "./SettingsSectionLink";
|
@ -1,5 +1,8 @@
|
|||||||
export * from "./useExpressionCategories";
|
export * from "./useExpressionCategories";
|
||||||
|
export * from "./useExpressionById";
|
||||||
export * from "./useExpressionFilterQueryIds";
|
export * from "./useExpressionFilterQueryIds";
|
||||||
|
export * from "./useExpressionQueryId";
|
||||||
|
export * from "./useExpressions";
|
||||||
export * from "./useExpressionSet";
|
export * from "./useExpressionSet";
|
||||||
export * from "./useExpressionSetQueryId";
|
export * from "./useExpressionSetQueryId";
|
||||||
export * from "./useExpressionSets";
|
export * from "./useExpressionSets";
|
||||||
|
9
src/hooks/useExpressionById.ts
Normal file
9
src/hooks/useExpressionById.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { useLiveQuery } from "dexie-react-hooks";
|
||||||
|
import { database, IndexedExpression } from "../model";
|
||||||
|
|
||||||
|
export function useExpressionById(id?: number): IndexedExpression | undefined {
|
||||||
|
return useLiveQuery(() => {
|
||||||
|
if (id === undefined) return undefined;
|
||||||
|
return database.expressions.where({ id }).first();
|
||||||
|
}, [id]);
|
||||||
|
}
|
7
src/hooks/useExpressionQueryId.ts
Normal file
7
src/hooks/useExpressionQueryId.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { useContext } from "react";
|
||||||
|
import { AppRouting } from "../model/routing";
|
||||||
|
|
||||||
|
export function useExpressionQueryId(): number | undefined {
|
||||||
|
const { route } = useContext(AppRouting);
|
||||||
|
return route.options?.expression_card_id;
|
||||||
|
}
|
9
src/hooks/useExpressions.ts
Normal file
9
src/hooks/useExpressions.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { useLiveQuery } from "dexie-react-hooks";
|
||||||
|
import { database } from "../model";
|
||||||
|
|
||||||
|
export function useExpressions() {
|
||||||
|
return useLiveQuery(
|
||||||
|
() => database.expressions.toArray(),
|
||||||
|
[database.expressions]
|
||||||
|
);
|
||||||
|
}
|
@ -7,10 +7,16 @@ export enum AppPath {
|
|||||||
ExpressionSetsPractice = "expression-sets/practice",
|
ExpressionSetsPractice = "expression-sets/practice",
|
||||||
ExpressionSetsDetails = "expression-sets/details",
|
ExpressionSetsDetails = "expression-sets/details",
|
||||||
Settings = "settings",
|
Settings = "settings",
|
||||||
CreateCards = "settings", // TODO split from settings
|
CardsList = "settings/cards",
|
||||||
|
CardView = "settings/view-card",
|
||||||
|
CreateCards = "settings/create-cards",
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RouteOptions {
|
export interface RouteOptions {
|
||||||
|
// Used in cards view
|
||||||
|
expression_card_id?: number;
|
||||||
|
|
||||||
|
// Used in practice view
|
||||||
expression_set_id?: number;
|
expression_set_id?: number;
|
||||||
expression_id_filters?: number[];
|
expression_id_filters?: number[];
|
||||||
}
|
}
|
||||||
|
50
src/views/ExpressionCardListView/ExpressionCardListView.tsx
Normal file
50
src/views/ExpressionCardListView/ExpressionCardListView.tsx
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import { useContext, useMemo } from "react";
|
||||||
|
import { ExpressionCard } from "../../components";
|
||||||
|
import { useExpressions } from "../../hooks";
|
||||||
|
import { IndexedExpression } from "../../model";
|
||||||
|
import { AppPath, AppRouting } from "../../model/routing";
|
||||||
|
import { ErrorView } from "../ErrorView";
|
||||||
|
|
||||||
|
export function ExpressionCardListView() {
|
||||||
|
const { setRoute } = useContext(AppRouting);
|
||||||
|
const expressions = useExpressions();
|
||||||
|
const expression_list = useMemo(
|
||||||
|
() => (expressions || []).concat().sort(sort_function),
|
||||||
|
[expressions]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!expressions) return null; // LOADING
|
||||||
|
if (!expressions.length) {
|
||||||
|
return <ErrorView message="No expression cards yet" />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="page-with-padding content-list scroll">
|
||||||
|
<ul className="content-list">
|
||||||
|
{expression_list.map(({ prompt, id, description }) => (
|
||||||
|
<li
|
||||||
|
key={id}
|
||||||
|
onClick={() =>
|
||||||
|
setRoute({
|
||||||
|
path: AppPath.CardView,
|
||||||
|
options: { expression_card_id: id },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<ExpressionCard
|
||||||
|
prompt={prompt}
|
||||||
|
categories={[]}
|
||||||
|
description={description}
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function sort_function(a: IndexedExpression, b: IndexedExpression) {
|
||||||
|
if (a.prompt < b.prompt) return -1;
|
||||||
|
if (a.prompt > b.prompt) return 1;
|
||||||
|
return 0;
|
||||||
|
}
|
1
src/views/ExpressionCardListView/index.ts
Normal file
1
src/views/ExpressionCardListView/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from "./ExpressionCardListView";
|
20
src/views/ExpressionCardView/ExpressionCardView.tsx
Normal file
20
src/views/ExpressionCardView/ExpressionCardView.tsx
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { ExpressionCard } from "../../components";
|
||||||
|
import { ExpressionDescription } from "../../components/ExpressionDescription";
|
||||||
|
import { useExpressionById, useExpressionQueryId } from "../../hooks";
|
||||||
|
import { ErrorView } from "../ErrorView";
|
||||||
|
|
||||||
|
export function ExpressionCardView() {
|
||||||
|
const expression_id = useExpressionQueryId();
|
||||||
|
const expression = useExpressionById(expression_id);
|
||||||
|
if (!expression) return <ErrorView message="No valid card selected" />;
|
||||||
|
return (
|
||||||
|
<div className="page-with-padding content-list scroll">
|
||||||
|
<ExpressionCard
|
||||||
|
prompt={expression.prompt}
|
||||||
|
categories={[]}
|
||||||
|
description={<ExpressionDescription expression={expression} />}
|
||||||
|
show_description
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
1
src/views/ExpressionCardView/index.ts
Normal file
1
src/views/ExpressionCardView/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from "./ExpressionCardView";
|
21
src/views/SettingsView/SettingsView.tsx
Normal file
21
src/views/SettingsView/SettingsView.tsx
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { SettingsSectionLink } from "../../components/SettingsSection";
|
||||||
|
import { AppPath } from "../../model/routing";
|
||||||
|
|
||||||
|
export function SettingsView() {
|
||||||
|
return (
|
||||||
|
<div className="page-with-padding content-list scroll">
|
||||||
|
<ul className="content-list">
|
||||||
|
<SettingsSectionLink
|
||||||
|
text="Create cards"
|
||||||
|
page={AppPath.CreateCards}
|
||||||
|
iconUrl="/icons/file-plus.svg"
|
||||||
|
/>
|
||||||
|
<SettingsSectionLink
|
||||||
|
text="Cards list"
|
||||||
|
page={AppPath.CardsList}
|
||||||
|
iconUrl="/icons/list.svg"
|
||||||
|
/>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
1
src/views/SettingsView/index.ts
Normal file
1
src/views/SettingsView/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from "./SettingsView";
|
Loading…
Reference in New Issue
Block a user