@mui/material#Tabs TypeScript Examples
The following examples show how to use
@mui/material#Tabs.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: Wallet.tsx From sapio-studio with Mozilla Public License 2.0 | 6 votes |
export function Wallet(props: { bitcoin_node_manager: BitcoinNodeManager }) {
const dispatch = useDispatch();
const idx = useSelector(selectWalletTab);
const handleChange = (_: any, idx: TabIndexes) => {
dispatch(switch_wallet_tab(idx));
};
return (
<div className="Wallet">
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
<Tabs
value={idx}
onChange={handleChange}
aria-label="basic tabs example"
>
<Tab label="Send"></Tab>
<Tab label="Send History"></Tab>
<Tab label="Workspaces"></Tab>
<Tab label="Contracts"></Tab>
</Tabs>
</Box>
<Box sx={{ overflowY: 'scroll', height: '100%' }}>
<WalletSend value={0} idx={idx} {...props}></WalletSend>
<WalletHistory value={1} idx={idx} {...props}></WalletHistory>
<Workspaces value={2} idx={idx}></Workspaces>
<ContractList value={3} idx={idx}></ContractList>
</Box>
</div>
);
}
Example #2
Source File: LibraryOptions.tsx From Tachidesk-WebUI with Mozilla Public License 2.0 | 6 votes |
function Options() {
const [currentTab, setCurrentTab] = useState<number>(0);
return (
<Box>
<Tabs
key={currentTab}
value={currentTab}
variant="fullWidth"
onChange={(e, newTab) => setCurrentTab(newTab)}
indicatorColor="primary"
textColor="primary"
>
<Tab label="Filter" value={0} />
<Tab label="Sort" value={1} />
<Tab label="Display" value={2} />
</Tabs>
{filtersTab(currentTab)}
{sortsTab(currentTab)}
{dispalyTab(currentTab)}
</Box>
);
}
Example #3
Source File: index.tsx From genshin-optimizer with MIT License | 6 votes |
function TabNav({ tab }: { tab: string }) {
const { t } = useTranslation("page_character")
return <Tabs
value={tab}
variant="scrollable"
allowScrollButtonsMobile
sx={{
"& .MuiTab-root:hover": {
transition: "background-color 0.25s ease",
backgroundColor: "rgba(255,255,255,0.1)"
},
}}
>
<Tab sx={{ minWidth: "20%" }} value="overview" label={t("tabs.overview")} icon={<Person />} component={RouterLink} to="" />
<Tab sx={{ minWidth: "20%" }} value="talent" label={t("tabs.talent")} icon={<FactCheck />} component={RouterLink} to="talent" />
<Tab sx={{ minWidth: "20%" }} value="equip" label={t("tabs.equip")} icon={<Checkroom />} component={RouterLink} to="equip" />
<Tab sx={{ minWidth: "20%" }} value="teambuffs" label={t("tabs.teambuffs")} icon={<Groups />} component={RouterLink} to="teambuffs" />
<Tab sx={{ minWidth: "20%" }} value="optimize" label={t("tabs.optimize")} icon={<Calculate />} component={RouterLink} to="optimize" />
</Tabs>
}
Example #4
Source File: DepositWithdrawDialog.tsx From wrap.scrt.network with MIT License | 5 votes |
export default function DepositWithdrawDialog({
token,
secretjs,
secretAddress,
balances,
isOpen,
setIsOpen,
}: {
token: Token;
secretjs: SecretNetworkClient | null;
secretAddress: string;
balances: Map<string, string>;
isOpen: boolean;
setIsOpen: React.Dispatch<React.SetStateAction<boolean>>;
}) {
const [selectedTab, setSelectedTab] = useState<string>("deposit");
const closeDialog = () => {
setIsOpen(false);
setSelectedTab("deposit");
};
return (
<Dialog open={isOpen} fullWidth={true} onClose={closeDialog}>
<TabContext value={selectedTab}>
<Box sx={{ borderBottom: 1, borderColor: "divider" }}>
<Tabs
value={selectedTab}
variant="fullWidth"
onChange={(_event: React.SyntheticEvent, newSelectedTab: string) =>
setSelectedTab(newSelectedTab)
}
>
<Tab label="IBC Deposit" value={"deposit"} />
<Tab label="IBC Withdraw" value={"withdraw"} />
</Tabs>
</Box>
<TabPanel value={"deposit"}>
<Deposit
token={token}
secretAddress={secretAddress}
onSuccess={(txhash) => {
closeDialog();
console.log("success", txhash);
}}
onFailure={(error) => console.error(error)}
/>
</TabPanel>
<TabPanel value={"withdraw"}>
<Withdraw
token={token}
secretjs={secretjs}
secretAddress={secretAddress}
balances={balances}
onSuccess={(txhash) => {
closeDialog();
console.log("success", txhash);
}}
onFailure={(error) => console.error(error)}
/>
</TabPanel>
</TabContext>
</Dialog>
);
}
Example #5
Source File: Profiler.tsx From NekoMaid with MIT License | 5 votes |
Profiler: React.FC = () => { const plugin = usePlugin() const theme = useTheme() const globalData = useGlobalData() const [tab, setTab] = useState(0) const [key, setTKey] = useState(0) const [status, setStatus] = useState(!!globalData.profilerStarted) useEffect(() => { const off = plugin.on('profiler:status', setStatus) return () => { off() } }, []) const transitionDuration = { enter: theme.transitions.duration.enteringScreen, exit: theme.transitions.duration.leavingScreen } const Elm = components[tab] const createFab = (onClick: any, children: any, show: boolean) => <Zoom in={show} timeout={transitionDuration} style={{ transitionDelay: (show ? transitionDuration.exit : 0) + 'ms' }} unmountOnExit > <Fab color='primary' sx={{ position: 'fixed', bottom: { xs: 16, sm: 40 }, right: { xs: 16, sm: 40 }, zIndex: 3 }} onClick={onClick} >{children}</Fab> </Zoom> return <Box sx={{ minHeight: status || !tab ? '100%' : undefined }}> <Toolbar /> <Paper square variant='outlined' sx={{ margin: '0 -1px', position: 'fixed', width: 'calc(100% + 1px)', zIndex: 3 }}> <Tabs value={tab} onChange={(_, it) => setTab(it)} variant='scrollable' scrollButtons='auto'> <Tab label={lang.profiler.summary} /> <Tab label='Timings' disabled={!globalData.hasTimings} /> <Tab label={lang.profiler.plugins} /> <Tab label={lang.profiler.carbageCollection} /> <Tab label={lang.profiler.entities} /> <Tab label={lang.profiler.heap} /> <Tab label={lang.profiler.threads} /> </Tabs> </Paper> <Tabs /> {tab < 4 && !status ? <Box sx={{ textAlign: 'center', marginTop: '40vh' }}>{lang.profiler.notStarted}</Box> : Elm && <Elm key={key} />} {createFab(() => plugin.emit('profiler:status', !status), status ? <Stop /> : <PlayArrow />, tab < 4)} {createFab(() => setTKey(key + 1), <Refresh />, tab >= 4)} </Box> }
Example #6
Source File: index.tsx From genshin-optimizer with MIT License | 5 votes |
export default function PageDocumentation() {
// const { t } = useTranslation("documentation")
ReactGA.send({ hitType: "pageview", page: '/doc' })
const { params: { currentTab } } = useMatch("/doc/:currentTab") ?? { params: { currentTab: "" } }
return <CardDark sx={{ my: 1 }}>
<Grid container sx={{ px: 2, py: 1 }}>
<Grid item flexGrow={1}>
<Typography variant="h6">
Documentation
</Typography>
</Grid>
<Grid item>
<Typography variant="h6">
<SqBadge color="info">Version. 2</SqBadge>
</Typography>
</Grid>
</Grid>
<Divider />
<CardContent>
<Grid container spacing={1}>
<Grid item xs={12} md={2}>
<CardLight sx={{ height: "100%" }}>
<Tabs
orientation="vertical"
value={currentTab}
aria-label="Documentation Navigation"
sx={{ borderRight: 1, borderColor: 'divider' }}
>
<Tab label="Overview" value="" component={Link} to="" />
<Tab label={"Key naming convention"} value="KeyNaming" component={Link} to="KeyNaming" />
<Tab label={<code>StatKey</code>} value="StatKey" component={Link} to="StatKey" />
<Tab label={<code>ArtifactSetKey</code>} value="ArtifactSetKey" component={Link} to="ArtifactSetKey" />
<Tab label={<code>CharacterKey</code>} value="CharacterKey" component={Link} to="CharacterKey" />
<Tab label={<code>WeaponKey</code>} value="WeaponKey" component={Link} to="WeaponKey" />
<Tab label={<code>MaterialKey</code>} value="MaterialKey" component={Link} to="MaterialKey" />
<Tab label={"Version History"} value="VersionHistory" component={Link} to="VersionHistory" />
</Tabs>
</CardLight>
</Grid>
<Grid item xs={12} md={10}>
<CardLight sx={{ height: "100%" }}>
<CardContent>
<Suspense fallback={<Skeleton variant="rectangular" width="100%" height={600} />}>
<Routes>
<Route index element={<Overview />} />
<Route path="/VersionHistory" element={<VersionHistoryPane />} />
<Route path="/MaterialKey" element={<MaterialKeyPane />} />
<Route path="/ArtifactSetKey" element={<ArtifactSetKeyPane />} />
<Route path="/WeaponKey" element={<WeaponKeyPane />} />
<Route path="/CharacterKey" element={<CharacterKeyPane />} />
<Route path="/StatKey" element={<StatKeyPane />} />
<Route path="/KeyNaming" element={<KeyNamingPane />} />
</Routes>
</Suspense>
</CardContent>
</CardLight>
</Grid>
</Grid>
</CardContent>
</CardDark>
}
Example #7
Source File: BaseSettingsModal.tsx From rewind with MIT License | 5 votes |
export function BaseSettingsModal(props: SettingsProps) {
const { onClose, tabs, opacity, onOpacityChange, tabIndex, onTabIndexChange } = props;
const handleTabChange = (event: any, newValue: any) => {
onTabIndexChange(newValue);
};
const displayedTab = tabs[tabIndex].component;
return (
<Paper
sx={{
filter: `opacity(${opacity}%)`,
height: "100%",
display: "flex",
flexDirection: "column",
position: "relative",
}}
elevation={2}
>
<Stack sx={{ py: 1, px: 2, alignItems: "center" }} direction={"row"} gap={1}>
<SettingsIcon />
<Typography fontWeight={"bolder"}>Settings</Typography>
<Box flexGrow={1} />
<IconButton onClick={onClose}>
<Close />
</IconButton>
</Stack>
<Divider />
<Stack direction={"row"} sx={{ flexGrow: 1, overflow: "auto" }}>
{/*TODO: Holy moly, the CSS here needs to be changed a bit*/}
<Tabs
orientation="vertical"
variant="scrollable"
value={tabIndex}
onChange={handleTabChange}
sx={{ borderRight: 1, borderColor: "divider", position: "absolute" }}
>
{tabs.map(({ label }, index) => (
<Tab label={label} key={index} tabIndex={index} sx={{ textTransform: "none" }} />
))}
</Tabs>
<Box sx={{ marginLeft: "90px" }}>{displayedTab}</Box>
</Stack>
<Divider />
<Stack sx={{ px: 2, py: 1, flexDirection: "row", alignItems: "center" }}>
<PromotionFooter />
<Box flexGrow={1} />
<Stack direction={"row"} alignItems={"center"} gap={2}>
<IconButton onClick={() => onOpacityChange(MIN_OPACITY)}>
<VisibilityOff />
</IconButton>
<Slider
value={opacity}
onChange={(_, v) => onOpacityChange(v as number)}
step={5}
min={MIN_OPACITY}
max={MAX_OPACITY}
valueLabelFormat={(value: number) => `${value}%`}
sx={{ width: "12em" }}
valueLabelDisplay={"auto"}
/>
<IconButton onClick={() => onOpacityChange(MAX_OPACITY)}>
<Visibility />
</IconButton>
</Stack>
</Stack>
</Paper>
);
}
Example #8
Source File: Header.tsx From genshin-optimizer with MIT License | 5 votes |
function HeaderContent({ anchor }) {
const theme = useTheme();
const isLarge = useMediaQuery(theme.breakpoints.up('lg'));
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
const { t } = useTranslation("ui")
const { params: { currentTab } } = useMatch({ path: "/:currentTab", end: false }) ?? { params: { currentTab: "" } };
if (isMobile) return <MobileHeader anchor={anchor} currentTab={currentTab} />
return <AppBar position="static" sx={{ bgcolor: "#343a40", display: "flex", flexWrap: "nowrap" }} elevation={0} id={anchor} >
<Tabs
value={currentTab}
variant="scrollable"
scrollButtons="auto"
sx={{
"& .MuiTab-root": {
px: 1,
flexDirection: "row",
minWidth: 40,
minHeight: "auto",
},
"& .MuiTab-root:hover": {
transition: "background-color 0.5s ease",
backgroundColor: "rgba(255,255,255,0.1)"
},
"& .MuiTab-root > .MuiTab-iconWrapper": {
mb: 0,
mr: 0.5
},
}}
>
<Tab value="" component={RouterLink} to="/" label={<Typography variant="h6" sx={{ px: 1 }}>
<Trans t={t} i18nKey="pageTitle">Genshin Optimizer</Trans>
</Typography>} />
{content.map(({ i18Key, value, to, svg }) => <Tab key={value} value={value} component={RouterLink} to={to} icon={<FontAwesomeIcon icon={svg} />} label={t(i18Key)} />)}
<Box flexGrow={1} />
{links.map(({ i18Key, href, label, svg }) => <Tab key={label} component="a" href={href} target="_blank" icon={<FontAwesomeIcon icon={svg} />} onClick={e => ReactGA.outboundLink({ label }, () => { })} label={isLarge && t(i18Key)} />)}
</Tabs>
</AppBar>
}
Example #9
Source File: TemplateDetailPage.tsx From fluttertemplates.dev with MIT License | 4 votes |
function RenderBody(props: TemplateCardProps) {
const router = useRouter();
const [selectedTab, setSelectedTab] = useState(0);
const handleTabChange = (event: any, newValue: any) => {
setSelectedTab(newValue);
};
const _frontmatter = props.frontmatter;
const _hasMdContent = props.content.toString().length != 0;
function renderTabs(selectedTab: number) {
if (_hasMdContent && selectedTab == 0) {
return (
<div
style={{
width: "100%",
height: "80%",
overflowX: "hidden",
overflowY: "auto",
}}
>
<ReactMarkdown
children={props.content}
remarkPlugins={[remarkGfm]}
rehypePlugins={[rehypeRaw]}
linkTarget="_blank"
components={{
img: ({ node, ...props }) => (
<img {...props} width="100%" height="100%" />
),
tr: ({ node, ...props }) => (
<Grid container spacing={1}>
{props.children}
</Grid>
),
td: ({ node, ...props }) => (
<Grid item xs={4}>
{props.children}
</Grid>
),
}}
/>
</div>
);
} else if (
(!_hasMdContent && selectedTab == 0) ||
(_hasMdContent && selectedTab == 1)
) {
return (
<Code
codeGistUrl={_frontmatter.codeGistUrl}
fullCodeUrl={_frontmatter.codeUrl}
/>
);
} else if (
(!_hasMdContent && selectedTab == 1) ||
(_hasMdContent && selectedTab == 2)
) {
return <PackagesUsed packages={_frontmatter.packages ?? []} />;
} else {
return <PageNotFoundPage />;
}
}
return (
<Grid
container
style={{
marginTop: "2rem",
}}
spacing={2}
justifyContent="center"
>
{!(_frontmatter.isProtected ?? false) && (
<Grid
item
md={6}
style={{
height: "90vh",
width: "100%",
}}
>
<Card elevation={0}>
<Typography
component="h5"
variant="h5"
style={{
fontWeight: "bold",
marginLeft: "1rem",
marginTop: "1rem",
}}
>
{_frontmatter.title}
</Typography>
{_frontmatter.categories && _frontmatter.categories.length > 0 && (
<div
style={{
marginLeft: "1rem",
marginBottom: "-10px",
}}
>
<CategoriesList
categories={_frontmatter.categories}
selected={""}
showAll={false}
/>
</div>
)}
<Tabs
value={selectedTab}
onChange={handleTabChange}
indicatorColor="secondary"
textColor="inherit"
centered
>
{_hasMdContent && (
<Tab
label="About"
icon={
<InfoOutlined
style={{
marginTop: "8px",
marginRight: "8px",
}}
/>
}
/>
)}
<Tab
label="Code"
icon={
<CodeOutlined
style={{
marginTop: "8px",
marginRight: "8px",
}}
/>
}
/>
<Tab
label="Packages Used"
icon={
<AttachmentOutlined
style={{
marginTop: "8px",
marginRight: "8px",
}}
/>
}
/>
</Tabs>
</Card>
{renderTabs(selectedTab)}
</Grid>
)}
<Grid
item
md={6}
style={{
display: "flex",
justifyContent: "center",
}}
>
<div
style={{
height: "80vh",
width: "calc(80vh/17 * 9)",
margin: "8px",
}}
>
<CustomIframe
url={_frontmatter.demoUrl}
showLoadingIndicator={true}
style={{
borderRadius: "16px",
border: "4px solid grey",
}}
/>
</div>
</Grid>
</Grid>
);
}
Example #10
Source File: Settings.tsx From sapio-studio with Mozilla Public License 2.0 | 4 votes |
export function SettingsInner() {
const [idx, set_idx] = React.useState<number>(0);
const [dialog_node, set_dialog_node] = React.useState<
[string | null, string[]]
>([null, []]);
const handleChange = (_: any, idx: number) => {
set_idx(idx);
};
const test_bitcoind = async () => {
window.electron
.bitcoin_command([{ method: 'getbestblockhash', parameters: [] }])
.then((h) =>
set_dialog_node(['Connection Seems OK:', [`Best Hash ${h[0]}`]])
)
.catch((e) => {
console.log('GOT', JSON.stringify(e));
const r = e.message;
if (typeof e.message === 'string') {
const err = JSON.parse(r);
if (
err instanceof Object &&
'code' in err &&
'name' in err &&
'message' in err
) {
set_dialog_node([
'¡Connection Not Working!',
[
`Name: ${err.name}`,
`Message: ${err.message}`,
`Error Code: ${err.code}`,
],
]);
return;
} else if (typeof err === 'string') {
set_dialog_node([
'¡Connection Not Working!',
[`${err}`],
]);
return;
}
}
set_dialog_node(['¡Unknown Error!', [`${r.toString()}`]]);
});
};
const test_sapio = async () => {
window.electron.sapio
.show_config()
.then((conf) => {
if ('ok' in conf)
set_dialog_node([
'Sapio-CLI is Working!\nUsing Configuration:',
[`${conf.ok}`],
]);
else
set_dialog_node(['¡Configuration Error!', [`${conf.err}`]]);
})
.catch((e) =>
set_dialog_node(['¡Configuration Error!', [`${e.toString()}`]])
);
};
const check_emulator = async () => {
window.electron.emulator.read_log().then((log) => {
if (log.length) {
const json = JSON.parse(log);
set_dialog_node([
'Emulator Status:',
[
`interface: ${json.interface}`,
`pk: ${json.pk}`,
`sync: ${json.sync}`,
],
]);
} else {
set_dialog_node(['Emulator Status:', ['Not Running']]);
}
});
};
return (
<div className="Settings">
<Box className="SettingsNav">
<Tabs
orientation="vertical"
value={idx}
onChange={handleChange}
aria-label="basic tabs example"
>
<Tab label="Guide"></Tab>
<Tab label="Sapio CLI"></Tab>
<Tab label="Bitcoin"></Tab>
<Tab label="Emulator"></Tab>
<Tab label="Display"></Tab>
</Tabs>
</Box>
<Box className="SettingsPanes">
<Dialog
open={
Boolean(dialog_node[0]) ||
Boolean(dialog_node[1].length)
}
onClose={() => set_dialog_node([null, []])}
aria-labelledby="alert-dialog-title"
aria-describedby="alert-dialog-description"
>
<DialogTitle id="alert-dialog-title">
{dialog_node[0]}
</DialogTitle>
<DialogContent>
<div id="alert-dialog-description">
{dialog_node[1].map((txt) => (
<DialogContentText key={txt}>
{txt}
</DialogContentText>
))}
</div>
</DialogContent>
<DialogActions>
<Button onClick={() => set_dialog_node([null, []])}>
Close
</Button>
</DialogActions>
</Dialog>
<Guide idx={idx} my_idx={0} />
<SettingPane name={'sapio_cli'} value={idx} idx={1}>
<Button
onClick={test_sapio}
variant="contained"
color="info"
size="large"
>
Test Sapio-Cli
</Button>
</SettingPane>
<SettingPane name={'bitcoin'} value={idx} idx={2}>
<Button
onClick={test_bitcoind}
variant="contained"
color="info"
size="large"
>
Test Connection
</Button>
</SettingPane>
<SettingPane name={'local_oracle'} value={idx} idx={3}>
<Button
variant="contained"
color="success"
size="large"
onClick={window.electron.emulator.start}
>
Start
</Button>
<Button
sx={{ marginLeft: '20px' }}
variant="contained"
color="error"
size="large"
onClick={window.electron.emulator.kill}
>
Kill
</Button>
<Button
sx={{ marginLeft: '20px' }}
variant="contained"
color="info"
size="large"
onClick={check_emulator}
>
Check Status
</Button>
</SettingPane>
<SettingPane name={'display'} value={idx} idx={4} />
</Box>
</div>
);
}
Example #11
Source File: ItemViewer.tsx From NekoMaid with MIT License | 4 votes |
ItemEditor: React.FC = () => {
const plugin = usePlugin()
const theme = useTheme()
const [item, setItem] = useState<Item | undefined>()
const [types, setTypes] = useState<string[]>([])
const [tab, setTab] = useState(0)
const [level, setLevel] = useState(1)
const [enchantment, setEnchantment] = useState<string | undefined>()
const [nbtText, setNBTText] = useState('')
const nbt: NBT = item?.nbt ? parse(item.nbt) : { id: 'minecraft:' + (item?.type || 'air').toLowerCase(), Count: new Byte(1) } as any
useEffect(() => {
if (!item || types.length) return
plugin.emit('item:fetch', (a: string[], b: string[]) => {
setTypes(a)
enchantments = b
})
}, [item])
useEffect(() => {
_setItem = (it: any) => {
setItem(it)
setNBTText(it.nbt ? stringify(parse(it.nbt), { pretty: true }) : '')
}
return () => { _setItem = null }
}, [])
const cancel = () => {
setItem(undefined)
if (_resolve) {
_resolve(false)
_resolve = null
}
}
const update = () => {
const newItem: any = { ...item }
if (nbt) {
newItem.nbt = stringify(nbt as any)
setNBTText(stringify(nbt, { pretty: true }))
}
setItem(newItem)
}
const isAir = item?.type === 'AIR'
const name = nbt?.tag?.display?.Name
const enchantmentMap: Record<string, true> = { }
return <Dialog open={!!item} onClose={cancel}>
<DialogTitle>{lang.itemEditor.title}</DialogTitle>
<DialogContent sx={{ display: 'flex', flexWrap: 'wrap', justifyContent: 'center' }}>
{item && <Box sx={{ display: 'flex', width: '100%', justifyContent: 'center' }}>
<ItemViewer item={item} />
<Autocomplete
options={types}
sx={{ maxWidth: 300, marginLeft: 1, flexGrow: 1 }}
value={item?.type}
onChange={(_, it) => {
item.type = it || 'AIR'
if (nbt) nbt.id = 'minecraft:' + (it ? it.toLowerCase() : 'air')
update()
}}
getOptionLabel={it => {
const locatedName = getName(it.toLowerCase())
return (locatedName ? locatedName + ' ' : '') + it
}}
renderInput={(params) => <TextField {...params} label={lang.itemEditor.itemType} size='small' variant='standard' />}
/>
</Box>}
<Tabs centered value={tab} onChange={(_, it) => setTab(it)} sx={{ marginBottom: 2 }}>
<Tab label={lang.itemEditor.baseAttribute} disabled={isAir} />
<Tab label={minecraft['container.enchant']} disabled={isAir} />
<Tab label='NBT' disabled={isAir} />
</Tabs>
{nbt && tab === 0 && <Grid container spacing={1} rowSpacing={1}>
<Grid item xs={12} md={6}><TextField
fullWidth
label={lang.itemEditor.count}
type='number'
variant='standard'
value={nbt.Count}
disabled={isAir}
onChange={e => {
nbt.Count = new Byte(item!.amount = parseInt(e.target.value))
update()
}}
/></Grid>
<Grid item xs={12} md={6}><TextField
fullWidth
label={lang.itemEditor.damage}
type='number'
variant='standard'
value={nbt.tag?.Damage}
disabled={isAir}
onChange={e => {
set(nbt, 'tag.Damage', parseInt(e.target.value))
update()
}}
/></Grid>
<Grid item xs={12} md={6}>
<TextField
fullWidth
label={lang.itemEditor.displayName}
variant='standard'
disabled={isAir}
value={name ? stringifyTextComponent(JSON.parse(name)) : ''}
onChange={e => {
set(nbt, 'tag.display.Name', JSON.stringify(item!.name = e.target.value))
update()
}}
/>
<FormControlLabel
label={minecraft['item.unbreakable']}
disabled={isAir}
checked={nbt.tag?.Unbreakable?.value === 1}
control={<Checkbox
checked={nbt.tag?.Unbreakable?.value === 1}
onChange={e => {
set(nbt, 'tag.Unbreakable', new Byte(+e.target.checked))
update()
}} />
}
/>
</Grid>
<Grid item xs={12} md={6}><TextField
fullWidth
multiline
label={lang.itemEditor.lore}
variant='standard'
maxRows={5}
disabled={isAir}
value={nbt.tag?.display?.Lore?.map(l => stringifyTextComponent(JSON.parse(l)))?.join('\n') || ''}
onChange={e => {
set(nbt, 'tag.display.Lore', e.target.value.split('\n').map(text => JSON.stringify(text)))
update()
}}
/></Grid>
</Grid>}
{nbt && tab === 1 && <Grid container spacing={1} sx={{ width: '100%' }}>
{nbt.tag?.Enchantments?.map((it, i) => {
enchantmentMap[it.id] = true
return <Grid item key={i}><Chip label={getEnchantmentName(it)} onDelete={() => {
nbt?.tag?.Enchantments?.splice(i, 1)
update()
}} /></Grid>
})}
<Grid item><Chip label={lang.itemEditor.newEnchantment} color='primary' onClick={() => {
setEnchantment('')
setLevel(1)
}} /></Grid>
<Dialog onClose={() => setEnchantment(undefined)} open={enchantment != null}>
<DialogTitle>{lang.itemEditor.newEnchantmentTitle}</DialogTitle>
<DialogContent>
<Box component='form' sx={{ display: 'flex', flexWrap: 'wrap' }}>
<FormControl variant='standard' sx={{ m: 1, minWidth: 120 }}>
<InputLabel htmlFor='item-editor-enchantment-selector'>{minecraft['container.enchant']}</InputLabel>
<Select
id='item-editor-enchantment-selector'
label={minecraft['container.enchant']}
value={enchantment || ''}
onChange={e => setEnchantment(e.target.value)}
>{enchantments
.filter(e => !(e in enchantmentMap))
.map(it => <MenuItem key={it} value={it}>{getEnchantmentName(it)}</MenuItem>)}
</Select>
</FormControl>
<FormControl sx={{ m: 1, minWidth: 120 }}>
<TextField
label={lang.itemEditor.level}
type='number'
variant='standard'
value={level}
onChange={e => setLevel(parseInt(e.target.value))}
/>
</FormControl>
</Box>
</DialogContent>
<DialogActions>
<Button onClick={() => setEnchantment(undefined)}>{minecraft['gui.cancel']}</Button>
<Button disabled={!enchantment || isNaN(level)} onClick={() => {
if (nbt) {
if (!nbt.tag) nbt.tag = { Damage: new Int(0) }
;(nbt.tag.Enchantments || (nbt.tag.Enchantments = [])).push({ id: enchantment!, lvl: new Short(level) })
}
setEnchantment(undefined)
update()
}}>{minecraft['gui.ok']}</Button>
</DialogActions>
</Dialog>
</Grid>}
</DialogContent>
{nbt && tab === 2 && <Box sx={{
'& .CodeMirror': { width: '100%' },
'& .CodeMirror-dialog, .CodeMirror-scrollbar-filler': { backgroundColor: theme.palette.background.paper + '!important' }
}}>
<UnControlled
value={nbtText}
options={{
mode: 'javascript',
phrases: lang.codeMirrorPhrases,
theme: theme.palette.mode === 'dark' ? 'material' : 'one-light'
}}
onChange={(_: any, __: any, nbt: string) => {
const n = parse(nbt) as any as NBT
const newItem: any = { ...item, nbt }
if (n.Count?.value != null) newItem.amount = n.Count.value
setItem(newItem)
}}
/>
</Box>}
<DialogActions>
<Button onClick={cancel}>{minecraft['gui.cancel']}</Button>
<Button onClick={() => {
setItem(undefined)
if (_resolve) {
_resolve(!item || item.type === 'AIR' ? null : item)
_resolve = null
}
}}>{minecraft['gui.ok']}</Button>
</DialogActions>
</Dialog>
}
Example #12
Source File: EntityView.tsx From firecms with MIT License | 4 votes |
export function EntityView<M extends { [Key: string]: any }, UserType>({
path,
entityId,
callbacks,
selectedSubpath,
copy,
permissions,
schema: schemaOrResolver,
subcollections,
onModifiedValues,
width
}: EntityViewProps<M, UserType>) {
const resolvedWidth: string | undefined = typeof width === "number" ? `${width}px` : width;
const classes = useStylesSide({ containerWidth: resolvedWidth ?? CONTAINER_WIDTH });
const dataSource = useDataSource();
const sideEntityController = useSideEntityController();
const snackbarContext = useSnackbarController();
const context = useFireCMSContext();
const authController = useAuthController<UserType>();
const [status, setStatus] = useState<EntityStatus>(copy ? "copy" : (entityId ? "existing" : "new"));
const [currentEntityId, setCurrentEntityId] = useState<string | undefined>(entityId);
const [readOnly, setReadOnly] = useState<boolean>(false);
const [tabsPosition, setTabsPosition] = React.useState(-1);
const [modifiedValues, setModifiedValues] = useState<EntityValues<any> | undefined>();
const {
entity,
dataLoading,
// eslint-disable-next-line no-unused-vars
dataLoadingError
} = useEntityFetch({
path,
entityId: currentEntityId,
schema: schemaOrResolver,
useCache: false
});
const [usedEntity, setUsedEntity] = useState<Entity<M> | undefined>(entity);
const resolvedSchema:ResolvedEntitySchema<M> = useMemo(() => computeSchema({
schemaOrResolver,
path,
entityId,
values: modifiedValues,
previousValues: usedEntity?.values
}), [schemaOrResolver, path, entityId, modifiedValues]);
useEffect(() => {
function beforeunload(e: any) {
if (modifiedValues) {
e.preventDefault();
e.returnValue = `You have unsaved changes in this ${resolvedSchema.name}. Are you sure you want to leave this page?`;
}
}
if (typeof window !== "undefined")
window.addEventListener("beforeunload", beforeunload);
return () => {
if (typeof window !== "undefined")
window.removeEventListener("beforeunload", beforeunload);
};
}, [modifiedValues, window]);
const customViews = resolvedSchema.views;
const customViewsCount = customViews?.length ?? 0;
useEffect(() => {
setUsedEntity(entity);
if (entity)
setReadOnly(!canEdit(permissions, entity, authController, path, context));
}, [entity, permissions]);
const theme = useTheme();
const largeLayout = useMediaQuery(theme.breakpoints.up("lg"));
useEffect(() => {
if (!selectedSubpath)
setTabsPosition(-1);
if (customViews) {
const index = customViews
.map((c) => c.path)
.findIndex((p) => p === selectedSubpath);
setTabsPosition(index);
}
if (subcollections && selectedSubpath) {
const index = subcollections
.map((c) => c.path)
.findIndex((p) => p === selectedSubpath);
setTabsPosition(index + customViewsCount);
}
}, [selectedSubpath]);
const onPreSaveHookError = useCallback((e: Error) => {
snackbarContext.open({
type: "error",
title: "Error before saving",
message: e?.message
});
console.error(e);
}, []);
const onSaveSuccessHookError = useCallback((e: Error) => {
snackbarContext.open({
type: "error",
title: `${resolvedSchema.name}: Error after saving (entity is saved)`,
message: e?.message
});
console.error(e);
}, []);
const onSaveSuccess = useCallback((updatedEntity: Entity<M>) => {
setCurrentEntityId(updatedEntity.id);
snackbarContext.open({
type: "success",
message: `${resolvedSchema.name}: Saved correctly`
});
setUsedEntity(updatedEntity);
setStatus("existing");
onModifiedValues(false);
if (tabsPosition === -1)
sideEntityController.close();
}, []);
const onSaveFailure = useCallback((e: Error) => {
snackbarContext.open({
type: "error",
title: `${resolvedSchema.name}: Error saving`,
message: e?.message
});
console.error("Error saving entity", path, entityId);
console.error(e);
}, []);
const onEntitySave = useCallback(async ({
schema,
path,
entityId,
values,
previousValues
}: {
schema: EntitySchema<M>,
path: string,
entityId: string | undefined,
values: EntityValues<M>,
previousValues?: EntityValues<M>,
}): Promise<void> => {
console.log("onEntitySave", path)
if (!status)
return;
return saveEntityWithCallbacks({
path,
entityId,
callbacks,
values,
previousValues,
schema,
status,
dataSource,
context,
onSaveSuccess,
onSaveFailure,
onPreSaveHookError,
onSaveSuccessHookError
});
}, [status, callbacks, dataSource, context, onSaveSuccess, onSaveFailure, onPreSaveHookError, onSaveSuccessHookError]);
const onDiscard = useCallback(() => {
if (tabsPosition === -1)
sideEntityController.close();
}, [sideEntityController, tabsPosition]);
const body = !readOnly
? (
<EntityForm
key={`form_${path}_${usedEntity?.id ?? "new"}`}
status={status}
path={path}
schemaOrResolver={schemaOrResolver}
onEntitySave={onEntitySave}
onDiscard={onDiscard}
onValuesChanged={setModifiedValues}
onModified={onModifiedValues}
entity={usedEntity}/>
)
: (usedEntity &&
<EntityPreview
entity={usedEntity}
path={path}
schema={schemaOrResolver}/>
)
;
const customViewsView: JSX.Element[] | undefined = customViews && customViews.map(
(customView, colIndex) => {
return (
<Box
className={classes.subcollectionPanel}
key={`custom_view_${customView.path}_${colIndex}`}
role="tabpanel"
flexGrow={1}
height={"100%"}
width={"100%"}
hidden={tabsPosition !== colIndex}>
<ErrorBoundary>
{customView.builder({
schema: resolvedSchema,
entity: usedEntity,
modifiedValues: modifiedValues ?? usedEntity?.values
})}
</ErrorBoundary>
</Box>
);
}
);
const subCollectionsViews = subcollections && subcollections.map(
(subcollection, colIndex) => {
const absolutePath = usedEntity ? `${usedEntity?.path}/${usedEntity?.id}/${removeInitialAndTrailingSlashes(subcollection.path)}` : undefined;
return (
<Box
className={classes.subcollectionPanel}
key={`subcol_${subcollection.name}_${colIndex}`}
role="tabpanel"
flexGrow={1}
hidden={tabsPosition !== colIndex + customViewsCount}>
{usedEntity && absolutePath
? <EntityCollectionView
path={absolutePath}
collection={subcollection}/>
: <Box m={3}
display={"flex"}
alignItems={"center"}
justifyContent={"center"}>
<Box>
You need to save your entity before
adding additional collections
</Box>
</Box>
}
</Box>
);
}
);
const getSelectedSubpath = useCallback((value: number) => {
if (value === -1) return undefined;
if (customViews && value < customViewsCount) {
return customViews[value].path;
}
if (subcollections) {
return subcollections[value - customViewsCount].path;
}
throw Error("Something is wrong in getSelectedSubpath");
}, [customViews]);
const onSideTabClick = useCallback((value: number) => {
setTabsPosition(value);
if (entityId) {
sideEntityController.open({
path,
entityId,
selectedSubpath: getSelectedSubpath(value),
overrideSchemaRegistry: false
});
}
}, []);
const loading = dataLoading || (!usedEntity && status === "existing");
const header = (
<Box sx={{
paddingLeft: 2,
paddingRight: 2,
paddingTop: 2,
display: "flex",
alignItems: "center",
backgroundColor: theme.palette.mode === "light" ? theme.palette.background.default : theme.palette.background.paper
}}
>
<IconButton onClick={(e) => sideEntityController.close()}
size="large">
<CloseIcon/>
</IconButton>
<Tabs
value={tabsPosition === -1 ? 0 : false}
indicatorColor="secondary"
textColor="inherit"
scrollButtons="auto"
>
<Tab
label={resolvedSchema.name}
classes={{
root: classes.tab
}}
wrapped={true}
onClick={() => {
onSideTabClick(-1);
}}/>
</Tabs>
<Box flexGrow={1}/>
{loading &&
<CircularProgress size={16} thickness={8}/>}
<Tabs
value={tabsPosition >= 0 ? tabsPosition : false}
indicatorColor="secondary"
textColor="inherit"
onChange={(ev, value) => {
onSideTabClick(value);
}}
className={classes.tabBar}
variant="scrollable"
scrollButtons="auto"
>
{customViews && customViews.map(
(view) =>
<Tab
classes={{
root: classes.tab
}}
wrapped={true}
key={`entity_detail_custom_tab_${view.name}`}
label={view.name}/>
)}
{subcollections && subcollections.map(
(subcollection) =>
<Tab
classes={{
root: classes.tab
}}
wrapped={true}
key={`entity_detail_collection_tab_${subcollection.name}`}
label={subcollection.name}/>
)}
</Tabs>
</Box>
);
return <div
className={clsx(classes.container, { [classes.containerWide]: tabsPosition !== -1 })}>
{
loading
? <CircularProgressCenter/>
: <>
{header}
<Divider/>
<div className={classes.tabsContainer}>
<Box
role="tabpanel"
hidden={!largeLayout && tabsPosition !== -1}
className={classes.form}>
{body}
</Box>
{customViewsView}
{subCollectionsViews}
</div>
</>
}
</div>;
}
Example #13
Source File: Library.tsx From Tachidesk-WebUI with Mozilla Public License 2.0 | 4 votes |
export default function Library() {
const { setTitle, setAction } = useContext(NavbarContext);
useEffect(() => {
setTitle('Library'); setAction(
<>
<AppbarSearch />
<LibraryOptions />
<UpdateChecker />
</>,
);
}, []);
const [tabs, setTabs] = useState<IMangaCategory[]>();
const [tabNum, setTabNum] = useState<number>(0);
const [tabSearchParam, setTabSearchParam] = useQueryParam('tab', NumberParam);
// a hack so MangaGrid doesn't stop working. I won't change it in case
// if I do manga pagination for library..
const [lastPageNum, setLastPageNum] = useState<number>(1);
const handleTabChange = (newTab: number) => {
setTabNum(newTab);
setTabSearchParam(newTab);
};
useEffect(() => {
client.get('/api/v1/category')
.then((response) => response.data)
.then((categories: ICategory[]) => {
const categoryTabs = categories.map((category) => ({
category,
mangas: [] as IManga[],
isFetched: false,
}));
setTabs(categoryTabs);
if (categoryTabs.length > 0) {
if (
tabSearchParam !== undefined
&& tabSearchParam !== null
&& !Number.isNaN(tabSearchParam)
&& categories.some((category) => category.order === Number(tabSearchParam))
) {
handleTabChange(Number(tabSearchParam!));
} else { handleTabChange(categoryTabs[0].category.order); }
}
});
}, []);
// fetch the current tab
useEffect(() => {
if (tabs !== undefined) {
tabs.forEach((tab, index) => {
if (tab.category.order === tabNum && !tab.isFetched) {
// eslint-disable-next-line @typescript-eslint/no-shadow
client.get(`/api/v1/category/${tab.category.id}`)
.then((response) => response.data)
.then((data: IManga[]) => {
const tabsClone = cloneObject(tabs);
tabsClone[index].mangas = data;
tabsClone[index].isFetched = true;
setTabs(tabsClone);
});
}
});
}
}, [tabs?.length, tabNum]);
if (tabs === undefined) {
return <LoadingPlaceholder />;
}
if (tabs.length === 0) {
return <EmptyView message="Your Library is empty" />;
}
let toRender;
if (tabs.length > 1) {
// eslint-disable-next-line max-len
const tabDefines = tabs.map((tab) => (<Tab label={tab.category.name} value={tab.category.order} />));
const tabBodies = tabs.map((tab) => (
<TabPanel index={tab.category.order} currentIndex={tabNum}>
<LibraryMangaGrid
mangas={tab.mangas}
hasNextPage={false}
lastPageNum={lastPageNum}
setLastPageNum={setLastPageNum}
message="Category is Empty"
isLoading={!tab.isFetched}
/>
</TabPanel>
));
// Visual Hack: 160px is min-width for viewport width of >600
const scrollableTabs = window.innerWidth < tabs.length * 160;
toRender = (
<>
<Tabs
key={tabNum}
value={tabNum}
onChange={(e, newTab) => handleTabChange(newTab)}
indicatorColor="primary"
textColor="primary"
centered={!scrollableTabs}
variant={scrollableTabs ? 'scrollable' : 'fullWidth'}
scrollButtons
allowScrollButtonsMobile
>
{tabDefines}
</Tabs>
{tabBodies}
</>
);
} else {
const mangas = tabs.length === 1 ? tabs[0].mangas : [];
toRender = (
<LibraryMangaGrid
mangas={mangas}
hasNextPage={false}
lastPageNum={lastPageNum}
setLastPageNum={setLastPageNum}
message="Your Library is empty"
isLoading={!tabs[0].isFetched}
/>
);
}
return toRender;
}
Example #14
Source File: ChapterOptions.tsx From Tachidesk-WebUI with Mozilla Public License 2.0 | 4 votes |
export default function ChapterOptions(props: IProps) {
const { options, optionsDispatch } = props;
const [filtersOpen, setFiltersOpen] = useState(false);
const [tabNum, setTabNum] = useState(0);
const filterOptions = useCallback(
(value: NullAndUndefined<boolean>, name: string) => {
optionsDispatch({ type: 'filter', filterType: name.toLowerCase(), filterValue: value });
}, [],
);
return (
<>
<IconButton
onClick={() => setFiltersOpen(!filtersOpen)}
color={options.active ? 'warning' : 'default'}
>
<FilterListIcon />
</IconButton>
<Drawer
anchor="bottom"
open={filtersOpen}
onClose={() => setFiltersOpen(false)}
PaperProps={{
style: {
maxWidth: 600,
padding: '1em',
marginLeft: 'auto',
marginRight: 'auto',
minHeight: '150px',
},
}}
>
<Box>
<Tabs
key={tabNum}
value={tabNum}
variant="fullWidth"
onChange={(e, newTab) => setTabNum(newTab)}
indicatorColor="primary"
textColor="primary"
>
<Tab value={0} label="Filter" />
<Tab value={1} label="Sort" />
<Tab value={2} label="Display" />
</Tabs>
<TabPanel index={0} currentIndex={tabNum}>
<Box sx={{ display: 'flex', flexDirection: 'column', minHeight: '150px' }}>
<FormControlLabel control={<ThreeStateCheckbox name="Unread" checked={options.unread} onChange={filterOptions} />} label="Unread" />
<FormControlLabel control={<ThreeStateCheckbox name="Downloaded" checked={options.downloaded} onChange={filterOptions} />} label="Downloaded" />
<FormControlLabel control={<ThreeStateCheckbox name="Bookmarked" checked={options.bookmarked} onChange={filterOptions} />} label="Bookmarked" />
</Box>
</TabPanel>
<TabPanel index={1} currentIndex={tabNum}>
<Box sx={{ display: 'flex', flexDirection: 'column', minHeight: '150px' }}>
{
SortTab.map((item) => (
<Stack
direction="row"
alignItems="center"
spacing="2"
sx={{ py: 1, height: 42 }}
onClick={() => (item[0] !== options.sortBy
? optionsDispatch({ type: 'sortBy', sortBy: item[0] })
: optionsDispatch({ type: 'sortReverse' }))}
>
<Box sx={{ height: 24, width: 24 }}>
{
options.sortBy === item[0]
&& (options.reverse
? (<ArrowUpward color="primary" />) : (<ArrowDownward color="primary" />))
}
</Box>
<Typography>{item[1]}</Typography>
</Stack>
))
}
</Box>
</TabPanel>
<TabPanel index={2} currentIndex={tabNum}>
<Stack flexDirection="column" sx={{ minHeight: '150px' }}>
<RadioGroup name="chapter-title-display" onChange={() => optionsDispatch({ type: 'showChapterNumber' })} value={options.showChapterNumber}>
<FormControlLabel label="By Source Title" value="title" control={<Radio checked={!options.showChapterNumber} />} />
<FormControlLabel label="By Chapter Number" value="chapterNumber" control={<Radio checked={options.showChapterNumber} />} />
</RadioGroup>
</Stack>
</TabPanel>
</Box>
</Drawer>
</>
);
}
Example #15
Source File: index.tsx From ExpressLRS-Configurator with GNU General Public License v3.0 | 4 votes |
WifiDeviceSelect: FunctionComponent<WifiDeviceSelectProps> = (props) => {
const { wifiDevice, wifiDevices, onChange } = props;
const options = useMemo(() => {
const result = wifiDevices.map((target) => {
return {
label: `${target.name} - ${
target.deviceName ? target.deviceName : target.target
} (${target.ip})`,
value: target.ip,
};
});
if (result.length === 0) {
result.push({
label: `Default (10.0.0.1)`,
value: `10.0.0.1`,
});
}
return result;
}, [wifiDevices]);
const [
currentlySelectedValue,
setCurrentlySelectedValue,
] = useState<Option | null>(
wifiDevice
? options.find((item) => item.value === wifiDevice) ?? null
: null
);
useEffect(() => {
setCurrentlySelectedValue(
options.find((item) => item.value === wifiDevice) ??
options[0] ??
currentlySelectedValue
);
}, [currentlySelectedValue, options, wifiDevice]);
const [currentTextBoxValue, setCurrentTextBoxValue] = useState<string | null>(
wifiDevice
);
const onDeviceSelectChange = useCallback(
(value: string | null) => {
if (value === null) {
setCurrentlySelectedValue(null);
} else {
setCurrentlySelectedValue(
options.find((item) => item.value === value) ?? null
);
onChange(value);
}
},
[onChange, options]
);
const onTextFieldValueChange = useCallback(
(event: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => {
setCurrentTextBoxValue(event.target.value);
onChange(event.target.value);
},
[onChange]
);
const [wifiSource, setWifiSource] = useState<WifiSourceType>(
WifiSourceType.LIST
);
const handleWifiSourceChange = useCallback(
(_event: React.SyntheticEvent, value: WifiSourceType) => {
setWifiSource(value);
if (value === WifiSourceType.LIST) {
onChange(currentlySelectedValue?.value ?? null);
} else if (value === WifiSourceType.MANUAL) {
onChange(currentTextBoxValue);
}
},
[currentTextBoxValue, currentlySelectedValue, onChange]
);
return (
<Box sx={styles.root}>
<Tabs
sx={styles.tabs}
defaultValue={WifiSourceType.LIST}
value={wifiSource}
onChange={handleWifiSourceChange}
>
<Tab label="WiFi Devices" value={WifiSourceType.LIST} />
<Tab label="Manual" value={WifiSourceType.MANUAL} />
</Tabs>
{wifiSource === WifiSourceType.LIST && (
<Box sx={styles.inner}>
<Omnibox
title="WiFi Device Selection"
currentValue={
options.find(
(item) => item.value === currentlySelectedValue?.value
) ?? null
}
onChange={onDeviceSelectChange}
options={options}
/>
</Box>
)}
{wifiSource === WifiSourceType.MANUAL && (
<div>
<TextField
onChange={onTextFieldValueChange}
fullWidth
label="IP Address"
value={currentTextBoxValue}
/>
</div>
)}
</Box>
);
}
Example #16
Source File: index.tsx From ExpressLRS-Configurator with GNU General Public License v3.0 | 4 votes |
FirmwareVersionForm: FunctionComponent<FirmwareVersionCardProps> = (
props
) => {
const { onChange, data, gitRepository } = props;
const [firmwareSource, setFirmwareSource] = useState<FirmwareSource>(
data?.source || FirmwareSource.GitTag
);
const handleFirmwareSourceChange = (
_event: React.SyntheticEvent,
value: FirmwareSource
) => {
setFirmwareSource(value);
};
const [showPreReleases, setShowPreReleases] = useState<boolean>(false);
useEffect(() => {
new ApplicationStorage()
.getShowPreReleases(false)
.then((value) => {
setShowPreReleases(value);
})
.catch((err: Error) => {
console.error('failed to get show pre-releases from storage', err);
});
}, []);
const onShowPreReleases = (
_event: React.ChangeEvent<HTMLInputElement>,
checked: boolean
) => {
setShowPreReleases(checked);
new ApplicationStorage().setShowPreReleases(checked).catch((err: Error) => {
console.error('failed to set show pre-releases in storage', err);
});
};
const [
queryGitTags,
{ loading: gitTagsLoading, data: gitTagsResponse, error: tagsError },
] = useGetReleasesLazyQuery();
const [
queryGitBranches,
{
loading: gitBranchesLoading,
data: gitBranchesResponse,
error: branchesError,
},
] = useGetBranchesLazyQuery();
const [
queryGitPullRequests,
{
loading: gitPullRequestsLoading,
data: gitPullRequestsResponse,
error: pullRequestsError,
},
] = useGetPullRequestsLazyQuery();
const loading =
gitTagsLoading || gitBranchesLoading || gitPullRequestsLoading;
const gitTags = useMemo(() => {
return (
gitTagsResponse?.releases.filter(
({ tagName }) => !gitRepository.tagExcludes.includes(tagName)
) ?? []
).sort((a, b) => semver.rcompare(a.tagName, b.tagName));
}, [gitRepository.tagExcludes, gitTagsResponse?.releases]);
const gitBranches = useMemo(() => {
return gitBranchesResponse?.gitBranches ?? [];
}, [gitBranchesResponse?.gitBranches]);
const gitPullRequests = gitPullRequestsResponse?.pullRequests;
const [currentGitTag, setCurrentGitTag] = useState<string>(
data?.gitTag || ''
);
const onGitTag = (name: string | null) => {
if (name === null) {
setCurrentGitTag('');
return;
}
setCurrentGitTag(name);
};
/*
We need to make sure that a valid value is selected
*/
useEffect(() => {
if (firmwareSource === FirmwareSource.GitTag) {
if (
!showPreReleases &&
gitTags?.length &&
gitTags?.length > 0 &&
gitTags
?.filter(({ preRelease }) => !preRelease)
.find((item) => item.tagName === currentGitTag) === undefined
) {
setCurrentGitTag(gitTags[0].tagName);
}
}
}, [showPreReleases, currentGitTag, gitTags, firmwareSource]);
const [currentGitBranch, setCurrentGitBranch] = useState<string>(
data?.gitBranch || ''
);
const onGitBranch = (name: string | null) => {
if (name === null) {
setCurrentGitBranch('');
return;
}
setCurrentGitBranch(name);
};
const [currentGitCommit, setCurrentGitCommit] = useState<string>(
data?.gitCommit || ''
);
const [debouncedGitCommit, setDebouncedGitCommit] = useState<string>(
data?.gitCommit || ''
);
const debouncedGitCommitHandler = useMemo(
() => debounce(setDebouncedGitCommit, 1000),
[setDebouncedGitCommit]
);
// Stop the invocation of the debounced function
// after unmounting
useEffect(() => {
return () => {
debouncedGitCommitHandler.cancel();
};
}, [debouncedGitCommitHandler]);
const setGitCommit = (value: string) => {
setCurrentGitCommit(value);
debouncedGitCommitHandler(value);
};
const onGitCommit = (event: React.ChangeEvent<HTMLInputElement>) => {
setGitCommit(event.target.value);
};
const [localPath, setLocalPath] = useState<string>(data?.localPath || '');
const onLocalPath = (event: React.ChangeEvent<HTMLInputElement>) => {
setLocalPath(event.target.value);
};
const [
currentGitPullRequest,
setCurrentGitPullRequest,
] = useState<PullRequestInput | null>(data?.gitPullRequest || null);
/*
Make sure that a valid pull request is selected
*/
useEffect(() => {
if (gitPullRequestsResponse?.pullRequests && currentGitPullRequest) {
const pullRequest =
gitPullRequestsResponse.pullRequests.find(
(item) => item.number === currentGitPullRequest.number
) || null;
// if we have a list of pull requests and the current pull request is not
// part of that list, then set current pull request to null
if (!pullRequest) {
setCurrentGitPullRequest(null);
}
// prevent stale head commit hash cache
if (
pullRequest &&
pullRequest.headCommitHash !== currentGitPullRequest.headCommitHash
) {
setCurrentGitPullRequest({
id: pullRequest.id,
number: pullRequest.number,
title: pullRequest.title,
headCommitHash: pullRequest.headCommitHash,
});
}
}
}, [gitPullRequestsResponse, currentGitPullRequest]);
const onGitPullRequest = (value: string | null) => {
if (value === null) {
setCurrentGitPullRequest(null);
return;
}
const iValue = parseInt(value, 10);
const pullRequest = gitPullRequests?.find((item) => item.number === iValue);
if (pullRequest) {
setCurrentGitPullRequest({
id: pullRequest.id,
number: pullRequest.number,
title: pullRequest.title,
headCommitHash: pullRequest.headCommitHash,
});
}
};
useEffect(() => {
const storage = new ApplicationStorage();
storage
.getFirmwareSource(gitRepository)
.then((result) => {
if (result !== null) {
if (result.source) setFirmwareSource(result.source);
if (result.gitTag) setCurrentGitTag(result.gitTag);
if (result.gitCommit) setGitCommit(result.gitCommit);
if (result.gitBranch) setCurrentGitBranch(result.gitBranch);
if (result.localPath) setLocalPath(result.localPath);
if (result.gitPullRequest)
setCurrentGitPullRequest(result.gitPullRequest);
}
})
.catch((err) => {
console.error('failed to get firmware source', err);
});
}, []);
const onChooseFolder = () => {
ipcRenderer
.invoke(IpcRequest.ChooseFolder)
.then((result: ChooseFolderResponseBody) => {
if (result.success) {
setLocalPath(result.directoryPath);
}
})
.catch((err) => {
console.error('failed to get local directory path: ', err);
});
};
useEffect(() => {
switch (firmwareSource) {
case FirmwareSource.GitTag:
queryGitTags({
variables: {
owner: gitRepository.owner,
repository: gitRepository.repositoryName,
},
});
break;
case FirmwareSource.GitBranch:
queryGitBranches({
variables: {
owner: gitRepository.owner,
repository: gitRepository.repositoryName,
},
});
break;
case FirmwareSource.GitCommit:
break;
case FirmwareSource.Local:
break;
case FirmwareSource.GitPullRequest:
queryGitPullRequests({
variables: {
owner: gitRepository.owner,
repository: gitRepository.repositoryName,
},
});
break;
default:
throw new Error(`unknown firmware source: ${firmwareSource}`);
}
}, [
gitRepository,
firmwareSource,
queryGitTags,
queryGitBranches,
queryGitPullRequests,
]);
useEffect(() => {
const updatedData = {
source: firmwareSource,
gitBranch: currentGitBranch,
gitTag: currentGitTag,
gitCommit: debouncedGitCommit,
localPath,
gitPullRequest: currentGitPullRequest,
};
onChange(updatedData);
const storage = new ApplicationStorage();
storage.setFirmwareSource(updatedData, gitRepository).catch((err) => {
console.error('failed to set firmware source', err);
});
}, [
firmwareSource,
currentGitBranch,
currentGitTag,
debouncedGitCommit,
localPath,
currentGitPullRequest,
onChange,
gitRepository,
]);
const gitTagOptions = useMemo(() => {
return gitTags
.filter((item) => {
if (!showPreReleases) {
return item.preRelease === false;
}
return true;
})
.map((item) => ({
label: item.tagName,
value: item.tagName,
}));
}, [gitTags, showPreReleases]);
const gitBranchOptions = useMemo(() => {
return gitBranches.map((branch) => ({
label: branch,
value: branch,
}));
}, [gitBranches]);
const gitPullRequestOptions = useMemo(() => {
return gitPullRequests?.map((pullRequest) => ({
label: `${pullRequest.title} #${pullRequest.number}`,
value: `${pullRequest.number}`,
}));
}, [gitPullRequests]);
const showBetaFpvAlert =
localPath?.toLocaleLowerCase()?.indexOf('betafpv') > -1;
return (
<>
<Tabs
sx={styles.tabs}
defaultValue={FirmwareSource.GitTag}
value={firmwareSource}
onChange={handleFirmwareSourceChange}
>
<Tab label="Official releases" value={FirmwareSource.GitTag} />
<Tab label="Git branch" value={FirmwareSource.GitBranch} />
<Tab label="Git commit" value={FirmwareSource.GitCommit} />
<Tab label="Local" value={FirmwareSource.Local} />
<Tab label="Git Pull Request" value={FirmwareSource.GitPullRequest} />
</Tabs>
{firmwareSource === FirmwareSource.GitTag && gitTags !== undefined && (
<>
<Box sx={styles.tabContents}>
{!loading && (
<>
<FormControlLabel
sx={styles.preReleaseCheckbox}
control={
<Checkbox
checked={showPreReleases}
onChange={onShowPreReleases}
/>
}
label="Show pre-releases"
/>
<Omnibox
title="Releases"
options={gitTagOptions}
currentValue={
gitTagOptions.find(
(item) => item.value === currentGitTag
) ?? null
}
onChange={onGitTag}
/>
<Button
size="small"
sx={styles.releaseNotes}
target="_blank"
rel="noreferrer noreferrer"
href={`${gitRepository.url}/releases/tag/${currentGitTag}`}
>
Release notes
</Button>
{currentGitTag &&
gitTagOptions.length > 0 &&
gitTagOptions[0]?.value !== currentGitTag && (
<Alert sx={styles.firmwareVersionAlert} severity="info">
There is a newer version of the firmware available
</Alert>
)}
</>
)}
</Box>
</>
)}
{firmwareSource === FirmwareSource.GitBranch &&
gitBranches !== undefined && (
<>
<Alert severity="warning" sx={styles.dangerZone}>
<AlertTitle>DANGER ZONE</AlertTitle>
Use these sources only if you know what you are doing or was
instructed by project developers
</Alert>
<Box sx={styles.tabContents}>
{!loading && (
<Omnibox
title="Git branches"
options={gitBranchOptions}
currentValue={
gitBranchOptions.find(
(item) => item.value === currentGitBranch
) ?? null
}
onChange={onGitBranch}
/>
)}
</Box>
</>
)}
{firmwareSource === FirmwareSource.GitCommit && (
<>
<Alert severity="warning" sx={styles.dangerZone}>
<AlertTitle>DANGER ZONE</AlertTitle>
Use these sources only if you know what you are doing or was
instructed by project developers
</Alert>
<Box sx={styles.tabContents}>
<TextField
id="git-commit-hash"
label="Git commit hash"
fullWidth
value={currentGitCommit}
onChange={onGitCommit}
/>
</Box>
</>
)}
{firmwareSource === FirmwareSource.Local && (
<>
<Alert severity="warning" sx={styles.dangerZone}>
<AlertTitle>DANGER ZONE</AlertTitle>
Use these sources only if you know what you are doing or was
instructed by project developers
</Alert>
<Box sx={styles.tabContents}>
<TextField
id="local-path"
label="Local path"
fullWidth
value={localPath}
onChange={onLocalPath}
/>
{showBetaFpvAlert && (
<Alert severity="error" sx={styles.betaFpvAlert}>
<AlertTitle>ATTENTION</AlertTitle>
You are trying to flash an outdated BetaFPV custom ExpressLRS
fork. BetaFPV hardware is fully supported in recent official
ExpressLRS releases. We recommend using official firmware to
have the best ExpressLRS experience.
</Alert>
)}
<Button
color="secondary"
size="small"
variant="contained"
sx={styles.chooseFolderButton}
onClick={onChooseFolder}
>
Choose folder
</Button>
</Box>
</>
)}
{firmwareSource === FirmwareSource.GitPullRequest &&
gitPullRequests !== undefined && (
<>
<Alert severity="warning" sx={styles.dangerZone}>
<AlertTitle>DANGER ZONE</AlertTitle>
Use these sources only if you know what you are doing or was
instructed by project developers
</Alert>
<Box sx={styles.tabContents}>
{!loading && (
<Omnibox
title="Git pull Requests"
options={gitPullRequestOptions ?? []}
currentValue={
gitPullRequestOptions?.find(
(item) =>
item.value === `${currentGitPullRequest?.number}`
) ?? null
}
onChange={onGitPullRequest}
/>
)}
</Box>
</>
)}
<Loader loading={loading} />
<ShowAlerts severity="error" messages={branchesError} />
<ShowAlerts severity="error" messages={tagsError} />
<ShowAlerts severity="error" messages={pullRequestsError} />
</>
);
}
Example #17
Source File: SignIn.tsx From Cromwell with MIT License | 4 votes |
SignInModal = observer(() => {
const authClient = useAuthClient();
const formType = appState.signInFormType;
const activeTab = formType === 'sign-up' ? 1 : 0;
const handleTabChange = (event: React.ChangeEvent<unknown>, newValue: number) => {
if (newValue === 1) appState.signInFormType = 'sign-up';
if (newValue === 0) appState.signInFormType = 'sign-in';
};
const onSignUpSuccess = async (user: TUser, password: string) => {
if (!user.email) return;
// Automatic sign-in after sign-up
const result = await authClient.signIn(user.email, password);
if (result.success) {
toast.success('You have been registered')
handleClose();
} else {
toast.error(result.message);
}
}
const handleClose = () => {
appState.isSignInOpen = false;
appState.signInFormType = 'sign-in';
}
const signInElements: SignInProps['elements'] = {
TextField: (props) => <TextField fullWidth
variant="standard"
size="small"
className={styles.textField}
{...props}
/>,
PasswordField,
Button: (props: any) => <Button
color="primary"
variant="contained"
className={styles.loginBtn}
{...props} />
}
return (
<Modal
className={commonStyles.center}
open={appState.isSignInOpen}
onClose={handleClose}
blurSelector={"#CB_root"}
>
<div className={styles.SingIn}>
<Tabs
className={styles.tabs}
value={activeTab}
indicatorColor="primary"
textColor="primary"
onChange={handleTabChange}
>
<Tab
className={styles.tab}
label="Sign in" />
<Tab
className={styles.tab}
label="Sign up" />
</Tabs>
{formType === 'sign-in' && (
<SignIn
classes={{
root: styles.loginForm,
forgotPassButton: styles.forgotPassText,
backToSignInButton: styles.forgotPassText,
resetPassInstructions: styles.resetPassInstructions,
}}
elements={signInElements}
onSignInSuccess={handleClose}
onSignInError={(result) => toast.error(result.message)}
onForgotPasswordFailure={(result) => toast.error(result.message)}
onResetPasswordFailure={(result) => toast.error(result.message)}
onForgotPasswordEmailSent={() => toast.success('We sent you an email')}
onResetPasswordSuccess={() => toast.success('Password changed')}
/>
)}
{formType === 'sign-up' && (
<SignUp
classes={{ root: styles.loginForm }}
elements={signInElements}
onSignUpSuccess={onSignUpSuccess}
onSignUpError={(result) => toast.error(result.message)}
/>
)}
</div>
</Modal >
)
})
Example #18
Source File: Product.tsx From Cromwell with MIT License | 4 votes |
ProductPage = () => {
const { id: productId } = useParams<{ id: string }>();
const client = getGraphQLClient();
const [isLoading, setIsLoading] = useState(false);
// const [product, setProdData] = useState<TProduct | null>(null);
const [attributes, setAttributes] = useState<TAttribute[]>([]);
const [activeTabNum, setActiveTabNum] = React.useState(0);
const productRef = React.useRef<TProduct | null>(null);
const [notFound, setNotFound] = useState(false);
const [canValidate, setCanValidate] = useState(false);
const forceUpdate = useForceUpdate();
const history = useHistory();
const product: TProduct | undefined = productRef.current;
const setProdData = (data: TProduct) => {
productRef.current = Object.assign({}, productRef.current, data);
}
useEffect(() => {
return () => {
resetSelected();
}
}, []);
const getProduct = async () => {
let prod: TProduct | undefined;
if (productId && productId !== 'new') {
try {
prod = await client?.getProductById(parseInt(productId), gql`
fragment AdminPanelProductFragment on Product {
id
slug
createDate
updateDate
isEnabled
pageTitle
pageDescription
meta {
keywords
}
name
price
oldPrice
sku
mainImage
images
description
descriptionDelta
views
mainCategoryId
stockAmount
stockStatus
manageStock
categories(pagedParams: {pageSize: 9999}) {
id
}
customMeta (keys: ${JSON.stringify(getCustomMetaKeysFor(EDBEntity.Product))})
attributes {
key
values {
value
}
}
variants {
id
name
price
oldPrice
sku
mainImage
images
description
descriptionDelta
stockAmount
stockStatus
manageStock
attributes
}
}`, 'AdminPanelProductFragment'
);
} catch (e) { console.error(e) }
if (prod?.id) {
setProdData(prod);
store.setStateProp({
prop: 'selectedItems',
payload: Object.assign({}, ...(prod.categories ?? []).map(cat => ({ [cat.id]: true }))),
});
store.setStateProp({
prop: 'selectedItem',
payload: prod?.mainCategoryId,
});
forceUpdate();
}
else setNotFound(true);
} else if (productId === 'new') {
setProdData({} as any);
forceUpdate();
}
return prod;
}
const getAttributes = async () => {
try {
const attr = await client?.getAttributes();
if (attr) setAttributes(attr);
} catch (e) { console.error(e) }
}
useEffect(() => {
(async () => {
setIsLoading(true);
await getAttributes();
await getProduct();
setIsLoading(false);
})();
}, []);
const refetchMeta = async () => {
if (!productId) return;
const data = await getProduct();
return data?.customMeta;
};
const handleSave = async () => {
const product = productRef.current;
setCanValidate(true);
if (!product?.name) return;
const productAttributes = product.attributes?.map(attr => ({
key: attr.key,
values: attr.values ? attr.values.map(val => ({
value: val.value,
})) : [],
}));
const selectedItems = store.getState().selectedItems;
const categoryIds = Object.keys(selectedItems)
.filter(id => selectedItems[id]).map(Number).filter(Boolean);
let mainCategoryId = store.getState().selectedItem ?? null;
if (mainCategoryId && !categoryIds.includes(mainCategoryId)) mainCategoryId = null;
if (product) {
const input: TProductInput = {
name: product.name,
categoryIds,
mainCategoryId,
price: typeof product.price === 'string' ? parseFloat(product.price) : product.price,
oldPrice: typeof product.oldPrice === 'string' ? parseFloat(product.oldPrice) : product.oldPrice,
sku: product.sku,
mainImage: product.mainImage,
images: product.images,
stockStatus: product.stockStatus ?? 'In stock',
stockAmount: product.stockAmount,
manageStock: product.manageStock,
description: product.description,
descriptionDelta: product.descriptionDelta,
slug: product.slug,
attributes: productAttributes,
pageTitle: product.pageTitle,
pageDescription: product.pageDescription,
meta: product.meta && {
keywords: product.meta.keywords
},
variants: product.variants?.map(variant => ({
id: typeof variant.id === 'number' ? variant.id : undefined,
name: variant.name,
price: typeof variant.price === 'string' ? parseFloat(variant.price) : variant.price,
oldPrice: typeof variant.oldPrice === 'string' ? parseFloat(variant.oldPrice) : variant.oldPrice,
sku: variant.sku,
mainImage: variant.mainImage,
images: variant.images,
description: variant.description,
descriptionDelta: variant.descriptionDelta,
stockAmount: variant.stockAmount,
stockStatus: variant.stockStatus,
manageStock: variant.manageStock,
attributes: variant.attributes,
})),
customMeta: Object.assign({}, product.customMeta, await getCustomMetaFor(EDBEntity.Product)),
isEnabled: product.isEnabled,
}
if (input.variants?.length) {
// Ensure that variants have at least one specified attribute
input.variants.forEach((variant, i) => {
const filteredAttributes: Record<string, string | number> = {};
Object.entries((variant.attributes ?? {})).forEach(([key, value]) => {
if (value && value !== 'any') filteredAttributes[key] = value;
});
if (!Object.keys(filteredAttributes).length) delete input.variants[i];
});
input.variants = input.variants.filter(Boolean);
}
if (productId === 'new') {
setIsLoading(true);
try {
const prod = await client?.createProduct(input);
if (prod?.id) {
toast.success('Created product');
history.replace(`${productPageInfo.baseRoute}/${prod.slug}`)
if (prod) setProdData(prod);
forceUpdate();
} else {
throw new Error('!prod?.id')
}
} catch (e) {
toast.error('Failed to create');
console.error(e);
}
setIsLoading(false);
} else {
try {
await client?.updateProduct(product.id, input);
await getProduct();
toast.success('Updated product');
} catch (e) {
toast.error('Failed to update');
console.error(e);
}
}
}
setCanValidate(false);
}
const handleTabChange = (event: React.ChangeEvent<unknown>, newValue: number) => {
setActiveTabNum(newValue);
}
if (notFound) {
return (
<div className={styles.Product}>
<div className={styles.notFoundPage}>
<p className={styles.notFoundText}>Product not found</p>
</div>
</div>
)
}
let pageFullUrl;
if (product?.slug) {
pageFullUrl = serviceLocator.getFrontendUrl() + resolvePageRoute('product', { slug: product.slug ?? product.id + '' });
}
return (
<div className={styles.Product}>
{/* <h2>Edit product</h2> */}
<div className={styles.header}>
{/* <p>Product id: {id}</p> */}
<div className={styles.headerLeft}>
<IconButton
onClick={() => window.history.back()}
>
<ArrowBackIcon style={{ fontSize: '18px' }} />
</IconButton>
<p className={commonStyles.pageTitle}>product</p>
</div>
<div >
<Tabs
value={activeTabNum}
indicatorColor="primary"
textColor="primary"
onChange={handleTabChange}
>
<Tab label="Main" />
<Tab label="Attributes" />
<Tab label="Variants" />
<Tab label="Categories" />
</Tabs>
</div>
<div className={styles.headerActions}>
{pageFullUrl && (
<Tooltip title="Open product in the new tab">
<IconButton
className={styles.openPageBtn}
aria-label="open"
onClick={() => { window.open(pageFullUrl, '_blank'); }}
>
<OpenInNewIcon />
</IconButton>
</Tooltip>
)}
<Button variant="contained"
color="primary"
size="small"
className={styles.saveBtn}
onClick={handleSave}>
Save
</Button>
</div>
</div>
{isLoading && <Skeleton width="100%" height="100%" style={{
transform: 'none',
margin: '20px 0'
}} />}
{!isLoading && product && (
<>
<TabPanel value={activeTabNum} index={0}>
<div className={styles.mainTab}>
<MainInfoCard
product={product}
setProdData={setProdData}
canValidate={canValidate}
/>
<div style={{ marginBottom: '15px' }}></div>
<RenderCustomFields
entityType={EDBEntity.Product}
entityData={product}
refetchMeta={refetchMeta}
/>
</div>
</TabPanel>
<TabPanel value={activeTabNum} index={1}>
<AttributesTab
forceUpdate={forceUpdate}
product={product}
attributes={attributes}
setProdData={setProdData}
/>
</TabPanel>
<TabPanel value={activeTabNum} index={2}>
<VariantsTab
forceUpdate={forceUpdate}
product={product}
attributes={attributes}
setProdData={setProdData}
/>
</TabPanel>
<TabPanel value={activeTabNum} index={3}>
<CategoriesTab />
</TabPanel>
</>
)}
</div >
)
}