mirror of
https://github.com/morten-olsen/refocus.dev.git
synced 2026-02-08 00:46:25 +01:00
init
This commit is contained in:
12
packages/widgets/src/github/index.ts
Normal file
12
packages/widgets/src/github/index.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { Widget } from '@refocus/sdk';
|
||||
import githubProfileWidget from './profile/index.widget';
|
||||
import pullRequest from './pull-request/index.widget';
|
||||
import pullRequstComments from './pull-request-comments/index.widget';
|
||||
|
||||
const github = [
|
||||
githubProfileWidget,
|
||||
pullRequest,
|
||||
pullRequstComments,
|
||||
] satisfies Widget<any>[];
|
||||
|
||||
export { github };
|
||||
28
packages/widgets/src/github/profile/editor.tsx
Normal file
28
packages/widgets/src/github/profile/editor.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import { Props } from './schema';
|
||||
|
||||
type EditorProps = {
|
||||
value?: Props;
|
||||
save: (data: Props) => void;
|
||||
};
|
||||
|
||||
const Editor: React.FC<EditorProps> = ({ value, save }) => {
|
||||
const [data, setData] = useState<Props>(value || { username: '' });
|
||||
|
||||
const handleChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setData((data) => ({ ...data, [e.target.name]: e.target.value }));
|
||||
}, []);
|
||||
|
||||
const handleSave = useCallback(() => {
|
||||
save(data);
|
||||
}, [data, save]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<input name="username" value={data.username} onChange={handleChange} />
|
||||
<button onClick={handleSave}>Save</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export { Editor };
|
||||
30
packages/widgets/src/github/profile/index.tsx
Normal file
30
packages/widgets/src/github/profile/index.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import { useGithubQuery, withGithub } from '@refocus/sdk';
|
||||
import { Github } from '@refocus/ui';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
type GithubProfileProps = {
|
||||
username: string;
|
||||
};
|
||||
|
||||
type QueryData = {
|
||||
username: string;
|
||||
};
|
||||
|
||||
const GithubProfile = withGithub<GithubProfileProps>(({ username }) => {
|
||||
const user = useGithubQuery(async (client, params: QueryData) => {
|
||||
const nextUser = await client.rest.users.getByUsername({
|
||||
username: params.username,
|
||||
});
|
||||
return nextUser.data;
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
user.fetch({ username });
|
||||
}, [username]);
|
||||
|
||||
if (!user.data) return null;
|
||||
|
||||
return <Github.Profile profile={user.data} />;
|
||||
}, Github.NotLoggedIn);
|
||||
|
||||
export { GithubProfile };
|
||||
14
packages/widgets/src/github/profile/index.widget.tsx
Normal file
14
packages/widgets/src/github/profile/index.widget.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import { GithubProfile } from '.';
|
||||
import { Widget } from '@refocus/sdk';
|
||||
import { schema } from './schema';
|
||||
import { Editor } from './editor';
|
||||
|
||||
const githubProfileWidget: Widget<typeof schema> = {
|
||||
name: 'Github Profile',
|
||||
id: 'github.profile',
|
||||
schema,
|
||||
component: GithubProfile,
|
||||
edit: Editor,
|
||||
};
|
||||
|
||||
export default githubProfileWidget;
|
||||
10
packages/widgets/src/github/profile/schema.ts
Normal file
10
packages/widgets/src/github/profile/schema.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { Type, Static } from '@sinclair/typebox';
|
||||
|
||||
const schema = Type.Object({
|
||||
username: Type.String(),
|
||||
});
|
||||
|
||||
type Props = Static<typeof schema>;
|
||||
|
||||
export type { Props };
|
||||
export { schema };
|
||||
44
packages/widgets/src/github/pull-request-comments/edit.tsx
Normal file
44
packages/widgets/src/github/pull-request-comments/edit.tsx
Normal file
@@ -0,0 +1,44 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import { Props } from './schema';
|
||||
|
||||
type EditorProps = {
|
||||
value?: Props;
|
||||
save: (data: Props) => void;
|
||||
};
|
||||
|
||||
const Edit: React.FC<EditorProps> = ({ value, save }) => {
|
||||
const [owner, setOwner] = useState(value?.owner || '');
|
||||
const [repo, setRepo] = useState(value?.repo || '');
|
||||
const [pr, setPr] = useState(value?.pr || '');
|
||||
|
||||
const handleSave = useCallback(() => {
|
||||
save({
|
||||
owner,
|
||||
repo,
|
||||
pr: typeof pr === 'string' ? parseInt(pr, 10) : pr,
|
||||
});
|
||||
}, [owner, repo, pr, save]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<input
|
||||
placeholder="Owner"
|
||||
value={owner}
|
||||
onChange={(e) => setOwner(e.target.value)}
|
||||
/>
|
||||
<input
|
||||
placeholder="Repo"
|
||||
value={repo}
|
||||
onChange={(e) => setRepo(e.target.value)}
|
||||
/>
|
||||
<input
|
||||
placeholder="PR"
|
||||
value={pr}
|
||||
onChange={(e) => setPr(e.target.value)}
|
||||
/>
|
||||
<button onClick={handleSave}>Save</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export { Edit };
|
||||
@@ -0,0 +1,28 @@
|
||||
import { Widget } from '@refocus/sdk';
|
||||
import { SiGithub } from 'react-icons/si';
|
||||
import { schema } from './schema';
|
||||
import { Edit } from './edit';
|
||||
import { View } from './view';
|
||||
|
||||
const widget: Widget<typeof schema> = {
|
||||
name: 'Github Pull Request Comments',
|
||||
id: 'github.pull-request-comments',
|
||||
icon: <SiGithub />,
|
||||
description: 'Display the 5 latest comments on a Github pull request',
|
||||
parseUrl: (url) => {
|
||||
if (url.hostname !== 'github.com') {
|
||||
return;
|
||||
}
|
||||
const pathParts = url.pathname.split('/').filter(Boolean);
|
||||
const [owner, repo, type, pr] = pathParts.slice(0);
|
||||
if (type !== 'pull' || !pr) {
|
||||
return;
|
||||
}
|
||||
return { owner, repo, pr: parseInt(pr, 10) };
|
||||
},
|
||||
schema,
|
||||
component: View,
|
||||
edit: Edit,
|
||||
};
|
||||
|
||||
export default widget;
|
||||
12
packages/widgets/src/github/pull-request-comments/schema.ts
Normal file
12
packages/widgets/src/github/pull-request-comments/schema.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { Type, Static } from '@sinclair/typebox';
|
||||
|
||||
const schema = Type.Object({
|
||||
owner: Type.String(),
|
||||
repo: Type.String(),
|
||||
pr: Type.Number(),
|
||||
});
|
||||
|
||||
type Props = Static<typeof schema>;
|
||||
|
||||
export type { Props };
|
||||
export { schema };
|
||||
74
packages/widgets/src/github/pull-request-comments/view.tsx
Normal file
74
packages/widgets/src/github/pull-request-comments/view.tsx
Normal file
@@ -0,0 +1,74 @@
|
||||
import {
|
||||
useAddWidgetNotification,
|
||||
useAutoUpdate,
|
||||
useGithubQuery,
|
||||
withGithub,
|
||||
} from '@refocus/sdk';
|
||||
import { Chat, Github, List } from '@refocus/ui';
|
||||
import { Props } from './schema';
|
||||
import { View as PullRequest } from '../pull-request/view';
|
||||
|
||||
type QueryData = {
|
||||
owner: string;
|
||||
repo: string;
|
||||
pr: number;
|
||||
};
|
||||
|
||||
const View = withGithub<Props>(({ owner, repo, pr }) => {
|
||||
const addNotification = useAddWidgetNotification();
|
||||
const { data, fetch } = useGithubQuery(async (client, params: QueryData) => {
|
||||
const response = await client.rest.pulls.listReviewComments({
|
||||
owner: params.owner,
|
||||
repo: params.repo,
|
||||
pull_number: params.pr,
|
||||
});
|
||||
return response.data.slice(0, 5);
|
||||
});
|
||||
|
||||
useAutoUpdate(
|
||||
{
|
||||
interval: 1000 * 60 * 5,
|
||||
action: async () => fetch({ owner, repo, pr }),
|
||||
callback: (next, prev) => {
|
||||
if (prev && next) {
|
||||
const previousIds = prev.map((comment) => comment.id);
|
||||
const newComments = next.filter(
|
||||
(comment) => !previousIds.includes(comment.id),
|
||||
);
|
||||
for (const comment of newComments) {
|
||||
addNotification({
|
||||
title: `New comments on PR #${pr} in ${owner}/${repo}`,
|
||||
message: comment.body,
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
[owner, repo, pr, addNotification],
|
||||
);
|
||||
|
||||
if (!data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<List>
|
||||
<PullRequest owner={owner} repo={repo} pr={pr} />
|
||||
{data.map((comment) => (
|
||||
<Chat.Message
|
||||
message={{
|
||||
sender: {
|
||||
name: comment.user.login,
|
||||
avatar: comment.user.avatar_url,
|
||||
},
|
||||
timestamp: new Date(comment.created_at),
|
||||
text: comment.body,
|
||||
}}
|
||||
key={comment.id}
|
||||
/>
|
||||
))}
|
||||
</List>
|
||||
);
|
||||
}, Github.NotLoggedIn);
|
||||
|
||||
export { View };
|
||||
44
packages/widgets/src/github/pull-request/edit.tsx
Normal file
44
packages/widgets/src/github/pull-request/edit.tsx
Normal file
@@ -0,0 +1,44 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import { Props } from './schema';
|
||||
|
||||
type EditorProps = {
|
||||
value?: Props;
|
||||
save: (data: Props) => void;
|
||||
};
|
||||
|
||||
const Edit: React.FC<EditorProps> = ({ value, save }) => {
|
||||
const [owner, setOwner] = useState(value?.owner || '');
|
||||
const [repo, setRepo] = useState(value?.repo || '');
|
||||
const [pr, setPr] = useState(value?.pr || '');
|
||||
|
||||
const handleSave = useCallback(() => {
|
||||
save({
|
||||
owner,
|
||||
repo,
|
||||
pr: typeof pr === 'string' ? parseInt(pr, 10) : pr,
|
||||
});
|
||||
}, [owner, repo, pr, save]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<input
|
||||
placeholder="Owner"
|
||||
value={owner}
|
||||
onChange={(e) => setOwner(e.target.value)}
|
||||
/>
|
||||
<input
|
||||
placeholder="Repo"
|
||||
value={repo}
|
||||
onChange={(e) => setRepo(e.target.value)}
|
||||
/>
|
||||
<input
|
||||
placeholder="PR"
|
||||
value={pr}
|
||||
onChange={(e) => setPr(e.target.value)}
|
||||
/>
|
||||
<button onClick={handleSave}>Save</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export { Edit };
|
||||
28
packages/widgets/src/github/pull-request/index.widget.tsx
Normal file
28
packages/widgets/src/github/pull-request/index.widget.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import { Widget } from '@refocus/sdk';
|
||||
import { SiGithub } from 'react-icons/si';
|
||||
import { schema } from './schema';
|
||||
import { Edit } from './edit';
|
||||
import { View } from './view';
|
||||
|
||||
const widget: Widget<typeof schema> = {
|
||||
name: 'Github Pull Request',
|
||||
description: 'Display an info card for a Github pull request',
|
||||
icon: <SiGithub />,
|
||||
id: 'github.pull-request',
|
||||
parseUrl: (url) => {
|
||||
if (url.hostname !== 'github.com') {
|
||||
return;
|
||||
}
|
||||
const pathParts = url.pathname.split('/').filter(Boolean);
|
||||
const [owner, repo, type, pr] = pathParts.slice(0);
|
||||
if (type !== 'pull' || !pr) {
|
||||
return;
|
||||
}
|
||||
return { owner, repo, pr: parseInt(pr, 10) };
|
||||
},
|
||||
schema,
|
||||
component: View,
|
||||
edit: Edit,
|
||||
};
|
||||
|
||||
export default widget;
|
||||
12
packages/widgets/src/github/pull-request/schema.ts
Normal file
12
packages/widgets/src/github/pull-request/schema.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { Type, Static } from '@sinclair/typebox';
|
||||
|
||||
const schema = Type.Object({
|
||||
owner: Type.String(),
|
||||
repo: Type.String(),
|
||||
pr: Type.Number(),
|
||||
});
|
||||
|
||||
type Props = Static<typeof schema>;
|
||||
|
||||
export type { Props };
|
||||
export { schema };
|
||||
36
packages/widgets/src/github/pull-request/view.tsx
Normal file
36
packages/widgets/src/github/pull-request/view.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import { useAutoUpdate, useGithubQuery, withGithub } from '@refocus/sdk';
|
||||
import { Props } from './schema';
|
||||
import { Github } from '@refocus/ui';
|
||||
|
||||
type QueryData = {
|
||||
owner: string;
|
||||
repo: string;
|
||||
pr: number;
|
||||
};
|
||||
|
||||
const View = withGithub<Props>(({ owner, repo, pr }) => {
|
||||
const { data, fetch } = useGithubQuery(async (client, params: QueryData) => {
|
||||
const response = await client.rest.pulls.get({
|
||||
owner: params.owner,
|
||||
repo: params.repo,
|
||||
pull_number: params.pr,
|
||||
});
|
||||
return response.data;
|
||||
});
|
||||
|
||||
useAutoUpdate(
|
||||
{
|
||||
interval: 1000 * 60 * 5,
|
||||
action: async () => fetch({ owner, repo, pr }),
|
||||
},
|
||||
[owner, repo, pr],
|
||||
);
|
||||
|
||||
if (!data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <Github.PullRequest pullRequest={data} />;
|
||||
}, Github.NotLoggedIn);
|
||||
|
||||
export { View };
|
||||
Reference in New Issue
Block a user