Skip to content

Commit

Permalink
Merge pull request #1 from yoshiya0503/development
Browse files Browse the repository at this point in the history
🎉v1.0 Release
  • Loading branch information
yoshiya0503 authored Oct 9, 2023
2 parents ca6c19f + 6fa331a commit 26f8f06
Show file tree
Hide file tree
Showing 72 changed files with 2,209 additions and 433 deletions.
39 changes: 37 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,37 @@
# rain
bluesky client
# ☔Rain

![Vercel](https://vercelbadge.vercel.app/api/yoshiya0503/rain)
![React](https://badges.aleen42.com/src/react.svg)
![Typescript](https://github.com/aleen42/badges/raw/master/src/typescript.svg)

**_Simple and beautiful bluesky client for web._**

<img width="1388" alt="スクリーンショット 2023-10-09 15 33 54" src="https://github.com/yoshiya0503/rain/assets/5334715/0963573d-d586-42fa-86ec-867e62851bfe">

# 🖥 Development

you only try follow.

```
yarn
yarn dev
```

# 🔖 Deployment

Now we use vercel to deploy. (hosting service for SPA)

# 🔨 Architecture

- _only to use react and material-UI._
- _we use <Suspense /> (react new feature) at all of api call._
- _response data managed by zustand._
- _we use typescript and reference types of atproto lexicons._

# ✨ Directory

- pages -> call by react-router-dom.
- stores -> zustand state management.
- templates -> layout component and skeletons UI in loading.
- components -> UI components by using MUI.
- hooks -> separated localize data manage, and logic from components.
4 changes: 2 additions & 2 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<link rel="icon" type="image/png" href="/rain.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React + TS</title>
<title>Rain</title>
</head>
<body>
<div id="root"></div>
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "skyline",
"name": "rain",
"private": true,
"version": "0.0.0",
"type": "module",
Expand Down
Binary file added public/rain.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions public/rain.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 0 additions & 1 deletion public/vite.svg

This file was deleted.

4 changes: 2 additions & 2 deletions src/agent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { BskyAgent, AtpSessionEvent, AtpSessionData } from "@atproto/api";
export const agent = new BskyAgent({
service: "https://bsky.social",
persistSession: (_?: AtpSessionEvent, session?: AtpSessionData) => {
localStorage.setItem("X-SKYLINE-REFRESHJWT", session?.refreshJwt || "");
localStorage.setItem("X-SKYLINE-ACCESSJWT", session?.accessJwt || "");
localStorage.setItem("X-RAIN-REFRESHJWT", session?.refreshJwt || "");
localStorage.setItem("X-RAIN-ACCESSJWT", session?.accessJwt || "");
},
});

Expand Down
138 changes: 138 additions & 0 deletions src/components/DialogContentFilter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import _ from "lodash";
import { useCallback } from "react";
import { useStore } from "@/stores";
import Stack from "@mui/material/Stack";
import Button from "@mui/material/Button";
import Divider from "@mui/material/Divider";
import Switch from "@mui/material/Switch";
import ToggleButton from "@mui/material/ToggleButton";
import ToggleButtonGroup from "@mui/material/ToggleButtonGroup";
import Dialog from "@mui/material/Dialog";
import DialogActions from "@mui/material/DialogActions";
import DialogContent from "@mui/material/DialogContent";
import DialogContentText from "@mui/material/DialogContentText";
import DialogTitle from "@mui/material/DialogTitle";
import List from "@mui/material/List";
import ListItem from "@mui/material/ListItem";
import ListItemText from "@mui/material/ListItemText";
import Typography from "@mui/material/Typography";
import { AppBskyActorDefs } from "@atproto/api";

type Props = {
open: boolean;
preferences: AppBskyActorDefs.Preferences;
onClose: () => void;
};

export const DialogContentFilter = (props: Props) => {
const updatePreferences = useStore((state) => state.updatePreferences);
const adultPref = _.find(props.preferences, (p) => AppBskyActorDefs.isAdultContentPref(p));
const contentLabelPref = _.filter(props.preferences, (p) => AppBskyActorDefs.isContentLabelPref(p));

const label = {
impersonation: {
title: "Impersonation",
description: "Accounts falsely claiming to be people or orgs",
},
nsfw: {
title: "Explicit Sexual Images",
description: "i.e. pornography",
},
gore: {
title: "Violent / Bloody",
description: "Gore, self-harm, torture",
},
suggestive: {
title: "Sexually Suggestive",
description: "Does not include nudity",
},
hate: {
title: "Hate Group Iconography",
description: "Images of terror groups, articles covering events, etc.",
},
nudity: {
title: "Other Nudity",
description: "Including non-sexual and artistic",
},
spam: {
title: "Spam",
description: "Excessive unwanted interactions",
},
};

const onToggleAdult = useCallback(() => {
const preferences = _.map(props.preferences, (pref) => {
if (AppBskyActorDefs.isAdultContentPref(pref)) return { ...pref, enabled: !pref.enabled };
return pref;
});
updatePreferences(preferences);
}, [props, updatePreferences]);

const onChangeFilter = useCallback(
(label: string) => (__: React.MouseEvent<HTMLElement>, visibility: string) => {
const preferences = _.map(props.preferences, (pref) => {
if (label === pref.label) return { ...pref, visibility };
return pref;
});
updatePreferences(preferences);
},
[props, updatePreferences]
);

return (
<Dialog open={props.open} fullWidth maxWidth="sm" PaperProps={{ sx: { borderRadius: 3 } }} onClose={props.onClose}>
<DialogTitle>Content Filter</DialogTitle>
<DialogContent>
<DialogContentText component="div">
<Stack direction="row" spacing={2} alignItems="center">
<Switch checked={!!adultPref?.enabled} onChange={onToggleAdult} />
Enable Sexual Content
</Stack>
</DialogContentText>
<List>
{_.map(contentLabelPref, (pref: AppBskyActorDefs.ContentLabelPref) => (
<ListItem
key={pref.label}
divider
secondaryAction={
<ToggleButtonGroup
color="primary"
size="small"
exclusive
value={pref.visibility}
onChange={onChangeFilter(pref.label)}
>
<ToggleButton value="hide">
<Typography sx={{ fontWeight: 600, textTransform: "none" }} variant="caption">
Hide
</Typography>
</ToggleButton>
<ToggleButton value="warn">
<Typography sx={{ fontWeight: 600, textTransform: "none" }} variant="caption">
Warn
</Typography>
</ToggleButton>
<ToggleButton value="show">
<Typography sx={{ fontWeight: 600, textTransform: "none" }} variant="caption">
Show
</Typography>
</ToggleButton>
</ToggleButtonGroup>
}
>
<ListItemText primary={_.get(label, pref.label).title} secondary={_.get(label, pref.label).description} />
</ListItem>
))}
</List>
</DialogContent>
<Divider />
<DialogActions sx={{ display: "flex", alignItems: "center", justifyContent: "center" }}>
<Button sx={{ width: "50%", borderRadius: 5, fontWeight: 600 }} variant="contained" onClick={props.onClose}>
DONE
</Button>
</DialogActions>
</Dialog>
);
};

export default DialogContentFilter;
91 changes: 91 additions & 0 deletions src/components/DialogFeed.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import _ from "lodash";
import { useCallback } from "react";
import { useStore } from "@/stores";
import Stack from "@mui/material/Stack";
import IconButton from "@mui/material/IconButton";
import Button from "@mui/material/Button";
import Dialog from "@mui/material/Dialog";
import Divider from "@mui/material/Divider";
import Avatar from "@mui/material/Avatar";
import List from "@mui/material/List";
import ListItem from "@mui/material/ListItem";
import ListItemAvatar from "@mui/material/ListItemAvatar";
import ListItemText from "@mui/material/ListItemText";
import DialogContent from "@mui/material/DialogContent";
import DialogActions from "@mui/material/DialogActions";
import DialogTitle from "@mui/material/DialogTitle";
import PushPinRoundedIcon from "@mui/icons-material/PushPinRounded";
import DeleteForeverRoundedIcon from "@mui/icons-material/DeleteForeverRounded";
import useFeedGenerator from "@/hooks/useFeedGenerator";
import { AppBskyFeedDefs, AppBskyActorDefs } from "@atproto/api";

type Props = {
feeds: AppBskyFeedDefs.GeneratorView[];
preferences: AppBskyActorDefs.Preferences;
open: boolean;
onClose: () => void;
onSend?: () => void;
};

export const DialogFeed = (props: Props) => {
const updateSavedFeedViewer = useStore((state) => state.updateSavedFeedViewer);
const { isPinned, onToggleSave, onTogglePin } = useFeedGenerator();

const onDelete = useCallback(
(feed: AppBskyFeedDefs.GeneratorView) => async () => {
onToggleSave(feed, props.preferences);
updateSavedFeedViewer(feed)
},
[props, onToggleSave, updateSavedFeedViewer]
);

return (
<Dialog open={props.open} fullWidth maxWidth="sm" PaperProps={{ sx: { borderRadius: 3 } }} onClose={props.onClose}>
<DialogTitle>Saved Feed</DialogTitle>
<DialogContent>
<List>
{_.map(props.feeds, (feed) => (
<ListItem
key={feed.uri}
divider
secondaryAction={
<Stack direction="row" spacing={1}>
<IconButton
size="small"
color="primary"
onClick={(e) => {
e.stopPropagation();
onTogglePin(feed, props.preferences);
}}
>
{isPinned(feed, props.preferences) ? (
<PushPinRoundedIcon />
) : (
<PushPinRoundedIcon color="disabled" />
)}
</IconButton>
<IconButton onClick={onDelete(feed)} color="error">
<DeleteForeverRoundedIcon />
</IconButton>
</Stack>
}
>
<ListItemAvatar>
<Avatar alt={feed.avatar} src={feed.avatar} variant="rounded" />
</ListItemAvatar>
<ListItemText primary={feed.displayName} secondary={`by @${feed.creator.handle}`} />
</ListItem>
))}
</List>
</DialogContent>
<Divider />
<DialogActions sx={{ display: "flex", alignItems: "center", justifyContent: "center" }}>
<Button sx={{ width: "50%", borderRadius: 5, fontWeight: 600 }} variant="contained" onClick={props.onClose}>
DONE
</Button>
</DialogActions>
</Dialog>
);
};

export default DialogFeed;
13 changes: 9 additions & 4 deletions src/components/DialogHandle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ type Props = {
export const DialogHandle = (props: Props) => {
const me = useMe();
const { open, withBackdrop } = useBackdrop();
const { handle, onChangeHandle, onUpdateHandle } = useHandle();
const disabled = false;
const { handle, onChangeHandle, onUpdateHandle, onClearHandle } = useHandle();
const disabled = !handle;

const onSend = async () => {
withBackdrop(async () => {
Expand All @@ -35,8 +35,13 @@ export const DialogHandle = (props: Props) => {
});
};

const onClear = async () => {
onClearHandle();
props.onClose();
};

return (
<Dialog open={props.open} fullWidth maxWidth="sm" onClose={props.onClose}>
<Dialog open={props.open} fullWidth maxWidth="sm" PaperProps={{ sx: { borderRadius: 3 } }} onClose={onClear}>
<Backdrop sx={{ zIndex: (theme) => theme.zIndex.drawer + 1 }} open={open}>
<CircularProgress color="primary" />
</Backdrop>
Expand Down Expand Up @@ -65,7 +70,7 @@ export const DialogHandle = (props: Props) => {
</DialogContent>
<DialogActions>
<Box>
<Button onClick={props.onClose}>Cancel</Button>
<Button onClick={onClear}>Cancel</Button>
<Button onClick={onSend} disabled={disabled}>
Save
</Button>
Expand Down
2 changes: 1 addition & 1 deletion src/components/DialogImage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export const DialogImage = (props: Props) => {
image={image?.thumb}
alt={image?.alt}
/>
<ImageListItemBar subtitle={image?.alt} position="bottom" />
{image?.alt && <ImageListItemBar subtitle={image?.alt} position="bottom" />}
</CardActionArea>
<IconButton
size="large"
Expand Down
Loading

0 comments on commit 26f8f06

Please sign in to comment.