Skip to content

Commit

Permalink
feat(ui): Add books list
Browse files Browse the repository at this point in the history
  • Loading branch information
dshemin committed Oct 28, 2023
1 parent 7ea2c63 commit bd4c4f3
Show file tree
Hide file tree
Showing 16 changed files with 676 additions and 29 deletions.
1 change: 0 additions & 1 deletion docs/entities.plantuml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ entity Tag {
note top
Allows to group books by some feature like: genre, author, and etc.
end note

entity Book {
* id : UUID
--
Expand Down
Binary file added ui/data/sample.pdf
Binary file not shown.
2 changes: 2 additions & 0 deletions ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"react-dom": "^18.2.0",
"react-i18next": "^13.3.1",
"react-oidc-context": "^2.3.0",
"react-pdf": "^7.5.1",
"react-router-dom": "^6.16.0"
},
"devDependencies": {
Expand All @@ -40,6 +41,7 @@
"miragejs": "^0.1.47",
"typescript": "^5.0.2",
"vite": "^4.4.5",
"vite-plugin-static-copy": "^0.17.0",
"vite-tsconfig-paths": "^4.2.1"
}
}
2 changes: 2 additions & 0 deletions ui/src/entities/book/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * as bookModel from "./model";
export * from "./ui";
12 changes: 12 additions & 0 deletions ui/src/entities/book/model/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { combine, createEffect, createStore } from "effector";
import api from "shared/api";
import type { Book } from "shared/api/book";

export const getBookListFx = createEffect((cursor?: string) => api.book.list(cursor));
export const $getBookListFxLoading = getBookListFx.pending;

export const $books = createStore<Book[]>([])
.on(getBookListFx.doneData, (_, book) => book.data);

export const $booksList = combine($books, (books) => Object.values(books));
export const $booksListEmpty = $booksList.map((list) => list.length === 0);
12 changes: 12 additions & 0 deletions ui/src/entities/book/ui/book-row/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Row } from "antd";
import { Book } from "shared/api/book";

export interface BookRowProps {
data: Book
}

export const BookRow: React.FC<BookRowProps> = ({ data }) => (
<Row>
{data.title}
</Row>
);
1 change: 1 addition & 0 deletions ui/src/entities/book/ui/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./book-row";
9 changes: 5 additions & 4 deletions ui/src/entities/storage/ui/storage-row/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ export interface StorageRowProps {
data: Storage
}

export const StorageRow: React.FC<StorageRowProps> = ({ data }) => <Row>
{data.name}
</Row>;

export const StorageRow: React.FC<StorageRowProps> = ({ data }) => (
<Row>
{data.name}
</Row>
);
56 changes: 55 additions & 1 deletion ui/src/pages/main/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,61 @@
import { Layout } from "antd";
import { list, variant } from "@effector/reflect";
import { Col, Empty, Layout, Spin } from "antd";
import { combine } from "effector";
import { bookModel } from "entities/book";
import { BookRow } from "entities/book/ui/book-row";
import { Book } from "shared/api/book";

const MainPage: React.FC = () => (
<Layout>
<BooksList/>
</Layout>
);

const Item: React.FC<{ book: Book }> = ({ book }) => (
<Col>
<BookRow
data={book}
/>
</Col>
);

const TasksList = list({
view: Item,
source: bookModel.$booksList,
mapItem: {
book: (book) => book,
},
getKey: (book) => book.id,
});

// eslint-disable-next-line effector/enforce-store-naming-convention
const BooksList = variant({
source: combine(
{
isLoading: bookModel.$getBookListFxLoading,
isEmpty: bookModel.$booksListEmpty,
},
({ isLoading, isEmpty }) => {
switch (true) {
case isLoading:
return "loading";

case isEmpty:
return "empty";
}

return "ready";
},
),
cases: {
loading: () => <Spin size="large" />,
empty: () => <Empty description="No books found" />,
ready: TasksList,
},
hooks: {
// eslint-disable-next-line @typescript-eslint/no-empty-function
mounted: bookModel.getBookListFx.prepend(() => { }),
},
});

export default MainPage;
12 changes: 7 additions & 5 deletions ui/src/pages/storage-list/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ const StorageListPage: React.FC = () => (
</Layout>
);

