Added ui
This commit is contained in:
153
ui/src/components/SpellModal.tsx
Normal file
153
ui/src/components/SpellModal.tsx
Normal file
@@ -0,0 +1,153 @@
|
||||
'use client';
|
||||
|
||||
import { Spell } from '@/api/spells.types';
|
||||
import { levelText, rollDice } from '@/js/spells';
|
||||
import { capitalize } from '@/js/utils';
|
||||
import { Grid, Modal } from '@mantine/core';
|
||||
import { notifications } from '@mantine/notifications';
|
||||
|
||||
interface SpellModalProps {
|
||||
spell: Spell;
|
||||
isOpen: boolean;
|
||||
onClose(): void;
|
||||
}
|
||||
|
||||
export default function SpellModal({ spell, isOpen, onClose }: SpellModalProps) {
|
||||
return (
|
||||
<Modal opened={isOpen} onClose={onClose} withCloseButton={false} size={'50%'} className='modal'>
|
||||
<h1 style={{ padding: '0', margin: '0' }}>{spell.name}</h1>
|
||||
<Grid gutter={1}>
|
||||
<Grid.Col span={4} style={{ paddingBottom: '1rem' }}>
|
||||
<span style={{ fontWeight: 'bold' }}>
|
||||
{capitalize(spell.school)} {levelText(spell)}
|
||||
</span>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={8} style={{ paddingBottom: '1rem' }}>
|
||||
<div style={{ float: 'right' }}>
|
||||
<span style={{ float: 'right' }}>
|
||||
{spell.components.verbal && spell.components.somatic ? 'V, ' : 'V '}
|
||||
{spell.components.somatic && spell.components.material ? 'S, ' : 'S '}
|
||||
{spell.components.material && spell.components.materials_needed ? 'M*' : 'M'}
|
||||
</span>
|
||||
{spell.components.materials_needed && (
|
||||
<span style={{ fontSize: '0.8em', color: 'gray' }}>
|
||||
<br />*{capitalize(spell.components.materials_needed)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={6}>
|
||||
<span style={{ fontWeight: 'bold', paddingRight: '1em' }}>Sources:</span>
|
||||
{spell.sources.map((s) => (
|
||||
<span style={{ paddingRight: '0.6em' }}>
|
||||
{s.source}
|
||||
{s.page ? `.${s.page}` : ''}
|
||||
</span>
|
||||
))}
|
||||
</Grid.Col>
|
||||
<Grid.Col span={6}>
|
||||
<span style={{ fontWeight: 'bold', marginRight: '1em' }}>Classes:</span>
|
||||
<span style={{ overflowWrap: 'break-word' }}>
|
||||
{spell.classes.map((c) => (
|
||||
<span style={{ paddingRight: '0.6em', display: 'inline-block' }} className='link'>
|
||||
{capitalize(c)}
|
||||
</span>
|
||||
))}
|
||||
</span>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={6}>
|
||||
<span style={{ fontWeight: 'bold', paddingRight: '1em' }}>Casting Time:</span>
|
||||
<span style={{ paddingRight: '0.6em' }}>
|
||||
{spell.casting_time.value} {capitalize(spell.casting_time.unit)}
|
||||
</span>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={6}>
|
||||
<span style={{ fontWeight: 'bold', paddingRight: '1em' }}>Range:</span>
|
||||
<span style={{ paddingRight: '0.6em' }}>
|
||||
{spell.range.type != 'point' && capitalize(spell.range.type)} {spell.range.value}{' '}
|
||||
{capitalize(spell.range.unit)}
|
||||
</span>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={6}>
|
||||
<span style={{ fontWeight: 'bold', paddingRight: '1em' }}>Duration:</span>
|
||||
<span style={{ paddingRight: '0.6em' }}>
|
||||
{spell.durations.map((d) => (
|
||||
<span>
|
||||
{capitalize(d.type)} {d.value} {capitalize(d.unit)}
|
||||
</span>
|
||||
))}
|
||||
</span>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={12}>
|
||||
<SpellDescription spell={spell} />
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
function SpellDescription({ spell }: { spell: Spell }) {
|
||||
function parseText(text: string) {
|
||||
const regex = /{@(.*?) (.*?)}/g;
|
||||
const matches = text.matchAll(regex);
|
||||
const result = [];
|
||||
let lastIndex = 0;
|
||||
for (const match of matches) {
|
||||
const [full, type, name] = match;
|
||||
result.push(text.slice(lastIndex, match.index));
|
||||
if (match.index !== undefined) {
|
||||
result.push(
|
||||
<span onClick={() => handleLink(type, name)} className='link'>
|
||||
{name}
|
||||
</span>
|
||||
);
|
||||
lastIndex = match.index + full.length;
|
||||
}
|
||||
}
|
||||
result.push(text.slice(lastIndex));
|
||||
return result;
|
||||
}
|
||||
|
||||
function handleLink(type: string, name: string) {
|
||||
if (type == 'spell') {
|
||||
console.log(`Link to spell: ${name}`);
|
||||
} else if (type == 'dice' || type == 'damage') {
|
||||
const rolls = rollDice(name);
|
||||
notifications.show({
|
||||
title: `Rolling ${name}`,
|
||||
message: `${rolls.join(' + ')} = ${rolls.reduce((a, b) => a + b, 0)}`,
|
||||
color: 'blue',
|
||||
autoClose: 5000,
|
||||
withCloseButton: false
|
||||
});
|
||||
} else {
|
||||
console.error(`Unknown link type: ${type}`);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{spell.description && (
|
||||
<>
|
||||
{spell.description.entries.map((e) =>
|
||||
typeof e === 'string' ? (
|
||||
<p>{parseText(e)}</p>
|
||||
) : (
|
||||
<>
|
||||
{e.type == 'list' ? (
|
||||
<ul>
|
||||
{e.items.map((text) => (
|
||||
<li>{parseText(text)}</li>
|
||||
))}
|
||||
</ul>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user