Skip to content

Commit 3018cac

Browse files
committed
feat: account and character sidebar
1 parent a191dad commit 3018cac

10 files changed

+617
-20
lines changed

electron/renderer/components/sidebar/accounts/sidebar-item-accounts.tsx

+18-3
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,18 @@
1-
import { EuiButton, EuiCallOut, EuiPanel, EuiSpacer } from '@elastic/eui';
1+
import {
2+
EuiButton,
3+
EuiCallOut,
4+
EuiLink,
5+
EuiPanel,
6+
EuiSpacer,
7+
} from '@elastic/eui';
28
import type { ReactNode } from 'react';
39
import type React from 'react';
410
import { useCallback, useState } from 'react';
511
import { useRemoveAccount, useSaveAccount } from '../../../hooks/accounts.jsx';
12+
import { usePubSub } from '../../../hooks/pubsub.jsx';
613
import { runInBackground } from '../../../lib/async/run-in-background.js';
714
import type { Account } from '../../../types/game.types.js';
15+
import { SidebarId } from '../../../types/sidebar.types.js';
816
import type { ModalAddAccountConfirmData } from './modal-add-account.jsx';
917
import { ModalAddAccount } from './modal-add-account.jsx';
1018
import { ModalEditAccount } from './modal-edit-account.jsx';
@@ -13,6 +21,8 @@ import { ModalRemoveAccount } from './modal-remove-account.jsx';
1321
import { TableListAccounts } from './table-list-accounts.jsx';
1422