const Item: React.FC<{ storage: Storage }> = ({ storage }) => <Col>
<StorageRow
data={storage}
/>
</Col>;
const Item: React.FC<{ storage: Storage }> = ({ storage }) => (
<Col>
<StorageRow
data={storage}
/>
</Col>
);

const TasksList = list({
view: Item,
Expand Down
49 changes: 49 additions & 0 deletions ui/src/shared/api/book.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import AbstractAPI, { Query } from "./abstract";
import { PaginatedData } from "./types";

class API extends AbstractAPI {
public async list(cursor?: string): Promise<PaginatedData<Book>> {
const query: Query = {};

if (cursor) {
query["cursor"] = cursor;
}

const resp = await this.request("GET", "/api/books", query);
const json = await resp.json();

return json;
}

public async get(id: string): Promise<Book> {
const resp = await this.request("GET", `/api/books/${id}`);
const json = await resp.json();

return json;
}
}

export interface Book {
id: string,
title: string,
uri: string,
tags: string[],
highlights: Highlight[],
bookmarks: Bookmark[],
}

export interface Highlight {
page: number,
start: number,
end: number,
title: string,
note: string,
}

export interface Bookmark {
page: number,
title: string,
note: string,
}

export default API;
3 changes: 3 additions & 0 deletions ui/src/shared/api/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import BookAPI from "./book";
import StorageAPI from "./storage";
import config from "shared/config";

class API {
public storage: StorageAPI;
public book: BookAPI;

public constructor(baseURL: string) {
this.storage = new StorageAPI(baseURL);
this.book = new BookAPI(baseURL);
}
}

Expand Down
33 changes: 33 additions & 0 deletions ui/src/shared/mock/book/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { AppServer } from "shared/mock/types";
import { Model } from "miragejs";
import { ModelDefinition } from "miragejs/-types";
import { Serializer } from "shared/mock/utils";
import { Book } from "shared/api/book";

export const BookModel: ModelDefinition<Book> = Model.extend({});

export const bookEndpoints = (server: AppServer): void => {
server.get("/books", (schema) => schema.all("book"));
server.get("/books/:id", (schema, request) => {
const attrs = JSON.parse(request.requestBody);

return schema.create("book", attrs);
});
};

export const bookSeed = (server: AppServer): void => {
server.create("book", {
id: crypto.randomUUID(),
title: "Book1",
uri: "https://localhost:5173/sample.pdf",
tags: [
"tag1",
"tag2",
"tag3",
],
highlights: [],
bookmarks: [],
});
};

export const bookSerializer = Serializer;
13 changes: 11 additions & 2 deletions ui/src/shared/mock/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,27 @@ import { Registry, Server } from "miragejs";
import { StorageModel, storageEndpoints, storageSeed, storageSerializer } from "./storage";
import { AnyFactories } from "miragejs/-types";
import Schema from "miragejs/orm/schema";
import { BookModel, bookEndpoints, bookSeed, bookSerializer } from "./book";

export const models = {
storage: StorageModel,
book: BookModel,
};

export const serializers = {
storage: storageSerializer,
book: bookSerializer,
};

export const endpoints = [storageEndpoints];
export const endpoints = [
storageEndpoints,
bookEndpoints,
];

export const seeds = [storageSeed];
export const seeds = [
storageSeed,
bookSeed,
];

export type AppRegistry = Registry<typeof models, AnyFactories>;
export type AppSchema = Schema<AppRegistry>;
Expand Down
29 changes: 24 additions & 5 deletions ui/vite.config.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,31 @@
import { defineConfig } from "vite";
import { PluginOption, defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import tsconfigPaths from "vite-tsconfig-paths";
import { viteStaticCopy } from "vite-plugin-static-copy";

// https://vitejs.dev/config/
export default defineConfig({
plugins: [
export default defineConfig(({ mode }) => {
let plugins: PluginOption[] = [
react(),
tsconfigPaths(),
],
envPrefix: "BS_",
];

if (mode === "development") {
plugins = [
...plugins,
viteStaticCopy({
targets: [
{
src: "data/sample.pdf",
dest: "/",
},
],
}),
];
}

return {
plugins,
envPrefix: "BS_",
};
});
Loading

0 comments on commit bd4c4f3

Please sign in to comment.