diff options
Diffstat (limited to 'Timeline/ClientApp/src/timeline')
6 files changed, 77 insertions, 56 deletions
diff --git a/Timeline/ClientApp/src/timeline/TimelineDeleteDialog.tsx b/Timeline/ClientApp/src/timeline/TimelineDeleteDialog.tsx index 2b682a6b..e2d8ad5a 100644 --- a/Timeline/ClientApp/src/timeline/TimelineDeleteDialog.tsx +++ b/Timeline/ClientApp/src/timeline/TimelineDeleteDialog.tsx @@ -4,7 +4,7 @@ import { useHistory } from 'react-router'; import { Trans } from 'react-i18next';
import { apiBaseUrl } from '../config';
-import { useUser } from '../data/user';
+import { useUserLoggedIn } from '../data/user';
import OperationDialog from '../common/OperationDialog';
interface TimelineDeleteDialog {
@@ -13,8 +13,8 @@ interface TimelineDeleteDialog { close: () => void;
}
-const TimelineDeleteDialog: React.FC<TimelineDeleteDialog> = props => {
- const user = useUser()!;
+const TimelineDeleteDialog: React.FC<TimelineDeleteDialog> = (props) => {
+ const user = useUserLoggedIn();
const history = useHistory();
const { name } = props;
@@ -35,14 +35,14 @@ const TimelineDeleteDialog: React.FC<TimelineDeleteDialog> = props => { inputScheme={[
{
type: 'text',
- validator: value => {
+ validator: (value) => {
if (value !== name) {
return 'timeline.deleteDialog.notMatch';
} else {
return null;
}
- }
- }
+ },
+ },
]}
onProcess={() => {
return axios.delete(
diff --git a/Timeline/ClientApp/src/timeline/TimelineInfoCard.tsx b/Timeline/ClientApp/src/timeline/TimelineInfoCard.tsx index 2ce7c378..b3af4cab 100644 --- a/Timeline/ClientApp/src/timeline/TimelineInfoCard.tsx +++ b/Timeline/ClientApp/src/timeline/TimelineInfoCard.tsx @@ -28,6 +28,7 @@ const TimelineInfoCard: React.FC<TimelineInfoCardProps> = (props) => { const { t } = useTranslation();
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const containerRef = React.useRef<HTMLDivElement>(null!);
const notifyHeight = React.useCallback((): void => {
@@ -48,13 +49,6 @@ const TimelineInfoCard: React.FC<TimelineInfoCardProps> = (props) => { (): void => setManageDropdownOpen((old) => !old),
[]
);
- const onManageProperty = React.useCallback(
- (): void => onManage!('property'),
- [onManage]
- );
- const onManageDelete = React.useCallback((): void => onManage!('delete'), [
- onManage,
- ]);
return (
<div
@@ -81,20 +75,23 @@ const TimelineInfoCard: React.FC<TimelineInfoCardProps> = (props) => { {t(timelineVisibilityTooltipTranslationMap[props.timeline.visibility])}
</small>
<div className="text-right mt-2">
- {props.onManage != null ? (
+ {onManage != null ? (
<Dropdown isOpen={manageDropdownOpen} toggle={toggleManageDropdown}>
<DropdownToggle outline color="primary">
{t('timeline.manage')}
</DropdownToggle>
<DropdownMenu>
- <DropdownItem onClick={onManageProperty}>
+ <DropdownItem onClick={() => onManage('property')}>
{t('timeline.manageItem.property')}
</DropdownItem>
<DropdownItem onClick={props.onMember}>
{t('timeline.manageItem.member')}
</DropdownItem>
<DropdownItem divider />
- <DropdownItem className="text-danger" onClick={onManageDelete}>
+ <DropdownItem
+ className="text-danger"
+ onClick={() => onManage('delete')}
+ >
{t('timeline.manageItem.delete')}
</DropdownItem>
</DropdownMenu>
diff --git a/Timeline/ClientApp/src/timeline/TimelineMember.tsx b/Timeline/ClientApp/src/timeline/TimelineMember.tsx index eac8d417..b8bb49ee 100644 --- a/Timeline/ClientApp/src/timeline/TimelineMember.tsx +++ b/Timeline/ClientApp/src/timeline/TimelineMember.tsx @@ -11,7 +11,7 @@ import { Modal,
Row,
Col,
- Button
+ Button,
} from 'reactstrap';
export interface TimelineMemberCallbacks {
@@ -25,7 +25,7 @@ export interface TimelineMemberProps { edit: TimelineMemberCallbacks | null | undefined;
}
-const TimelineMember: React.FC<TimelineMemberProps> = props => {
+const TimelineMember: React.FC<TimelineMemberProps> = (props) => {
const { t } = useTranslation();
const [userSearchText, setUserSearchText] = useState<string>('');
@@ -87,7 +87,7 @@ const TimelineMember: React.FC<TimelineMemberProps> = props => { <>
<SearchInput
value={userSearchText}
- onChange={v => {
+ onChange={(v) => {
setUserSearchText(v);
}}
loading={userSearchState.type === 'loading'}
@@ -95,27 +95,27 @@ const TimelineMember: React.FC<TimelineMemberProps> = props => { if (userSearchText === '') {
setUserSearchState({
type: 'error',
- data: 'login.emptyUsername'
+ data: 'login.emptyUsername',
});
return;
}
setUserSearchState({ type: 'loading' });
edit.onCheckUser(userSearchText).then(
- u => {
+ (u) => {
if (u == null) {
setUserSearchState({
type: 'error',
- data: 'timeline.userNotExist'
+ data: 'timeline.userNotExist',
});
} else {
setUserSearchState({ type: 'user', data: u });
}
},
- e => {
+ (e) => {
setUserSearchState({
type: 'error',
- data: e.toString()
+ data: `${e as string}`,
});
}
);
@@ -125,7 +125,7 @@ const TimelineMember: React.FC<TimelineMemberProps> = props => { if (userSearchState.type === 'user') {
const u = userSearchState.data;
const addable =
- members.findIndex(m => m.username === u.username) === -1;
+ members.findIndex((m) => m.username === u.username) === -1;
return (
<>
{!addable ? (
@@ -150,7 +150,7 @@ const TimelineMember: React.FC<TimelineMemberProps> = props => { className="align-self-center"
disabled={!addable}
onClick={() => {
- edit.onAddUser(u).then(_ => {
+ void edit.onAddUser(u).then((_) => {
setUserSearchText('');
setUserSearchState({ type: 'init' });
});
@@ -185,7 +185,9 @@ export interface TimelineMemberDialogProps extends TimelineMemberProps { onClose: () => void;
}
-export const TimelineMemberDialog: React.FC<TimelineMemberDialogProps> = props => {
+export const TimelineMemberDialog: React.FC<TimelineMemberDialogProps> = (
+ props
+) => {
return (
<Modal isOpen={props.open} toggle={props.onClose}>
<TimelineMember {...props} />
diff --git a/Timeline/ClientApp/src/timeline/TimelinePageTemplate.tsx b/Timeline/ClientApp/src/timeline/TimelinePageTemplate.tsx index 3660ad78..2cfb4341 100644 --- a/Timeline/ClientApp/src/timeline/TimelinePageTemplate.tsx +++ b/Timeline/ClientApp/src/timeline/TimelinePageTemplate.tsx @@ -19,6 +19,7 @@ import { TimelineMemberDialog } from './TimelineMember'; import TimelinePropertyChangeDialog from './TimelinePropertyChangeDialog';
import { TimelinePageTemplateUIProps } from './TimelinePageTemplateUI';
import { TimelinePostSendCallback } from './TimelinePostEdit';
+import { UiLogicError } from '../common';
export interface TimelinePageTemplateProps<
TManageItem,
@@ -88,7 +89,7 @@ export default function TimelinePageTemplate< },
(error) => {
if (subscribe) {
- setError(error.toString());
+ setError(`${error as string}`);
}
}
);
@@ -129,13 +130,19 @@ export default function TimelinePageTemplate< let dialogElement: React.ReactElement | undefined;
if (dialog === 'property') {
+ if (timeline == null) {
+ throw new UiLogicError(
+ 'Timeline is null but attempt to open change property dialog.'
+ );
+ }
+
dialogElement = (
<TimelinePropertyChangeDialog
open
close={closeDialog}
oldInfo={{
- visibility: timeline!.visibility,
- description: timeline!.description,
+ visibility: timeline.visibility,
+ description: timeline.description,
}}
onProcess={(req) => {
return service.changeProperty(name, req).then((newTimeline) => {
@@ -145,13 +152,19 @@ export default function TimelinePageTemplate< />
);
} else if (dialog === 'member') {
+ if (timeline == null) {
+ throw new UiLogicError(
+ 'Timeline is null but attempt to open change property dialog.'
+ );
+ }
+
dialogElement = (
<TimelineMemberDialog
open
onClose={closeDialog}
- members={[timeline!.owner, ...timeline!.members]}
+ members={[timeline.owner, ...timeline.members]}
edit={
- service.hasManagePermission(user, timeline!)
+ service.hasManagePermission(user, timeline)
? {
onCheckUser: (u) => {
return fetchUser(u).catch((e) => {
@@ -168,18 +181,18 @@ export default function TimelinePageTemplate< onAddUser: (u) => {
return service.addMember(name, u.username).then((_) => {
setTimeline({
- ...timeline!,
- members: concat(timeline!.members, u),
+ ...timeline,
+ members: concat(timeline.members, u),
});
});
},
onRemoveUser: (u) => {
- service.removeMember(name, u).then((_) => {
+ void service.removeMember(name, u).then((_) => {
setTimeline({
- ...timeline!,
+ ...timeline,
members: without(
- timeline!.members,
- timeline!.members.find((m) => m.username === u)
+ timeline.members,
+ timeline.members.find((m) => m.username === u)
),
});
});
diff --git a/Timeline/ClientApp/src/timeline/TimelinePageTemplateUI.tsx b/Timeline/ClientApp/src/timeline/TimelinePageTemplateUI.tsx index d96b3260..22fb1987 100644 --- a/Timeline/ClientApp/src/timeline/TimelinePageTemplateUI.tsx +++ b/Timeline/ClientApp/src/timeline/TimelinePageTemplateUI.tsx @@ -44,7 +44,7 @@ export default function TimelinePageTemplateUI<TTimeline, TEditItems>( const onPostEditHeightChange = React.useCallback((height: number): void => {
const { current: bottomSpaceDiv } = bottomSpaceRef;
if (bottomSpaceDiv != null) {
- bottomSpaceDiv.style.height = height + 'px';
+ bottomSpaceDiv.style.height = `${height}px`;
}
if (height === 0) {
const alertHost = getAlertHost();
@@ -54,7 +54,7 @@ export default function TimelinePageTemplateUI<TTimeline, TEditItems>( } else {
const alertHost = getAlertHost();
if (alertHost != null) {
- alertHost.style.marginBottom = height + 'px';
+ alertHost.style.marginBottom = `${height}px`;
}
}
}, []);
diff --git a/Timeline/ClientApp/src/timeline/TimelinePostEdit.tsx b/Timeline/ClientApp/src/timeline/TimelinePostEdit.tsx index fe1fda9b..2b76b03b 100644 --- a/Timeline/ClientApp/src/timeline/TimelinePostEdit.tsx +++ b/Timeline/ClientApp/src/timeline/TimelinePostEdit.tsx @@ -1,12 +1,13 @@ import React from 'react';
import clsx from 'clsx';
-import { Container, Button, Spinner, Row, Col } from 'reactstrap';
+import { Button, Spinner, Row, Col } from 'reactstrap';
import { useTranslation } from 'react-i18next';
import { pushAlert } from '../common/alert-service';
import { CreatePostRequest } from '../data/timeline';
import FileInput from '../common/FileInput';
+import { UiLogicError } from '../common';
interface TimelinePostEditImageProps {
onSelect: (blob: Blob | null) => void;
@@ -96,11 +97,12 @@ const TimelinePostEdit: React.FC<TimelinePostEditProps> = (props) => { const canSend = kind === 'text' || (kind === 'image' && imageBlob != null);
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+ const containerRef = React.useRef<HTMLDivElement>(null!);
+
React.useEffect(() => {
if (props.onHeightChange) {
- props.onHeightChange(
- document.getElementById('timeline-post-edit-area')!.clientHeight
- );
+ props.onHeightChange(containerRef.current.clientHeight);
}
return () => {
if (props.onHeightChange) {
@@ -117,20 +119,31 @@ const TimelinePostEdit: React.FC<TimelinePostEditProps> = (props) => { const onSend = React.useCallback(() => {
setState('process');
- const req: CreatePostRequest =
- kind === 'text'
- ? {
+ const req: CreatePostRequest = (() => {
+ switch (kind) {
+ case 'text':
+ return {
content: {
type: 'text',
text: text,
},
+ } as CreatePostRequest;
+ case 'image':
+ if (imageBlob == null) {
+ throw new UiLogicError(
+ 'Content type is image but image blob is null.'
+ );
}
- : {
+ return {
content: {
type: 'image',
- data: imageBlob!,
+ data: imageBlob,
},
- };
+ } as CreatePostRequest;
+ default:
+ throw new UiLogicError('Unknown content type.');
+ }
+ })();
onPost(req).then(
(_) => {
@@ -155,11 +168,7 @@ const TimelinePostEdit: React.FC<TimelinePostEditProps> = (props) => { }, []);
return (
- <Container
- id="timeline-post-edit-area"
- fluid
- className="fixed-bottom bg-light"
- >
+ <div ref={containerRef} className="container-fluid fixed-bottom bg-light">
<Row>
<Col className="px-0">
{kind === 'text' ? (
@@ -198,7 +207,7 @@ const TimelinePostEdit: React.FC<TimelinePostEditProps> = (props) => { })()}
</Col>
</Row>
- </Container>
+ </div>
);
};
|