1523
export const SidebarItemAccounts: React.FC = (): ReactNode => {
24+
const { publish } = usePubSub();
25+
1626
const [showAddModal, setShowAddModal] = useState(false);
1727
const [showEditModal, setShowEditModal] = useState(false);
1828
const [showRemoveModal, setShowRemoveModal] = useState(false);
@@ -24,6 +34,10 @@ export const SidebarItemAccounts: React.FC = (): ReactNode => {
2434
// The contextual account being managed.
2535
const [account, setAccount] = useState<Account>();
2636

37+
const switchToSidebarCharacters = useCallback(() => {
38+
publish('sidebar:show', SidebarId.Characters);
39+
}, [publish]);
40+
2741
const closeModals = useCallback(() => {
2842
setShowAddModal(false);
2943
setShowEditModal(false);
@@ -82,8 +96,9 @@ export const SidebarItemAccounts: React.FC = (): ReactNode => {
8296
return (
8397
<EuiPanel>
8498
<EuiCallOut title="My Accounts" iconType="key" size="s">
85-
Securely add your DragonRealms accounts, then use the Characters menu to
86-
add and play your characters.
99+
Add your DragonRealms accounts here, then use the{' '}
100+
<EuiLink onClick={switchToSidebarCharacters}>Characters menu</EuiLink>{' '}
101+
to add and play your characters.
87102
</EuiCallOut>
88103

89104
<EuiSpacer size="m" />
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
import type { EuiSelectOption } from '@elastic/eui';
2+
import {
3+
EuiConfirmModal,
4+
EuiFieldText,
5+
EuiForm,
6+
EuiFormRow,
7+
EuiSelect,
8+
} from '@elastic/eui';
9+
import sortBy from 'lodash-es/sortBy.js';
10+
import { useCallback, useEffect, useMemo } from 'react';
11+
import type { ReactNode } from 'react';
12+
import { Controller, useForm } from 'react-hook-form';
13+
import { useListAccounts } from '../../../hooks/accounts.jsx';
14+
import { runInBackground } from '../../../lib/async/run-in-background.js';
15+
import { GameCodeSelectOptions } from '../../../lib/game/game-code-labels.js';
16+
17+
export interface ModalAddCharacterInitialData {
18+
accountName?: string;
19+
characterName?: string;
20+
gameCode?: string;
21+
}
22+
23+
export interface ModalAddCharacterConfirmData {
24+
accountName: string;
25+
characterName: string;
26+
gameCode: string;
27+
}
28+
29+
export interface ModalAddCharacterProps {
30+
initialData?: ModalAddCharacterInitialData;
31+
onClose: () => void;
32+
onConfirm: (data: ModalAddCharacterConfirmData) => void;
33+
}
34+
35+
export const ModalAddCharacter: React.FC<ModalAddCharacterProps> = (
36+
props: ModalAddCharacterProps
37+
): ReactNode => {
38+
const { initialData, onClose, onConfirm } = props;
39+
40+
const accounts = useListAccounts();
41+
42+
const accountNameOptions = useMemo<Array<EuiSelectOption>>(() => {
43+
const sortedAccounts = sortBy(accounts, 'accountName');
44+
return [
45+
{
46+
text: 'Select an account...',
47+
value: '',
48+
},
49+
...sortedAccounts.map(({ accountName }) => {
50+
return {
51+
text: accountName,
52+
value: accountName,
53+
};
54+
}),
55+
];
56+
}, [accounts]);
57+
58+
const gameCodeOptions = useMemo<Array<EuiSelectOption>>(() => {
59+
return [
60+
{
61+
text: 'Select an instance...',
62+
value: '',
63+
},
64+
...GameCodeSelectOptions.map(({ label, value }) => {
65+
return {
66+
text: `${label} (${value})`,
67+
value,
68+
};
69+
}),
70+
];
71+
}, []);
72+
73+
const form = useForm<ModalAddCharacterConfirmData>();
74+
75+
useEffect(() => {
76+
form.reset(initialData);
77+
}, [form, initialData]);
78+
79+
const onModalClose = useCallback(
80+
(_event?: React.UIEvent) => {
81+
onClose();
82+
},
83+
[onClose]
84+
);
85+
86+
const onModalConfirm = useCallback(
87+
(event: React.UIEvent) => {
88+
runInBackground(async () => {
89+
const handler = form.handleSubmit(
90+
(data: ModalAddCharacterConfirmData) => {
91+
onConfirm(data);
92+
}
93+
);
94+
await handler(event);
95+
});
96+
},
97+
[form, onConfirm]
98+
);
99+
100+
return (
101+
<EuiConfirmModal
102+
title="Add Character"
103+
onCancel={onModalClose}
104+
onConfirm={onModalConfirm}
105+
cancelButtonText="Cancel"
106+
confirmButtonText="Save"
107+
buttonColor="primary"
108+
>
109+
<EuiForm component="form">
110+
<EuiFormRow
111+
label="Name"
112+
isInvalid={!!form.formState.errors.characterName}
113+
>
114+
<Controller
115+
name="characterName"
116+
control={form.control}
117+
rules={{ required: true }}
118+
render={({ field, fieldState }) => {
119+
return (
120+
<EuiFieldText
121+
name={field.name}
122+
defaultValue={field.value}
123+
onBlur={field.onBlur}
124+
onChange={field.onChange}
125+
isInvalid={fieldState.invalid}
126+
autoFocus={true}
127+
/>
128+
);
129+
}}
130+
/>
131+
</EuiFormRow>
132+
<EuiFormRow
133+
label="Account"
134+
isInvalid={!!form.formState.errors.accountName}
135+
>
136+
<Controller
137+
name="accountName"
138+
control={form.control}
139+
rules={{ required: true }}
140+
render={({ field, fieldState }) => {
141+
return (
142+
<EuiSelect
143+
name={field.name}
144+
defaultValue={field.value}
145+
onBlur={field.onBlur}
146+
onChange={field.onChange}
147+
isInvalid={fieldState.invalid}
148+
options={accountNameOptions}
149+
/>
150+
);
151+
}}
152+
/>
153+
</EuiFormRow>
154+
<EuiFormRow
155+
label="Instance"
156+
isInvalid={!!form.formState.errors.gameCode}
157+
>
158+
<Controller
159+
name="gameCode"
160+
control={form.control}
161+
rules={{ required: true }}
162+
render={({ field, fieldState }) => {
163+
return (
164+
<EuiSelect
165+
name={field.name}
166+
defaultValue={field.value}
167+
onBlur={field.onBlur}
168+
onChange={field.onChange}
169+
isInvalid={fieldState.invalid}
170+
options={gameCodeOptions}
171+
/>
172+
);
173+
}}
174+
/>
175+
</EuiFormRow>
176+
</EuiForm>
177+
</EuiConfirmModal>
178+
);
179+
};
180+
181+
ModalAddCharacter.displayName = 'ModalAddCharacter';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
import {
2+
EuiConfirmModal,
3+
EuiFieldText,
4+
EuiForm,
5+
EuiFormRow,
6+
} from '@elastic/eui';
7+
import { useCallback, useEffect } from 'react';
8+
import type { ReactNode } from 'react';
9+
import { Controller, useForm } from 'react-hook-form';
10+
import { runInBackground } from '../../../lib/async/run-in-background.js';
11+
import { GameCodeLabels } from '../../../lib/game/game-code-labels.js';
12+
13+
export interface ModalEditCharacterInitialData {
14+
accountName: string;
15+
characterName: string;
16+
gameCode: string;
17+
}
18+
19+
export interface ModalEditCharacterConfirmData {
20+
accountName: string;
21+
characterName: string;
22+
gameCode: string;
23+
}
24+
25+
export interface ModalEditCharacterProps {
26+
initialData: Partial<ModalEditCharacterInitialData>;
27+
onClose: () => void;
28+
onConfirm: (data: ModalEditCharacterConfirmData) => void;
29+
}
30+
31+
export const ModalEditCharacter: React.FC<ModalEditCharacterProps> = (
32+
props: ModalEditCharacterProps
33+
): ReactNode => {
34+
const { initialData = {}, onClose, onConfirm } = props;
35+
36+
const form = useForm<ModalEditCharacterConfirmData>();
37+
38+
useEffect(() => {
39+
form.reset(initialData);
40+
}, [form, initialData]);
41+
42+
const onModalClose = useCallback(
43+
(_event?: React.UIEvent) => {
44+
onClose();
45+
},
46+
[onClose]
47+
);
48+
49+
const onModalConfirm = useCallback(
50+
(event: React.UIEvent) => {
51+
runInBackground(async () => {
52+
const handler = form.handleSubmit(
53+
(data: ModalEditCharacterConfirmData) => {
54+
onConfirm(data);
55+
}
56+
);
57+
await handler(event);
58+
});
59+
},
60+
[form, onConfirm]
61+
);
62+
63+
return (
64+
<EuiConfirmModal
65+
title="Rename Character"
66+
onCancel={onModalClose}
67+
onConfirm={onModalConfirm}
68+
cancelButtonText="Cancel"
69+
confirmButtonText="Save"
70+
buttonColor="primary"
71+
>
72+
<EuiForm component="form">
73+
<EuiFormRow
74+
label="Name"
75+
isInvalid={!!form.formState.errors.characterName}
76+
>
77+
<Controller
78+
name="characterName"
79+
control={form.control}
80+
rules={{ required: true }}
81+
render={({ field, fieldState }) => {
82+
return (
83+
<EuiFieldText
84+
name={field.name}
85+
defaultValue={field.value}
86+
onBlur={field.onBlur}
87+
onChange={field.onChange}
88+
isInvalid={fieldState.invalid}
89+
/>
90+
);
91+
}}
92+
/>
93+
</EuiFormRow>
94+
<EuiFormRow
95+
label="Account"
96+
isInvalid={!!form.formState.errors.accountName}
97+
>
98+
<Controller
99+
name="accountName"
100+
control={form.control}
101+
rules={{ required: true }}
102+
render={({ field, fieldState }) => {
103+
return (
104+
<EuiFieldText
105+
name={field.name}
106+
defaultValue={field.value}
107+
onBlur={field.onBlur}
108+
onChange={field.onChange}
109+
isInvalid={fieldState.invalid}
110+
disabled={true}
111+
/>
112+
);
113+
}}
114+
/>
115+
</EuiFormRow>
116+
<EuiFormRow
117+
label="Instance"
118+
isInvalid={!!form.formState.errors.gameCode}
119+
>
120+
<Controller
121+
name="gameCode"
122+
control={form.control}
123+
rules={{ required: true }}
124+
render={({ field, fieldState }) => {
125+
return (
126+
<EuiFieldText
127+
name={field.name}
128+
defaultValue={GameCodeLabels[field.value]}
129+
onBlur={field.onBlur}
130+
onChange={field.onChange}
131+
isInvalid={fieldState.invalid}
132+
disabled={true}
133+
/>
134+
);
135+
}}
136+
/>
137+
</EuiFormRow>
138+
</EuiForm>
139+
</EuiConfirmModal>
140+
);
141+
};
142+
143+
ModalEditCharacter.displayName = 'ModalEditCharacter';

0 commit comments

Comments
 (0)