Skip to content

Commit

Permalink
Add post url when live started feature (#160)
Browse files Browse the repository at this point in the history
  • Loading branch information
nzws authored Mar 29, 2023
1 parent 32b7823 commit 7033cc0
Show file tree
Hide file tree
Showing 5 changed files with 421 additions and 84 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@ import {
} from '@chakra-ui/react';
import { FC, useCallback, useState } from 'react';
import { LivePrivate } from 'api-types/common/types';
import { Dialog } from './dialog';
import { LiveInfoModal } from './live-info-modal';
import { Dialog } from '../dialog';
import { LiveInfoModal } from '../live-info-modal';
import { useAuth } from '~/utils/hooks/use-auth';
import { client } from '~/utils/api/client';
import { useAPIError } from '~/utils/hooks/api/use-api-error';
import { StartModal } from '../start-modal';

type Props = {
live: LivePrivate;
Expand Down Expand Up @@ -40,42 +41,52 @@ export const GeneralSettings: FC<Props> = ({ live, notPushing }) => {
useAPIError(error);

const handlePublish = useCallback(
(isStart: boolean) => {
void (async () => {
if (!token) {
return;
}
async (isStart: boolean) => {
if (!token) {
return;
}

try {
await client.v1.streams._liveId(live.id).action.post({
body: {
command: isStart ? 'publish' : 'end'
},
headers: {
Authorization: `Bearer ${token}`
}
});
onCloseStop();
onCloseStart();
} catch (e) {
console.warn(e);
setError(e);
}
})();
try {
await client.v1.streams._liveId(live.id).action.post({
body: {
command: isStart ? 'publish' : 'end'
},
headers: {
Authorization: `Bearer ${token}`
}
});
} catch (e) {
console.warn(e);
setError(e);

throw e;
}
},
[live.id, token, onCloseStart, onCloseStop]
[live.id, token]
);

const handleStart = useCallback(async () => {
await handlePublish(true);
}, [handlePublish]);

const handleEnd = useCallback(() => {
void (async () => {
await handlePublish(false);
onCloseStop();
})();
}, [handlePublish, onCloseStop]);

return (
<Stack spacing={6}>
<Wrap spacing={6}>
<WrapItem>
<Dialog
<StartModal
isOpen={isOpenStart}
onClose={onCloseStart}
onSubmit={() => handlePublish(true)}
title="配信を開始しますか?"
submitText="配信を開始"
onPublish={handleStart}
url={live.publicUrl}
hashtag={live.hashtag}
isBroadcastViaBrowser={false}
/>

<Tooltip
Expand All @@ -97,7 +108,7 @@ export const GeneralSettings: FC<Props> = ({ live, notPushing }) => {
<Dialog
isOpen={isOpenStop}
onClose={onCloseStop}
onSubmit={() => handlePublish(false)}
onSubmit={handleEnd}
title="配信を終了しますか?"
submitText="配信を終了"
/>
Expand Down
251 changes: 251 additions & 0 deletions apps/web/organisms/live/admin/start-modal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
import {
Button,
Modal,
ModalOverlay,
ModalContent,
ModalHeader,
ModalFooter,
ModalBody,
ModalCloseButton,
Stack,
FormControl,
Checkbox,
FormLabel,
Textarea,
Collapse,
useToast,
Alert,
AlertIcon
} from '@chakra-ui/react';
import { FC, useCallback, useEffect, useState } from 'react';
import { useIntl } from 'react-intl';
import {
MisskeyRequestReSignError,
postToExternal,
Visibility
} from '~/utils/api/post-to-external';
import {
MASTODON_DOMAIN_LS,
MISSKEY_DOMAIN_LS,
SignInType
} from '~/utils/contexts/auth';
import { useAPIError } from '~/utils/hooks/api/use-api-error';
import { useUsersMe } from '~/utils/hooks/api/use-users-me';
import { useAuth } from '~/utils/hooks/use-auth';

type Props = {
onPublish: () => Promise<void>;
url: string;
hashtag?: string;
isOpen: boolean;
onClose: () => void;
isBroadcastViaBrowser: boolean;
};

export const StartModal: FC<Props> = ({
onPublish,
url,
hashtag,
isOpen,
onClose,
isBroadcastViaBrowser
}) => {
const { mastodonToken, misskeyToken } = useAuth();
const toast = useToast();
const intl = useIntl();
const [me] = useUsersMe();
const [isLoading, setIsLoading] = useState(false);
const [publishPost, setPublishPost] = useState(true);
const [postText, setPostText] = useState('');
const [postError, setPostError] = useState<unknown>();
useAPIError(postError);

const applyPreset = useCallback(
(index: number) => {
const preset = presets[index];
if (!preset) {
return;
}

setPostText(
preset.text
.replace(/\{url\}/g, url)
.replace(/\{hashtag\}/g, hashtag ? `#${hashtag}` : '')
);
},
[url, hashtag]
);

const post = useCallback(async () => {
if (!publishPost || !postText) {
return;
}

try {
if (mastodonToken) {
const domain = localStorage.getItem(MASTODON_DOMAIN_LS);
if (!domain) {
throw new Error('domain is required');
}

await postToExternal(
{
type: SignInType.Mastodon,
domain,
token: mastodonToken
},
postText,
Visibility.Public
);
} else if (misskeyToken) {
const domain = localStorage.getItem(MISSKEY_DOMAIN_LS);
if (!domain) {
throw new Error('domain is required');
}

try {
await postToExternal(
{
type: SignInType.Misskey,
domain,
token: misskeyToken
},
postText,
Visibility.Public
);
} catch (e) {
if (e instanceof MisskeyRequestReSignError) {
toast({
title: intl.formatMessage({
id: 'toast.api-error.title'
}),
description: intl.formatMessage({
id: 'live.comment-post.permission-denied'
}),
status: 'error',
duration: 10000,
isClosable: true
});
}
}
}
} catch (e) {
setPostError(e);
// 投稿が失敗しても throw しない
}
}, [publishPost, postText, mastodonToken, misskeyToken, toast, intl]);

const handleClick = useCallback(() => {
void (async () => {
try {
setIsLoading(true);
await onPublish();
await post();
onClose();
} catch {
// noop
} finally {
setIsLoading(false);
}
})();
}, [onPublish, onClose, post]);

useEffect(() => {
applyPreset(0);
}, [applyPreset]);

return (
<Modal
isOpen={isOpen}
onClose={onClose}
isCentered
motionPreset="slideInBottom"
scrollBehavior="inside"
size="xl"
>
<ModalOverlay />
<ModalContent>
<ModalHeader>配信を開始</ModalHeader>
<ModalCloseButton />
<ModalBody>
<Stack spacing={6}>
{isBroadcastViaBrowser && (
<Alert status="info">
<AlertIcon />
配信を開始すると、マイクがオンになります。(ブラウザの許可ダイアログが表示された場合は、許可してください)
</Alert>
)}

<FormControl>
<Checkbox
isChecked={publishPost}
onChange={e => setPublishPost(e.target.checked)}
>
配信を開始したら {me?.account} で公開投稿する
</Checkbox>
</FormControl>

<Collapse in={publishPost} animateOpacity>
<Stack spacing={6}>
<FormControl>
<FormLabel>投稿文</FormLabel>

<Textarea
value={postText}
onChange={e => setPostText(e.target.value)}
maxLength={500}
disabled={isLoading}
/>
</FormControl>

<FormControl>
<FormLabel>プリセットを使用</FormLabel>

<Stack spacing={2}>
{presets.map((preset, index) => (
<Button
key={preset.name}
size="sm"
onClick={() => applyPreset(index)}
disabled={isLoading}
>
{preset.name}
</Button>
))}
</Stack>
</FormControl>
</Stack>
</Collapse>
</Stack>
</ModalBody>

<ModalFooter>
<Button
colorScheme="blue"
width="100%"
onClick={handleClick}
isLoading={isLoading}
>
配信を開始{publishPost && '&投稿'}
</Button>
</ModalFooter>
</ModalContent>
</Modal>
);
};

type Preset = {
name: string;
text: string;
};

const presets: Preset[] = [
{
name: 'デフォルト',
text: 'KnzkLive で配信中! {url} {hashtag}'
},
{
name: 'Knzk',
text: '{url}\n{url}\n{url}'
}
];
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ import { useWakeLock } from '~/utils/hooks/use-wake-lock';
import { TimeCounter } from '~/atoms/time-counter';
import { useConvertLiveId } from '~/utils/hooks/api/use-convert-live-id';
import { useBeforeUnload } from 'react-use';
import { StartModal } from '~/organisms/live/admin/start-modal';

type Params = { slug: string; id: string };

Expand Down Expand Up @@ -215,13 +216,16 @@ const Page: NextPage<PageProps<Props, Params & PathProps>> = ({

<StartedNote />

<Dialog
isOpen={isOpenStart}
onClose={onCloseStart}
onSubmit={() => void connect()}
title="配信を開始しますか?マイクがオンになります。"
submitText="配信を開始"
/>
{live && (
<StartModal
isOpen={isOpenStart}
onClose={onCloseStart}
onPublish={connect}
url={live.publicUrl}
hashtag={live.hashtag}
isBroadcastViaBrowser={true}
/>
)}

<Dialog
isOpen={isOpenStop}
Expand Down
Loading

1 comment on commit 7033cc0

@vercel
Copy link

@vercel vercel bot commented on 7033cc0 Mar 29, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

knzklive2 – ./

www.knzk.live
knzklive2-knzklive.vercel.app
knzklive2-git-production-knzklive.vercel.app
knzk.live

Please sign in to comment.