mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-24 21:48:39 +01:00
Library console in typescript (#90221)
## About The Pull Request Huge jsx UI converted over to typescript, removed uselocalstate, and some tiny qol changes to boot. This is not a rewrite, it should function exactly the same <details> <summary>photos</summary> Inventory screen is split into sections so you shouldn't lose the search controls while scrolling  Native tabs for poster selection, candy striped  Modal gets a bit more padding  </details> ## Why It's Good For The Game Big win for typescript conversions. The file is made more digestible as well ## Changelog 🆑 fix: The library console has been refactored. Report any issues in the github! /🆑
This commit is contained in:
@@ -2,7 +2,8 @@ import { Box, NoticeBox, Stack } from 'tgui-core/components';
|
||||
|
||||
import { useBackend } from '../../backend';
|
||||
import { Window } from '../../layouts';
|
||||
import { PageSelect, SearchAndDisplay } from '../LibraryConsole';
|
||||
import { PageSelect } from '../LibraryConsole/components/PageSelect';
|
||||
import { SearchAndDisplay } from '../LibraryConsole/components/Search';
|
||||
import { LibraryAdminData } from './types';
|
||||
|
||||
export function BookListing(props) {
|
||||
|
||||
@@ -1,934 +0,0 @@
|
||||
import { map, sortBy } from 'common/collections';
|
||||
import { useState } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Dropdown,
|
||||
Flex,
|
||||
Input,
|
||||
LabeledList,
|
||||
Modal,
|
||||
NoticeBox,
|
||||
NumberInput,
|
||||
Section,
|
||||
Stack,
|
||||
Table,
|
||||
} from 'tgui-core/components';
|
||||
import { classes } from 'tgui-core/react';
|
||||
|
||||
import { useBackend, useLocalState } from '../backend';
|
||||
import { Window } from '../layouts';
|
||||
import { sanitizeText } from '../sanitize';
|
||||
|
||||
export const LibraryConsole = (props) => {
|
||||
const { act, data } = useBackend();
|
||||
const { display_lore } = data;
|
||||
return (
|
||||
<Window
|
||||
theme={display_lore ? 'spookyconsole' : ''}
|
||||
title="Library Terminal"
|
||||
width={880}
|
||||
height={520}
|
||||
>
|
||||
<Window.Content m="0">
|
||||
<Flex height="100%">
|
||||
<Flex.Item>
|
||||
<PopoutMenu />
|
||||
</Flex.Item>
|
||||
<Flex.Item grow position="relative" pl={1}>
|
||||
<PageDisplay />
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
|
||||
export const PopoutMenu = (props) => {
|
||||
const { act, data } = useBackend();
|
||||
const { screen_state, show_dropdown, display_lore } = data;
|
||||
return (
|
||||
<Section fill maxWidth={show_dropdown ? '150px' : '36px'}>
|
||||
<Stack vertical fill>
|
||||
<Stack.Item>
|
||||
<Button
|
||||
fluid
|
||||
fontSize="13px"
|
||||
onClick={() => act('toggle_dropdown')}
|
||||
icon={show_dropdown === 1 ? 'chevron-left' : 'chevron-right'}
|
||||
tooltip={!show_dropdown && 'Expand'}
|
||||
content={!!show_dropdown && 'Collapse'}
|
||||
/>
|
||||
</Stack.Item>
|
||||
<PopoutEntry id={1} icon="list" text="Inventory" />
|
||||
<PopoutEntry id={2} icon="calendar" text="Checkout" />
|
||||
<PopoutEntry id={3} icon="server" text="Archive" />
|
||||
<PopoutEntry id={4} icon="upload" text="Upload" />
|
||||
<PopoutEntry id={5} icon="print" text="Print" />
|
||||
{!!display_lore && (
|
||||
<PopoutEntry
|
||||
id={6}
|
||||
icon="question"
|
||||
text={screen_state === 6 ? 'Gur Fbeprere' : 'Forbidden Lore'}
|
||||
color="black"
|
||||
font="copperplate"
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
export const PageDisplay = (props) => {
|
||||
const { act, data } = useBackend();
|
||||
const { screen_state } = data;
|
||||
/* eslint-disable indent */
|
||||
/* eslint-disable operator-linebreak */
|
||||
return screen_state === 1 ? (
|
||||
<Inventory />
|
||||
) : screen_state === 2 ? (
|
||||
<Checkout />
|
||||
) : screen_state === 3 ? (
|
||||
<Archive />
|
||||
) : screen_state === 4 ? (
|
||||
<Upload />
|
||||
) : screen_state === 5 ? (
|
||||
<Print />
|
||||
) : screen_state === 6 ? (
|
||||
<Forbidden />
|
||||
) : null;
|
||||
/* eslint-enable indent */
|
||||
/* eslint-enable operator-linebreak */
|
||||
};
|
||||
|
||||
export const Inventory = (props) => {
|
||||
const { act, data } = useBackend();
|
||||
const { inventory_page_count, inventory_page, has_inventory } = data;
|
||||
if (!has_inventory) {
|
||||
return (
|
||||
<NoticeBox>No Book Records detected. Update your inventory!</NoticeBox>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Stack vertical justify="space-between" height="100%">
|
||||
<Stack.Item grow>
|
||||
<ScrollableSection
|
||||
header="Library Inventory"
|
||||
contents={<InventoryDetails />}
|
||||
/>
|
||||
</Stack.Item>
|
||||
<Stack.Item align="center">
|
||||
<PageSelect
|
||||
minimum_page_count={1}
|
||||
page_count={inventory_page_count}
|
||||
current_page={inventory_page}
|
||||
call_on_change={(value) =>
|
||||
act('switch_inventory_page', {
|
||||
page: value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export const InventoryDetails = (props) => {
|
||||
const { act, data } = useBackend();
|
||||
const inventory = sortBy(
|
||||
map(data.inventory, (book, i) => ({
|
||||
...book,
|
||||
// Generate a unique id
|
||||
key: i,
|
||||
})),
|
||||
(book) => book.key,
|
||||
);
|
||||
return (
|
||||
<Section>
|
||||
<Table>
|
||||
<Table.Row header>
|
||||
<Table.Cell>Remove</Table.Cell>
|
||||
<Table.Cell>Title</Table.Cell>
|
||||
<Table.Cell>Author</Table.Cell>
|
||||
</Table.Row>
|
||||
{inventory.map((book) => (
|
||||
<Table.Row key={book.key}>
|
||||
<Table.Cell>
|
||||
<Button
|
||||
color="bad"
|
||||
onClick={() =>
|
||||
act('inventory_remove', {
|
||||
book_id: book.ref,
|
||||
})
|
||||
}
|
||||
icon="times"
|
||||
>
|
||||
Clear Record
|
||||
</Button>
|
||||
</Table.Cell>
|
||||
<Table.Cell>{book.title}</Table.Cell>
|
||||
<Table.Cell>{book.author}</Table.Cell>
|
||||
</Table.Row>
|
||||
))}
|
||||
</Table>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
export const Checkout = (props) => {
|
||||
const { act, data } = useBackend();
|
||||
const { checkout_page, checkout_page_count } = data;
|
||||
|
||||
const [checkoutBook, setCheckoutBook] = useLocalState('CheckoutBook', false);
|
||||
return (
|
||||
<Stack vertical height="100%" justify="space-between">
|
||||
<Stack.Item grow>
|
||||
<Stack vertical height="100%">
|
||||
<Stack.Item grow>
|
||||
<ScrollableSection
|
||||
header="Checked Out Books"
|
||||
contents={<CheckoutEntries />}
|
||||
/>
|
||||
</Stack.Item>
|
||||
<Stack.Item align="center">
|
||||
<PageSelect
|
||||
minimum_page_count={1}
|
||||
page_count={checkout_page_count}
|
||||
current_page={checkout_page}
|
||||
call_on_change={(value) =>
|
||||
act('switch_checkout_page', {
|
||||
page: value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Button
|
||||
fluid
|
||||
icon="barcode"
|
||||
content="Check-Out Book"
|
||||
fontSize="20px"
|
||||
onClick={() => setCheckoutBook(true)}
|
||||
/>
|
||||
</Stack.Item>
|
||||
{!!checkoutBook && <CheckoutModal />}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export const CheckoutEntries = (props) => {
|
||||
const { act, data } = useBackend();
|
||||
const { checkouts, has_checkout } = data;
|
||||
|
||||
if (!has_checkout) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<Table>
|
||||
<Table.Row header>
|
||||
<Table.Cell>Check-In</Table.Cell>
|
||||
<Table.Cell>Title</Table.Cell>
|
||||
<Table.Cell>Author</Table.Cell>
|
||||
<Table.Cell>Borrower</Table.Cell>
|
||||
<Table.Cell>Time Left</Table.Cell>
|
||||
</Table.Row>
|
||||
{checkouts.map((entry) => (
|
||||
<Table.Row key={entry.id}>
|
||||
<Table.Cell>
|
||||
<Button
|
||||
onClick={() =>
|
||||
act('checkin', {
|
||||
checked_out_id: entry.ref,
|
||||
})
|
||||
}
|
||||
icon="box-open"
|
||||
/>
|
||||
</Table.Cell>
|
||||
<Table.Cell>{entry.title}</Table.Cell>
|
||||
<Table.Cell>{entry.author}</Table.Cell>
|
||||
<Table.Cell>{entry.borrower}</Table.Cell>
|
||||
<Table.Cell backgroundColor={entry.overdue ? 'bad' : 'good'}>
|
||||
{entry.overdue ? 'Overdue' : entry.due_in_minutes + ' Minutes'}
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
))}
|
||||
</Table>
|
||||
);
|
||||
};
|
||||
|
||||
const CheckoutModal = (props) => {
|
||||
const { act, data } = useBackend();
|
||||
const inventory = sortBy(
|
||||
map(data.inventory, (book, i) => ({
|
||||
...book,
|
||||
// Generate a unique id
|
||||
key: i,
|
||||
})),
|
||||
(book) => book.key,
|
||||
);
|
||||
|
||||
const [checkoutBook, setCheckoutBook] = useLocalState('CheckoutBook', false);
|
||||
const [bookName, setBookName] = useState('Insert Book name...');
|
||||
const [checkoutee, setCheckoutee] = useState('Recipient');
|
||||
const [checkoutPeriod, setCheckoutPeriod] = useState(5);
|
||||
return (
|
||||
<Modal width="500px">
|
||||
<Box fontSize="20px" pb={1}>
|
||||
Are you sure you want to loan out this book?
|
||||
</Box>
|
||||
<Dropdown
|
||||
over
|
||||
mb={1.7}
|
||||
width="100%"
|
||||
selected={bookName}
|
||||
options={inventory.map((book) => book.title)}
|
||||
value={bookName}
|
||||
onSelected={(e) => setBookName(e)}
|
||||
/>
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Loan To">
|
||||
<Input
|
||||
width="160px"
|
||||
value={checkoutee}
|
||||
onChange={(e, value) => setCheckoutee(value)}
|
||||
/>
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Loan Period">
|
||||
<NumberInput
|
||||
value={checkoutPeriod}
|
||||
unit=" Minutes"
|
||||
minValue={1}
|
||||
step={1}
|
||||
stepPixelSize={10}
|
||||
onChange={(e, value) => setCheckoutPeriod(value)}
|
||||
/>
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
<Stack justify="center" align="center" pt={1}>
|
||||
<Stack.Item>
|
||||
<Button
|
||||
icon="upload"
|
||||
content="Loan Out"
|
||||
fontSize="16px"
|
||||
color="good"
|
||||
onClick={() => {
|
||||
setCheckoutBook(false);
|
||||
act('checkout', {
|
||||
book_name: bookName,
|
||||
loaned_to: checkoutee,
|
||||
checkout_time: checkoutPeriod,
|
||||
});
|
||||
}}
|
||||
lineHeight={2}
|
||||
/>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Button
|
||||
icon="times"
|
||||
content="Return"
|
||||
fontSize="16px"
|
||||
color="bad"
|
||||
onClick={() => setCheckoutBook(false)}
|
||||
lineHeight={2}
|
||||
/>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export const Archive = (props) => {
|
||||
const { act, data } = useBackend();
|
||||
const { can_connect, can_db_request, page_count, our_page } = data;
|
||||
if (!can_connect) {
|
||||
return (
|
||||
<NoticeBox>
|
||||
Unable to retrieve book listings. Please contact your system
|
||||
administrator for assistance.
|
||||
</NoticeBox>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Stack vertical justify="space-between" height="100%">
|
||||
<Stack.Item grow>
|
||||
<ScrollableSection
|
||||
header="Remote Archive"
|
||||
contents={<SearchAndDisplay />}
|
||||
/>
|
||||
</Stack.Item>
|
||||
<Stack.Item align="center">
|
||||
<PageSelect
|
||||
minimum_page_count={1}
|
||||
page_count={page_count}
|
||||
current_page={our_page}
|
||||
disabled={!can_db_request}
|
||||
call_on_change={(value) =>
|
||||
act('switch_page', {
|
||||
page: value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export const SearchAndDisplay = (props) => {
|
||||
const { act, data } = useBackend();
|
||||
const {
|
||||
search_categories = [],
|
||||
book_id,
|
||||
title,
|
||||
category,
|
||||
author,
|
||||
params_changed,
|
||||
can_db_request,
|
||||
} = data;
|
||||
const records = sortBy(
|
||||
map(data.pages, (record, i) => ({
|
||||
...record,
|
||||
// Generate a unique id
|
||||
key: i,
|
||||
})),
|
||||
(record) => record.key,
|
||||
);
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Stack justify="space-between">
|
||||
<Stack.Item pb={0.6}>
|
||||
<Stack>
|
||||
<Stack.Item>
|
||||
<Input
|
||||
value={book_id}
|
||||
placeholder={book_id === null ? 'ID' : book_id}
|
||||
mt={0.5}
|
||||
width="70px"
|
||||
onChange={(e, value) =>
|
||||
act('set_search_id', {
|
||||
id: value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Dropdown
|
||||
width="120px"
|
||||
options={search_categories}
|
||||
selected={category}
|
||||
onSelected={(value) =>
|
||||
act('set_search_category', {
|
||||
category: value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Input
|
||||
value={title}
|
||||
placeholder={title || 'Title'}
|
||||
mt={0.5}
|
||||
onChange={(e, value) =>
|
||||
act('set_search_title', {
|
||||
title: value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Input
|
||||
value={author}
|
||||
placeholder={author || 'Author'}
|
||||
mt={0.5}
|
||||
onChange={(e, value) =>
|
||||
act('set_search_author', {
|
||||
author: value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Button
|
||||
disabled={!can_db_request}
|
||||
textAlign="right"
|
||||
onClick={() => act('search')}
|
||||
color={params_changed ? 'good' : ''}
|
||||
icon="book"
|
||||
>
|
||||
Search
|
||||
</Button>
|
||||
<Button
|
||||
disabled={!can_db_request}
|
||||
textAlign="right"
|
||||
onClick={() => act('clear_data')}
|
||||
color="bad"
|
||||
icon="fire"
|
||||
>
|
||||
Reset Search
|
||||
</Button>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
<Table>
|
||||
<Table.Row>
|
||||
<Table.Cell fontSize={1.5}>#</Table.Cell>
|
||||
<Table.Cell fontSize={1.5}>Category</Table.Cell>
|
||||
<Table.Cell fontSize={1.5}>Title</Table.Cell>
|
||||
<Table.Cell fontSize={1.5}>Author</Table.Cell>
|
||||
</Table.Row>
|
||||
{records.map((record) => (
|
||||
<Table.Row key={record.key}>
|
||||
<Table.Cell>
|
||||
<Button
|
||||
onClick={() =>
|
||||
act('print_book', {
|
||||
book_id: record.id,
|
||||
})
|
||||
}
|
||||
icon="print"
|
||||
>
|
||||
{record.id}
|
||||
</Button>
|
||||
</Table.Cell>
|
||||
<Table.Cell>{record.category}</Table.Cell>
|
||||
<Table.Cell>{record.title}</Table.Cell>
|
||||
<Table.Cell>{record.author}</Table.Cell>
|
||||
</Table.Row>
|
||||
))}
|
||||
</Table>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export const Upload = (props) => {
|
||||
const { act, data } = useBackend();
|
||||
const {
|
||||
active_newscaster_cooldown,
|
||||
cache_author,
|
||||
cache_content,
|
||||
cache_title,
|
||||
can_db_request,
|
||||
has_cache,
|
||||
has_scanner,
|
||||
cooldown_string,
|
||||
} = data;
|
||||
const [uploadToDB, setUploadToDB] = useLocalState('UploadDB', false);
|
||||
if (!has_scanner) {
|
||||
return (
|
||||
<NoticeBox>
|
||||
No nearby scanner detected, construct one to continue.
|
||||
</NoticeBox>
|
||||
);
|
||||
}
|
||||
if (!has_cache) {
|
||||
return <NoticeBox>Scan in a book to upload.</NoticeBox>;
|
||||
}
|
||||
const contentHtml = {
|
||||
__html: sanitizeText(cache_content),
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<Stack vertical height="100%">
|
||||
<Stack.Item>
|
||||
<Box fontSize="20px" textAlign="center" pt="6px">
|
||||
Current Scan Cache
|
||||
</Box>
|
||||
</Stack.Item>
|
||||
<Stack.Item grow>
|
||||
<Stack vertical height="100%">
|
||||
<Stack.Item>
|
||||
<Stack justify="center">
|
||||
<Stack.Item>
|
||||
<Box pt={1} fontSize={'20px'}>
|
||||
Title:
|
||||
</Box>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Input
|
||||
fontSize="20px"
|
||||
value={cache_title}
|
||||
placeholder={cache_title || 'Title'}
|
||||
mt={0.5}
|
||||
width={22}
|
||||
onChange={(e, value) =>
|
||||
act('set_cache_title', {
|
||||
title: value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Box pt={1} fontSize="20px">
|
||||
Author:
|
||||
</Box>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Input
|
||||
fontSize="20px"
|
||||
value={cache_author}
|
||||
placeholder={cache_author || 'Author'}
|
||||
mt={0.5}
|
||||
onChange={(e, value) =>
|
||||
act('set_cache_author', {
|
||||
author: value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
<Stack.Item grow>
|
||||
<Section
|
||||
fill
|
||||
scrollable
|
||||
preserveWhitespace
|
||||
fontSize="15px"
|
||||
title="Content:"
|
||||
>
|
||||
<Box dangerouslySetInnerHTML={contentHtml} />
|
||||
</Section>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Stack>
|
||||
<Stack.Item grow>
|
||||
<Button
|
||||
disabled={!active_newscaster_cooldown}
|
||||
fluid
|
||||
tooltip={
|
||||
active_newscaster_cooldown
|
||||
? "Send your book to the station's newscaster's channel."
|
||||
: 'Please wait ' +
|
||||
cooldown_string +
|
||||
' before sending your book to the newscaster!'
|
||||
}
|
||||
tooltipPosition="top"
|
||||
icon="newspaper"
|
||||
content="Newscaster"
|
||||
fontSize="30px"
|
||||
lineHeight={2}
|
||||
textAlign="center"
|
||||
onClick={() => act('news_post')}
|
||||
/>
|
||||
</Stack.Item>
|
||||
<Stack.Item grow>
|
||||
<Button
|
||||
disabled={!can_db_request}
|
||||
fluid
|
||||
icon="server"
|
||||
content="Archive"
|
||||
fontSize="30px"
|
||||
lineHeight={2}
|
||||
textAlign="center"
|
||||
onClick={() => setUploadToDB(true)}
|
||||
/>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
{!!uploadToDB && <UploadModal />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const UploadModal = (props) => {
|
||||
const { act, data } = useBackend();
|
||||
|
||||
const { upload_categories, default_category, can_db_request } = data;
|
||||
const [uploadToDB, setUploadToDB] = useLocalState('UploadDB', false);
|
||||
const [uploadCategory, setUploadCategory] = useState('');
|
||||
|
||||
const display_category = uploadCategory || default_category;
|
||||
return (
|
||||
<Modal width="650px">
|
||||
<Box fontSize="20px" pb={2}>
|
||||
Are you sure you want to upload this book to the database?
|
||||
</Box>
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Category">
|
||||
<Dropdown
|
||||
options={upload_categories}
|
||||
selected={display_category}
|
||||
onSelected={(value) => setUploadCategory(value)}
|
||||
/>
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
<Stack justify="center" align="center" pt={2}>
|
||||
<Stack.Item>
|
||||
<Button
|
||||
disabled={!can_db_request}
|
||||
icon="upload"
|
||||
content="Upload To DB"
|
||||
fontSize="18px"
|
||||
color="good"
|
||||
onClick={() => {
|
||||
setUploadToDB(false);
|
||||
act('upload', {
|
||||
category: display_category,
|
||||
});
|
||||
}}
|
||||
lineHeight={2}
|
||||
/>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Button
|
||||
icon="times"
|
||||
content="Return"
|
||||
fontSize="18px"
|
||||
color="bad"
|
||||
onClick={() => setUploadToDB(false)}
|
||||
lineHeight={2}
|
||||
/>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export const Print = (props) => {
|
||||
const { act, data } = useBackend();
|
||||
const { deity, religion, bible_name, bible_sprite, posters } = data;
|
||||
const [selectedPoster, setSelectedPoster] = useState(posters[0]);
|
||||
|
||||
return (
|
||||
<Stack vertical fill>
|
||||
<Stack.Item grow>
|
||||
<Stack fill>
|
||||
<Stack.Item width="50%">
|
||||
<Section fill scrollable>
|
||||
{posters.map((poster) => (
|
||||
<div
|
||||
key={poster}
|
||||
title={poster}
|
||||
className={classes([
|
||||
'Button',
|
||||
'Button--fluid',
|
||||
'Button--color--transparent',
|
||||
'Button--ellipsis',
|
||||
selectedPoster &&
|
||||
poster === selectedPoster &&
|
||||
'Button--selected',
|
||||
])}
|
||||
onClick={() => setSelectedPoster(poster)}
|
||||
>
|
||||
{poster}
|
||||
</div>
|
||||
))}
|
||||
</Section>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Stack vertical height="100%">
|
||||
<Stack.Item
|
||||
textAlign="center"
|
||||
fontSize="25px"
|
||||
italic
|
||||
bold
|
||||
textColor="#0b94c4"
|
||||
>
|
||||
{bible_name}
|
||||
</Stack.Item>
|
||||
<Stack.Item textAlign="center" fontSize="22px" textColor="purple">
|
||||
In the Name of {deity}
|
||||
</Stack.Item>
|
||||
<Stack.Item textAlign="center" fontSize="22px" textColor="purple">
|
||||
For the Sake of {religion}
|
||||
</Stack.Item>
|
||||
<Stack.Item align="center">
|
||||
<Box className={classes(['bibles224x224', bible_sprite])} />
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Stack justify="space-between">
|
||||
<Stack.Item grow>
|
||||
<Button
|
||||
fluid
|
||||
icon="scroll"
|
||||
content="Poster"
|
||||
fontSize="30px"
|
||||
lineHeight={2}
|
||||
textAlign="center"
|
||||
onClick={() =>
|
||||
act('print_poster', {
|
||||
poster_name: selectedPoster,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Stack.Item>
|
||||
<Stack.Item grow>
|
||||
<Button
|
||||
fluid
|
||||
icon="cross"
|
||||
content="Bible"
|
||||
fontSize="30px"
|
||||
lineHeight={2}
|
||||
textAlign="center"
|
||||
onClick={() => act('print_bible')}
|
||||
/>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
const ForbiddenModal = (props) => {
|
||||
const { act, data } = useBackend();
|
||||
return (
|
||||
<Modal>
|
||||
<Box className="LibraryComputer__CultText" fontSize="28px">
|
||||
Accessing Forbidden Lore Vault v 1.3:
|
||||
</Box>
|
||||
<Box className="LibraryComputer__CultText" pt={0.4}>
|
||||
Are you absolutely sure you want to proceed?
|
||||
</Box>
|
||||
<Box className="LibraryComputer__CultText" pt={0.2} bold>
|
||||
EldritchRelics Inc. will take no responsibility for this choice
|
||||
</Box>
|
||||
<Stack justify="center" align="center">
|
||||
<Stack.Item>
|
||||
<Button
|
||||
className="LibraryComputer__CultText"
|
||||
fluid
|
||||
icon="check"
|
||||
content="Assent"
|
||||
color="good"
|
||||
fontSize="20px"
|
||||
onClick={() => act('lore_spawn')}
|
||||
lineHeight={2}
|
||||
/>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Button
|
||||
className="LibraryComputer__CultText"
|
||||
fluid
|
||||
icon="times"
|
||||
content="Decline"
|
||||
color="bad"
|
||||
fontSize="20px"
|
||||
onClick={() => act('lore_deny')}
|
||||
lineHeight={2}
|
||||
/>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export const Forbidden = (props) => {
|
||||
const description =
|
||||
'Abf vqrnz cebprffhf pbzchgngvbanyvf fghqrer vapvcvrzhf\nCebprffhf pbzchgngvbanyrf fhag erf nofgenpgnr dhnr pbzchgngberf vapbyhag\nHg ribyihag, cebprffhf nyvn nofgenpgn dhnr qngn znavchyner qvphaghe\nRibyhgvbavf cebprffhf qvevtvghe cre rkrzcyhz erthynr cebtenzzngvf ibpngv\nUbzvarf cebtenzzngn nq cebprffhf erpgbf rssvpvhag\nEriren fcvevghf pbzchgngbevv phz vapnagnzragvf pbavhatvzhf\nCebprffhf pbzchgngvbanyvf rfg zhyghz fvzvyvf vqrnr irarsvpnr fcvevghf\nivqrev nhg gnatv aba cbgrfg\nAba rfg rk zngrevn pbzcbfvgn\nFrq vq cynpreng vcfhz\nAba cbgrfg bcrenev bchf vagryyrpghnyr\nErfcbaqrev cbgrfg\nZhaqhz nssvprer cbgrfg rebtnaqb crphavnz nq evcnz iry cre oenppuvhz \nebobgv snoevpnaqb zbqrenaqb\nPbafvyvvf hgvzhe cebprffvohf nhthenaqv fhag fvphg vapnagnzragn irarsvpvv';
|
||||
return (
|
||||
<Box className="LibraryComputer__CultNonsense" preserveWhitespace>
|
||||
{description}
|
||||
<ForbiddenModal />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export const ScrollableSection = (props) => {
|
||||
const { header, contents } = props;
|
||||
|
||||
return (
|
||||
<Section fill scrollable>
|
||||
<Box fontSize="20px" textAlign="center">
|
||||
{header}
|
||||
</Box>
|
||||
<Box position="relative" top="10px">
|
||||
{contents}
|
||||
</Box>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
export const PopoutEntry = (props) => {
|
||||
const { act, data } = useBackend();
|
||||
const { id, text, icon, color, font } = props;
|
||||
const { show_dropdown, screen_state } = data;
|
||||
const selected_color = color || 'good';
|
||||
const deselected_color = color || '';
|
||||
|
||||
return (
|
||||
<Stack.Item>
|
||||
<Button
|
||||
fluid
|
||||
fontSize="13px"
|
||||
onClick={() =>
|
||||
act('set_screen', {
|
||||
screen_index: id,
|
||||
})
|
||||
}
|
||||
color={id === screen_state ? selected_color : deselected_color}
|
||||
fontFamily={font}
|
||||
icon={icon}
|
||||
tooltip={!show_dropdown && text}
|
||||
content={!!show_dropdown && text}
|
||||
/>
|
||||
</Stack.Item>
|
||||
);
|
||||
};
|
||||
|
||||
export const PageSelect = (props) => {
|
||||
const {
|
||||
minimum_page_count,
|
||||
page_count,
|
||||
current_page,
|
||||
call_on_change,
|
||||
disabled,
|
||||
} = props;
|
||||
|
||||
if (page_count === 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
<Stack.Item>
|
||||
<Button
|
||||
disabled={current_page === minimum_page_count || disabled}
|
||||
icon="angle-double-left"
|
||||
onClick={() => call_on_change(minimum_page_count)}
|
||||
/>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Button
|
||||
disabled={current_page === minimum_page_count || disabled}
|
||||
icon="chevron-left"
|
||||
onClick={() => call_on_change(current_page - 1)}
|
||||
/>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Input
|
||||
placeholder={current_page + '/' + page_count}
|
||||
onChange={(e, value) => {
|
||||
// I am so sorry
|
||||
if (value !== '') {
|
||||
call_on_change(value);
|
||||
e.target.value = null;
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Button
|
||||
disabled={current_page === page_count || disabled}
|
||||
icon="chevron-right"
|
||||
onClick={() => call_on_change(current_page + 1)}
|
||||
/>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Button
|
||||
disabled={current_page === page_count || disabled}
|
||||
icon="angle-double-right"
|
||||
onClick={() => call_on_change(page_count)}
|
||||
/>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,58 @@
|
||||
import { Button, Input, Stack } from 'tgui-core/components';
|
||||
|
||||
export function PageSelect(props) {
|
||||
const {
|
||||
call_on_change,
|
||||
current_page,
|
||||
disabled,
|
||||
minimum_page_count,
|
||||
page_count,
|
||||
} = props;
|
||||
|
||||
if (page_count === 1) return;
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
<Stack.Item>
|
||||
<Button
|
||||
disabled={current_page === minimum_page_count || disabled}
|
||||
icon="angle-double-left"
|
||||
onClick={() => call_on_change(minimum_page_count)}
|
||||
/>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Button
|
||||
disabled={current_page === minimum_page_count || disabled}
|
||||
icon="chevron-left"
|
||||
onClick={() => call_on_change(current_page - 1)}
|
||||
/>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Input
|
||||
placeholder={current_page + '/' + page_count}
|
||||
onChange={(e, value) => {
|
||||
// I am so sorry
|
||||
if (value !== '') {
|
||||
call_on_change(value);
|
||||
e.currentTarget.value = '';
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Button
|
||||
disabled={current_page === page_count || disabled}
|
||||
icon="chevron-right"
|
||||
onClick={() => call_on_change(current_page + 1)}
|
||||
/>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Button
|
||||
disabled={current_page === page_count || disabled}
|
||||
icon="angle-double-right"
|
||||
onClick={() => call_on_change(page_count)}
|
||||
/>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
import { useBackend } from 'tgui/backend';
|
||||
import { Button, Section, Stack } from 'tgui-core/components';
|
||||
|
||||
import { LibraryConsoleData } from '../types';
|
||||
|
||||
export function PopoutMenu(props) {
|
||||
const { act, data } = useBackend<LibraryConsoleData>();
|
||||
const { display_lore, screen_state, show_dropdown } = data;
|
||||
|
||||
return (
|
||||
<Section fill maxWidth={show_dropdown ? '150px' : '36px'}>
|
||||
<Stack vertical fill>
|
||||
<Stack.Item>
|
||||
<Button
|
||||
fluid
|
||||
fontSize="13px"
|
||||
onClick={() => act('toggle_dropdown')}
|
||||
icon={show_dropdown === 1 ? 'chevron-left' : 'chevron-right'}
|
||||
tooltip={!show_dropdown && 'Expand'}
|
||||
>
|
||||
{!!show_dropdown && 'Collapse'}
|
||||
</Button>
|
||||
</Stack.Item>
|
||||
<PopoutEntry id={1} icon="list" text="Inventory" />
|
||||
<PopoutEntry id={2} icon="calendar" text="Checkout" />
|
||||
<PopoutEntry id={3} icon="server" text="Archive" />
|
||||
<PopoutEntry id={4} icon="upload" text="Upload" />
|
||||
<PopoutEntry id={5} icon="print" text="Print" />
|
||||
{!!display_lore && (
|
||||
<PopoutEntry
|
||||
id={6}
|
||||
icon="question"
|
||||
text={screen_state === 6 ? 'Gur Fbeprere' : 'Forbidden Lore'}
|
||||
color="black"
|
||||
font="copperplate"
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
|
||||
export function PopoutEntry(props) {
|
||||
const { act, data } = useBackend<LibraryConsoleData>();
|
||||
|
||||
const { id, text, icon, color, font } = props;
|
||||
const { screen_state, show_dropdown } = data;
|
||||
|
||||
const selected_color = color || 'good';
|
||||
const deselected_color = color || '';
|
||||
|
||||
return (
|
||||
<Stack.Item>
|
||||
<Button
|
||||
fluid
|
||||
fontSize="13px"
|
||||
onClick={() =>
|
||||
act('set_screen', {
|
||||
screen_index: id,
|
||||
})
|
||||
}
|
||||
color={id === screen_state ? selected_color : deselected_color}
|
||||
fontFamily={font}
|
||||
icon={icon}
|
||||
tooltip={!show_dropdown && text}
|
||||
>
|
||||
{!!show_dropdown && text}
|
||||
</Button>
|
||||
</Stack.Item>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { ReactNode } from 'react';
|
||||
import { Section, Stack } from 'tgui-core/components';
|
||||
|
||||
type Props = {
|
||||
contents: ReactNode;
|
||||
header: ReactNode;
|
||||
};
|
||||
|
||||
export function ScrollableSection(props: Props) {
|
||||
const { contents, header } = props;
|
||||
|
||||
return (
|
||||
<Stack fill vertical>
|
||||
<Stack.Item>
|
||||
<Section fontSize="20px" textAlign="center" color="label">
|
||||
{header}
|
||||
</Section>
|
||||
</Stack.Item>
|
||||
<Stack.Item grow>
|
||||
<Section fill scrollable>
|
||||
{contents}
|
||||
</Section>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
import { useBackend } from 'tgui/backend';
|
||||
import {
|
||||
Button,
|
||||
Dropdown,
|
||||
Input,
|
||||
Section,
|
||||
Stack,
|
||||
Table,
|
||||
} from 'tgui-core/components';
|
||||
|
||||
import { LibraryConsoleData } from '../types';
|
||||
|
||||
export function SearchAndDisplay(props) {
|
||||
return (
|
||||
<Stack fill vertical>
|
||||
<Stack.Item>
|
||||
<SearchTabs />
|
||||
</Stack.Item>
|
||||
<Stack.Item grow>
|
||||
<SearchResults />
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
function SearchTabs(props) {
|
||||
const { act, data } = useBackend<LibraryConsoleData>();
|
||||
const {
|
||||
author,
|
||||
book_id,
|
||||
can_db_request,
|
||||
category,
|
||||
params_changed,
|
||||
search_categories = [],
|
||||
title,
|
||||
} = data;
|
||||
|
||||
return (
|
||||
<Section fill>
|
||||
<Stack justify="space-between">
|
||||
<Stack.Item pb={0.6}>
|
||||
<Stack>
|
||||
<Stack.Item>
|
||||
<Input
|
||||
value={book_id}
|
||||
placeholder={book_id === null ? 'ID' : book_id}
|
||||
mt={0.5}
|
||||
width="70px"
|
||||
onChange={(e, value) =>
|
||||
act('set_search_id', {
|
||||
id: value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Dropdown
|
||||
width="120px"
|
||||
options={search_categories}
|
||||
selected={category}
|
||||
onSelected={(value) =>
|
||||
act('set_search_category', {
|
||||
category: value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Input
|
||||
value={title}
|
||||
placeholder={title || 'Title'}
|
||||
mt={0.5}
|
||||
onChange={(e, value) =>
|
||||
act('set_search_title', {
|
||||
title: value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Input
|
||||
value={author}
|
||||
placeholder={author || 'Author'}
|
||||
mt={0.5}
|
||||
onChange={(e, value) =>
|
||||
act('set_search_author', {
|
||||
author: value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Button
|
||||
disabled={!can_db_request}
|
||||
textAlign="right"
|
||||
onClick={() => act('search')}
|
||||
color={params_changed ? 'good' : ''}
|
||||
icon="book"
|
||||
>
|
||||
Search
|
||||
</Button>
|
||||
<Button
|
||||
disabled={!can_db_request}
|
||||
textAlign="right"
|
||||
onClick={() => act('clear_data')}
|
||||
color="bad"
|
||||
icon="fire"
|
||||
>
|
||||
Reset Search
|
||||
</Button>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
|
||||
function SearchResults(props) {
|
||||
const { act, data } = useBackend<LibraryConsoleData>();
|
||||
const { pages } = data;
|
||||
|
||||
const sorted = pages
|
||||
.map((record, i) => ({
|
||||
...record,
|
||||
// Generate a unique id
|
||||
key: i,
|
||||
}))
|
||||
.sort((a, b) => a.key - b.key);
|
||||
|
||||
return (
|
||||
<Section fill scrollable>
|
||||
<Table>
|
||||
<Table.Row className="candystripe">
|
||||
<Table.Cell fontSize={1.5}>#</Table.Cell>
|
||||
<Table.Cell fontSize={1.5}>Category</Table.Cell>
|
||||
<Table.Cell fontSize={1.5}>Title</Table.Cell>
|
||||
<Table.Cell fontSize={1.5}>Author</Table.Cell>
|
||||
</Table.Row>
|
||||
{sorted.map((record) => (
|
||||
<Table.Row key={record.key} className="candystripe">
|
||||
<Table.Cell>
|
||||
<Button
|
||||
onClick={() =>
|
||||
act('print_book', {
|
||||
book_id: record.id,
|
||||
})
|
||||
}
|
||||
icon="print"
|
||||
>
|
||||
{record.id}
|
||||
</Button>
|
||||
</Table.Cell>
|
||||
<Table.Cell>{record.category}</Table.Cell>
|
||||
<Table.Cell>{record.title}</Table.Cell>
|
||||
<Table.Cell>{record.author}</Table.Cell>
|
||||
</Table.Row>
|
||||
))}
|
||||
</Table>
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import { useState } from 'react';
|
||||
import { Stack } from 'tgui-core/components';
|
||||
|
||||
import { useBackend } from '../../backend';
|
||||
import { Window } from '../../layouts';
|
||||
import { PopoutMenu } from './components/PopoutMenu';
|
||||
import { Archive } from './screens/Archive';
|
||||
import { Checkout } from './screens/Checkout';
|
||||
import { Forbidden } from './screens/Forbidden';
|
||||
import { Inventory } from './screens/Inventory';
|
||||
import { Print } from './screens/Print';
|
||||
import { Upload } from './screens/Upload';
|
||||
import { LibraryConsoleData } from './types';
|
||||
import { LibraryContext } from './useLibraryContext';
|
||||
|
||||
export function LibraryConsole(props) {
|
||||
const { data } = useBackend<LibraryConsoleData>();
|
||||
const { display_lore, screen_state } = data;
|
||||
|
||||
const checkoutBookState = useState(false);
|
||||
const uploadToDBState = useState(false);
|
||||
|
||||
return (
|
||||
<LibraryContext.Provider value={{ checkoutBookState, uploadToDBState }}>
|
||||
<Window
|
||||
theme={display_lore ? 'spookyconsole' : ''}
|
||||
title="Library Terminal"
|
||||
width={880}
|
||||
height={520}
|
||||
>
|
||||
<Window.Content>
|
||||
<Stack fill>
|
||||
<Stack.Item>
|
||||
<PopoutMenu />
|
||||
</Stack.Item>
|
||||
<Stack.Item grow>
|
||||
{screen_state === 1 && <Inventory />}
|
||||
{screen_state === 2 && <Checkout />}
|
||||
{screen_state === 3 && <Archive />}
|
||||
{screen_state === 4 && <Upload />}
|
||||
{screen_state === 5 && <Print />}
|
||||
{screen_state === 6 && <Forbidden />}
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Window.Content>
|
||||
</Window>
|
||||
</LibraryContext.Provider>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import { useBackend } from 'tgui/backend';
|
||||
import { NoticeBox, Stack } from 'tgui-core/components';
|
||||
|
||||
import { PageSelect } from '../components/PageSelect';
|
||||
import { SearchAndDisplay } from '../components/Search';
|
||||
import { LibraryConsoleData } from '../types';
|
||||
|
||||
export function Archive(props) {
|
||||
const { act, data } = useBackend<LibraryConsoleData>();
|
||||
const { can_connect, can_db_request, page_count, our_page } = data;
|
||||
|
||||
if (!can_connect) {
|
||||
return (
|
||||
<NoticeBox>
|
||||
Unable to retrieve book listings. Please contact your system
|
||||
administrator for assistance.
|
||||
</NoticeBox>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack vertical justify="space-between" fill>
|
||||
<Stack.Item grow>
|
||||
<SearchAndDisplay />
|
||||
</Stack.Item>
|
||||
<Stack.Item align="center">
|
||||
<PageSelect
|
||||
minimum_page_count={1}
|
||||
page_count={page_count}
|
||||
current_page={our_page}
|
||||
disabled={!can_db_request}
|
||||
call_on_change={(value) =>
|
||||
act('switch_page', {
|
||||
page: value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
import { useState } from 'react';
|
||||
import { useBackend } from 'tgui/backend';
|
||||
import {
|
||||
Button,
|
||||
Dropdown,
|
||||
Input,
|
||||
LabeledList,
|
||||
Modal,
|
||||
NoticeBox,
|
||||
NumberInput,
|
||||
Stack,
|
||||
Table,
|
||||
} from 'tgui-core/components';
|
||||
|
||||
import { PageSelect } from '../components/PageSelect';
|
||||
import { ScrollableSection } from '../components/ScrollableSection';
|
||||
import { LibraryConsoleData } from '../types';
|
||||
import { useLibraryContext } from '../useLibraryContext';
|
||||
|
||||
export function Checkout(props) {
|
||||
const { act, data } = useBackend<LibraryConsoleData>();
|
||||
const { checkout_page, checkout_page_count } = data;
|
||||
|
||||
const { checkoutBookState } = useLibraryContext();
|
||||
const [checkoutBook, setCheckoutBook] = checkoutBookState;
|
||||
|
||||
return (
|
||||
<Stack vertical height="100%" justify="space-between">
|
||||
<Stack.Item grow>
|
||||
<Stack vertical height="100%">
|
||||
<Stack.Item grow>
|
||||
<ScrollableSection
|
||||
header="Checked Out Books"
|
||||
contents={<CheckoutEntries />}
|
||||
/>
|
||||
</Stack.Item>
|
||||
<Stack.Item align="center">
|
||||
<PageSelect
|
||||
minimum_page_count={1}
|
||||
page_count={checkout_page_count}
|
||||
current_page={checkout_page}
|
||||
call_on_change={(value) =>
|
||||
act('switch_checkout_page', {
|
||||
page: value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Button
|
||||
fluid
|
||||
icon="barcode"
|
||||
fontSize="20px"
|
||||
onClick={() => setCheckoutBook(true)}
|
||||
>
|
||||
Check-Out Book
|
||||
</Button>
|
||||
</Stack.Item>
|
||||
{!!checkoutBook && <CheckoutModal />}
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
function CheckoutModal(props) {
|
||||
const { act, data } = useBackend<LibraryConsoleData>();
|
||||
|
||||
const inventory = data.inventory
|
||||
.map((book, i) => ({
|
||||
...book,
|
||||
// Generate a unique id
|
||||
key: i,
|
||||
}))
|
||||
.sort((a, b) => a.key - b.key);
|
||||
|
||||
const { checkoutBookState } = useLibraryContext();
|
||||
const [checkoutBook, setCheckoutBook] = checkoutBookState;
|
||||
|
||||
const [bookName, setBookName] = useState('Insert Book name...');
|
||||
const [checkoutee, setCheckoutee] = useState('Recipient');
|
||||
const [checkoutPeriod, setCheckoutPeriod] = useState(5);
|
||||
|
||||
return (
|
||||
<Modal width="500px" py={4}>
|
||||
<Stack fill vertical>
|
||||
<Stack.Item fontSize="20px">
|
||||
Are you sure you want to loan out this book?
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Dropdown
|
||||
over
|
||||
width="100%"
|
||||
selected={bookName}
|
||||
options={inventory.map((book) => book.title)}
|
||||
onSelected={(e) => setBookName(e)}
|
||||
/>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Loan To">
|
||||
<Input
|
||||
width="160px"
|
||||
value={checkoutee}
|
||||
onChange={(e, value) => setCheckoutee(value)}
|
||||
/>
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Loan Period">
|
||||
<NumberInput
|
||||
value={checkoutPeriod}
|
||||
unit=" Minutes"
|
||||
minValue={1}
|
||||
maxValue={1440}
|
||||
step={1}
|
||||
stepPixelSize={10}
|
||||
onChange={(value) => setCheckoutPeriod(value)}
|
||||
/>
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Stack justify="center" align="center" pt={1}>
|
||||
<Stack.Item>
|
||||
<Button
|
||||
icon="upload"
|
||||
fontSize="16px"
|
||||
color="good"
|
||||
onClick={() => {
|
||||
setCheckoutBook(false);
|
||||
act('checkout', {
|
||||
book_name: bookName,
|
||||
loaned_to: checkoutee,
|
||||
checkout_time: checkoutPeriod,
|
||||
});
|
||||
}}
|
||||
lineHeight={2}
|
||||
>
|
||||
Loan Out
|
||||
</Button>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Button
|
||||
icon="times"
|
||||
fontSize="16px"
|
||||
color="bad"
|
||||
onClick={() => setCheckoutBook(false)}
|
||||
lineHeight={2}
|
||||
>
|
||||
Return
|
||||
</Button>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export function CheckoutEntries(props) {
|
||||
const { act, data } = useBackend<LibraryConsoleData>();
|
||||
const { checkouts = [] } = data;
|
||||
|
||||
return (
|
||||
<Table>
|
||||
<Table.Row header className="candystripe">
|
||||
<Table.Cell>Title</Table.Cell>
|
||||
<Table.Cell>Author</Table.Cell>
|
||||
<Table.Cell>Borrower</Table.Cell>
|
||||
<Table.Cell>Time Left</Table.Cell>
|
||||
<Table.Cell>Check-In</Table.Cell>
|
||||
</Table.Row>
|
||||
{checkouts.length === 0 ? (
|
||||
<Table.Row>
|
||||
<Table.Cell textAlign="center" colSpan={5}>
|
||||
<NoticeBox>No books checked out.</NoticeBox>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
) : (
|
||||
checkouts.map((entry) => (
|
||||
<Table.Row key={entry.id} className="candystripe">
|
||||
<Table.Cell>{entry.title}</Table.Cell>
|
||||
<Table.Cell>{entry.author}</Table.Cell>
|
||||
<Table.Cell>{entry.borrower}</Table.Cell>
|
||||
<Table.Cell backgroundColor={entry.overdue ? 'bad' : 'good'}>
|
||||
{entry.overdue ? 'Overdue' : entry.due_in_minutes + ' Minutes'}
|
||||
</Table.Cell>
|
||||
<Table.Cell width="70px" textAlign="center">
|
||||
<Button
|
||||
mb={1}
|
||||
onClick={() =>
|
||||
act('checkin', {
|
||||
checked_out_id: entry.ref,
|
||||
})
|
||||
}
|
||||
icon="box-open"
|
||||
/>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
))
|
||||
)}
|
||||
</Table>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import { useBackend } from 'tgui/backend';
|
||||
import { Box, Button, Modal, Stack } from 'tgui-core/components';
|
||||
|
||||
const description =
|
||||
'Abf vqrnz cebprffhf pbzchgngvbanyvf fghqrer vapvcvrzhf\nCebprffhf pbzchgngvbanyrf fhag erf nofgenpgnr dhnr pbzchgngberf vapbyhag\nHg ribyihag, cebprffhf nyvn nofgenpgn dhnr qngn znavchyner qvphaghe\nRibyhgvbavf cebprffhf qvevtvghe cre rkrzcyhz erthynr cebtenzzngvf ibpngv\nUbzvarf cebtenzzngn nq cebprffhf erpgbf rssvpvhag\nEriren fcvevghf pbzchgngbevv phz vapnagnzragvf pbavhatvzhf\nCebprffhf pbzchgngvbanyvf rfg zhyghz fvzvyvf vqrnr irarsvpnr fcvevghf\nivqrev nhg gnatv aba cbgrfg\nAba rfg rk zngrevn pbzcbfvgn\nFrq vq cynpreng vcfhz\nAba cbgrfg bcrenev bchf vagryyrpghnyr\nErfcbaqrev cbgrfg\nZhaqhz nssvprer cbgrfg rebtnaqb crphavnz nq evcnz iry cre oenppuvhz \nebobgv snoevpnaqb zbqrenaqb\nPbafvyvvf hgvzhe cebprffvohf nhthenaqv fhag fvphg vapnagnzragn irarsvpvv';
|
||||
|
||||
export function Forbidden(props) {
|
||||
return (
|
||||
<Box className="LibraryComputer__CultNonsense" preserveWhitespace>
|
||||
{description}
|
||||
<ForbiddenModal />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
function ForbiddenModal(props) {
|
||||
const { act } = useBackend();
|
||||
|
||||
return (
|
||||
<Modal>
|
||||
<Box className="LibraryComputer__CultText" fontSize="28px">
|
||||
Accessing Forbidden Lore Vault v 1.3:
|
||||
</Box>
|
||||
<Box className="LibraryComputer__CultText" pt={0.4}>
|
||||
Are you absolutely sure you want to proceed?
|
||||
</Box>
|
||||
<Box className="LibraryComputer__CultText" pt={0.2} bold>
|
||||
EldritchRelics Inc. will take no responsibility for this choice
|
||||
</Box>
|
||||
<Stack justify="center" align="center">
|
||||
<Stack.Item>
|
||||
<Button
|
||||
className="LibraryComputer__CultText"
|
||||
fluid
|
||||
icon="check"
|
||||
color="good"
|
||||
fontSize="20px"
|
||||
onClick={() => act('lore_spawn')}
|
||||
lineHeight={2}
|
||||
>
|
||||
Assent
|
||||
</Button>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Button
|
||||
className="LibraryComputer__CultText"
|
||||
fluid
|
||||
icon="times"
|
||||
color="bad"
|
||||
fontSize="20px"
|
||||
onClick={() => act('lore_deny')}
|
||||
lineHeight={2}
|
||||
>
|
||||
Decline
|
||||
</Button>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
import { useBackend } from 'tgui/backend';
|
||||
import { Button, NoticeBox, Stack, Table } from 'tgui-core/components';
|
||||
|
||||
import { PageSelect } from '../components/PageSelect';
|
||||
import { ScrollableSection } from '../components/ScrollableSection';
|
||||
import { LibraryConsoleData } from '../types';
|
||||
|
||||
export function Inventory(props) {
|
||||
const { act, data } = useBackend<LibraryConsoleData>();
|
||||
const { inventory_page_count, inventory_page, has_inventory } = data;
|
||||
|
||||
if (!has_inventory) {
|
||||
return (
|
||||
<NoticeBox>No Book Records detected. Update your inventory!</NoticeBox>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack vertical justify="space-between" height="100%">
|
||||
<Stack.Item grow>
|
||||
<ScrollableSection
|
||||
header="Library Inventory"
|
||||
contents={<InventoryDetails />}
|
||||
/>
|
||||
</Stack.Item>
|
||||
<Stack.Item align="center">
|
||||
<PageSelect
|
||||
minimum_page_count={1}
|
||||
page_count={inventory_page_count}
|
||||
current_page={inventory_page}
|
||||
call_on_change={(value) =>
|
||||
act('switch_inventory_page', {
|
||||
page: value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
function InventoryDetails(props) {
|
||||
const { act, data } = useBackend<LibraryConsoleData>();
|
||||
const { inventory = [] } = data;
|
||||
|
||||
const sorted = inventory
|
||||
.map((book, i) => ({
|
||||
...book,
|
||||
// Generate a unique id
|
||||
key: i,
|
||||
}))
|
||||
.sort((a, b) => a.key - b.key);
|
||||
|
||||
return (
|
||||
<Table>
|
||||
<Table.Row header className="candystripe">
|
||||
<Table.Cell>Title</Table.Cell>
|
||||
<Table.Cell>Author</Table.Cell>
|
||||
<Table.Cell textAlign="center">Remove</Table.Cell>
|
||||
</Table.Row>
|
||||
{sorted.map((book) => (
|
||||
<Table.Row key={book.key} className="candystripe">
|
||||
<Table.Cell>{book.title}</Table.Cell>
|
||||
<Table.Cell>{book.author}</Table.Cell>
|
||||
<Table.Cell collapsing>
|
||||
<Button
|
||||
mb={1}
|
||||
color="bad"
|
||||
onClick={() =>
|
||||
act('inventory_remove', {
|
||||
book_id: book.ref,
|
||||
})
|
||||
}
|
||||
icon="times"
|
||||
>
|
||||
Clear Record
|
||||
</Button>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
))}
|
||||
</Table>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
import { useState } from 'react';
|
||||
import { useBackend } from 'tgui/backend';
|
||||
import { Box, Button, Section, Stack, Tabs } from 'tgui-core/components';
|
||||
import { classes } from 'tgui-core/react';
|
||||
|
||||
import { LibraryConsoleData } from '../types';
|
||||
|
||||
export function Print(props) {
|
||||
const { act, data } = useBackend<LibraryConsoleData>();
|
||||
const { bible_name, bible_sprite, deity, posters, religion } = data;
|
||||
|
||||
const [selectedPoster, setSelectedPoster] = useState(posters[0]);
|
||||
|
||||
return (
|
||||
<Stack vertical fill>
|
||||
<Stack.Item grow>
|
||||
<Stack fill>
|
||||
<Stack.Item width="50%">
|
||||
<Section fill scrollable>
|
||||
<Tabs vertical>
|
||||
{posters.map((poster) => {
|
||||
const selected = selectedPoster === poster;
|
||||
|
||||
return (
|
||||
<Tabs.Tab
|
||||
className="candystripe"
|
||||
selected={selected}
|
||||
color={selected && 'good'}
|
||||
key={poster}
|
||||
onClick={() => setSelectedPoster(poster)}
|
||||
>
|
||||
{poster}
|
||||
</Tabs.Tab>
|
||||
);
|
||||
})}
|
||||
</Tabs>
|
||||
</Section>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Stack vertical height="100%">
|
||||
<Stack.Item
|
||||
textAlign="center"
|
||||
fontSize="25px"
|
||||
italic
|
||||
bold
|
||||
textColor="#0b94c4"
|
||||
>
|
||||
{bible_name}
|
||||
</Stack.Item>
|
||||
<Stack.Item textAlign="center" fontSize="22px" textColor="purple">
|
||||
In the Name of {deity}
|
||||
</Stack.Item>
|
||||
<Stack.Item textAlign="center" fontSize="22px" textColor="purple">
|
||||
For the Sake of {religion}
|
||||
</Stack.Item>
|
||||
<Stack.Item align="center">
|
||||
<Box className={classes(['bibles224x224', bible_sprite])} />
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Stack justify="space-between">
|
||||
<Stack.Item grow>
|
||||
<Button
|
||||
fluid
|
||||
icon="scroll"
|
||||
fontSize="30px"
|
||||
lineHeight={2}
|
||||
textAlign="center"
|
||||
onClick={() =>
|
||||
act('print_poster', {
|
||||
poster_name: selectedPoster,
|
||||
})
|
||||
}
|
||||
>
|
||||
Poster
|
||||
</Button>
|
||||
</Stack.Item>
|
||||
<Stack.Item grow>
|
||||
<Button
|
||||
fluid
|
||||
icon="cross"
|
||||
fontSize="30px"
|
||||
lineHeight={2}
|
||||
textAlign="center"
|
||||
onClick={() => act('print_bible')}
|
||||
>
|
||||
Bible
|
||||
</Button>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
import { useState } from 'react';
|
||||
import { useBackend } from 'tgui/backend';
|
||||
import { sanitizeText } from 'tgui/sanitize';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Dropdown,
|
||||
Input,
|
||||
LabeledList,
|
||||
Modal,
|
||||
NoticeBox,
|
||||
Section,
|
||||
Stack,
|
||||
} from 'tgui-core/components';
|
||||
|
||||
import { LibraryConsoleData } from '../types';
|
||||
import { useLibraryContext } from '../useLibraryContext';
|
||||
|
||||
export function Upload(props) {
|
||||
const { act, data } = useBackend<LibraryConsoleData>();
|
||||
const {
|
||||
active_newscaster_cooldown,
|
||||
cache_author,
|
||||
cache_content,
|
||||
cache_title,
|
||||
can_db_request,
|
||||
cooldown_string,
|
||||
has_cache,
|
||||
has_scanner,
|
||||
} = data;
|
||||
|
||||
const { uploadToDBState } = useLibraryContext();
|
||||
const [uploadToDB, setUploadToDB] = uploadToDBState;
|
||||
|
||||
if (!has_scanner) {
|
||||
return (
|
||||
<NoticeBox>
|
||||
No nearby scanner detected, construct one to continue.
|
||||
</NoticeBox>
|
||||
);
|
||||
}
|
||||
|
||||
if (!has_cache) {
|
||||
return <NoticeBox>Scan in a book to upload.</NoticeBox>;
|
||||
}
|
||||
|
||||
const contentHtml = {
|
||||
__html: sanitizeText(cache_content),
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stack vertical height="100%">
|
||||
<Stack.Item>
|
||||
<Box fontSize="20px" textAlign="center" pt="6px">
|
||||
Current Scan Cache
|
||||
</Box>
|
||||
</Stack.Item>
|
||||
<Stack.Item grow>
|
||||
<Stack vertical height="100%">
|
||||
<Stack.Item>
|
||||
<Stack justify="center">
|
||||
<Stack.Item>
|
||||
<Box pt={1} fontSize={'20px'}>
|
||||
Title:
|
||||
</Box>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Input
|
||||
fontSize="20px"
|
||||
value={cache_title}
|
||||
placeholder={cache_title || 'Title'}
|
||||
mt={0.5}
|
||||
width={22}
|
||||
onChange={(e, value) =>
|
||||
act('set_cache_title', {
|
||||
title: value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Box pt={1} fontSize="20px">
|
||||
Author:
|
||||
</Box>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Input
|
||||
fontSize="20px"
|
||||
value={cache_author}
|
||||
placeholder={cache_author || 'Author'}
|
||||
mt={0.5}
|
||||
onChange={(e, value) =>
|
||||
act('set_cache_author', {
|
||||
author: value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
<Stack.Item grow>
|
||||
<Section
|
||||
fill
|
||||
scrollable
|
||||
preserveWhitespace
|
||||
fontSize="15px"
|
||||
title="Content:"
|
||||
>
|
||||
<Box dangerouslySetInnerHTML={contentHtml} />
|
||||
</Section>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Stack>
|
||||
<Stack.Item grow>
|
||||
<Button
|
||||
disabled={!active_newscaster_cooldown}
|
||||
fluid
|
||||
tooltip={
|
||||
active_newscaster_cooldown
|
||||
? "Send your book to the station's newscaster's channel."
|
||||
: 'Please wait ' +
|
||||
cooldown_string +
|
||||
' before sending your book to the newscaster!'
|
||||
}
|
||||
tooltipPosition="top"
|
||||
icon="newspaper"
|
||||
fontSize="30px"
|
||||
lineHeight={2}
|
||||
textAlign="center"
|
||||
onClick={() => act('news_post')}
|
||||
>
|
||||
Newscaster
|
||||
</Button>
|
||||
</Stack.Item>
|
||||
<Stack.Item grow>
|
||||
<Button
|
||||
disabled={!can_db_request}
|
||||
fluid
|
||||
icon="server"
|
||||
fontSize="30px"
|
||||
lineHeight={2}
|
||||
textAlign="center"
|
||||
onClick={() => setUploadToDB(true)}
|
||||
>
|
||||
Archive
|
||||
</Button>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
{!!uploadToDB && <UploadModal />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function UploadModal(props) {
|
||||
const { act, data } = useBackend<LibraryConsoleData>();
|
||||
const { upload_categories, default_category, can_db_request } = data;
|
||||
|
||||
const { uploadToDBState } = useLibraryContext();
|
||||
const [uploadToDB, setUploadToDB] = uploadToDBState;
|
||||
|
||||
const [uploadCategory, setUploadCategory] = useState('');
|
||||
|
||||
const display_category = uploadCategory || default_category;
|
||||
|
||||
return (
|
||||
<Modal width="650px">
|
||||
<Box fontSize="20px" pb={2}>
|
||||
Are you sure you want to upload this book to the database?
|
||||
</Box>
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Category">
|
||||
<Dropdown
|
||||
options={upload_categories}
|
||||
selected={display_category}
|
||||
onSelected={(value) => setUploadCategory(value)}
|
||||
/>
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
<Stack justify="center" align="center" pt={2}>
|
||||
<Stack.Item>
|
||||
<Button
|
||||
disabled={!can_db_request}
|
||||
icon="upload"
|
||||
fontSize="18px"
|
||||
color="good"
|
||||
onClick={() => {
|
||||
setUploadToDB(false);
|
||||
act('upload', {
|
||||
category: display_category,
|
||||
});
|
||||
}}
|
||||
lineHeight={2}
|
||||
>
|
||||
Upload To DB
|
||||
</Button>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Button
|
||||
icon="times"
|
||||
fontSize="18px"
|
||||
color="bad"
|
||||
onClick={() => setUploadToDB(false)}
|
||||
lineHeight={2}
|
||||
>
|
||||
Return
|
||||
</Button>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import { BooleanLike } from 'tgui-core/react';
|
||||
|
||||
type CheckoutEntry = {
|
||||
author: string;
|
||||
borrower: string;
|
||||
due_in_minutes: number;
|
||||
id: string;
|
||||
overdue: BooleanLike;
|
||||
ref: string;
|
||||
title: string;
|
||||
};
|
||||
|
||||
type InventoryEntry = {
|
||||
author: string;
|
||||
ref: string;
|
||||
title: string;
|
||||
};
|
||||
|
||||
type Page = {
|
||||
author: string;
|
||||
category: string;
|
||||
id: string;
|
||||
title: string;
|
||||
};
|
||||
|
||||
export type LibraryConsoleData = {
|
||||
active_newscaster_cooldown: BooleanLike;
|
||||
author: string;
|
||||
bible_name: string;
|
||||
bible_sprite: string;
|
||||
book_id: string;
|
||||
cache_author: string;
|
||||
cache_content: string;
|
||||
cache_title: string;
|
||||
can_connect: BooleanLike;
|
||||
can_db_request: BooleanLike;
|
||||
category: string;
|
||||
checkout_page_count: number;
|
||||
checkout_page: number;
|
||||
checkouts: CheckoutEntry[];
|
||||
cooldown_string: string;
|
||||
default_category: string;
|
||||
deity: string;
|
||||
display_lore: BooleanLike;
|
||||
has_cache: BooleanLike;
|
||||
has_checkout: BooleanLike;
|
||||
has_inventory: BooleanLike;
|
||||
has_scanner: BooleanLike;
|
||||
inventory_page_count: number;
|
||||
inventory_page: number;
|
||||
inventory: InventoryEntry[];
|
||||
our_page: number;
|
||||
page_count: number;
|
||||
pages: Page[];
|
||||
params_changed: BooleanLike;
|
||||
posters: string[];
|
||||
religion: string;
|
||||
screen_state: number;
|
||||
search_categories: string[];
|
||||
show_dropdown: BooleanLike;
|
||||
title: string;
|
||||
upload_categories: string[];
|
||||
};
|
||||
@@ -0,0 +1,15 @@
|
||||
import { createContext, Dispatch, SetStateAction, useContext } from 'react';
|
||||
|
||||
type LibraryContextType = {
|
||||
checkoutBookState: [boolean, Dispatch<SetStateAction<boolean>>];
|
||||
uploadToDBState: [boolean, Dispatch<SetStateAction<boolean>>];
|
||||
};
|
||||
|
||||
export const LibraryContext = createContext<LibraryContextType>({
|
||||
checkoutBookState: [false, () => {}],
|
||||
uploadToDBState: [false, () => {}],
|
||||
});
|
||||
|
||||
export function useLibraryContext() {
|
||||
return useContext(LibraryContext);
|
||||
}
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
|
||||
import { useBackend } from '../backend';
|
||||
import { Window } from '../layouts';
|
||||
import { PageSelect } from './LibraryConsole';
|
||||
import { PageSelect } from './LibraryConsole/components/PageSelect';
|
||||
|
||||
export const LibraryVisitor = (props) => {
|
||||
return (
|
||||
|
||||
Reference in New Issue
Block a user