Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: Persist 'Share as Image' options #1581

Open
wants to merge 23 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
baeb5b0
initial implementation
sharunkumar Aug 11, 2024
ae428a1
use JSON.parse
sharunkumar Aug 11, 2024
51f9488
lint + editor update
sharunkumar Aug 11, 2024
1d08717
implement `common` section
sharunkumar Aug 11, 2024
de855c4
persist allParentComments value when hitting upper/lower limit
sharunkumar Aug 11, 2024
db6a9e0
move `defaultPreferences` to separate file
sharunkumar Aug 11, 2024
29d1410
`includePostText` -> `includePostContent`
sharunkumar Aug 11, 2024
6f423e3
Merge remote-tracking branch 'upstream/main' into persist-share-as-im…
sharunkumar Aug 11, 2024
f4db7a2
Merge remote-tracking branch 'upstream/main' into persist-share-as-im…
sharunkumar Aug 11, 2024
a34f6d8
Merge branch 'main' into persist-share-as-image-choices
sharunkumar Aug 11, 2024
89b9279
Merge Release 2.17.0
sharunkumar Aug 13, 2024
775ba47
Merge branch 'main' into persist-share-as-image-choices
sharunkumar Aug 17, 2024
95dfbb9
Merge branch 'main' into persist-share-as-image-choices
sharunkumar Aug 24, 2024
e66819e
Merge branch 'main' into persist-share-as-image-choices
sharunkumar Aug 29, 2024
af78955
Merge branch 'main' into persist-share-as-image-choices
sharunkumar Sep 11, 2024
e671ed1
Merge branch 'main' into persist-share-as-image-choices
sharunkumar Sep 30, 2024
1f74e89
Merge branch 'main' into persist-share-as-image-choices
sharunkumar Oct 2, 2024
3b74aca
Merge branch 'main' into persist-share-as-image-choices
sharunkumar Oct 3, 2024
e0230b2
Merge branch 'main' into persist-share-as-image-choices
sharunkumar Oct 4, 2024
c8af557
fix bug related to sharing the post as image
sharunkumar Oct 5, 2024
090983d
Merge branch 'main' into persist-share-as-image-choices
sharunkumar Oct 20, 2024
badb846
Merge branch 'main' into persist-share-as-image-choices
sharunkumar Oct 23, 2024
7957aa0
Merge remote-tracking branch 'upstream/main' into persist-share-as-im…
sharunkumar Nov 11, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions src/features/share/asImage/DefaultPreferences.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { ShareAsImagePreferences } from "./ShareAsImagePreferences";

export const defaultPreferences: ShareAsImagePreferences = {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'd like to have these in a separate file, as I intend to flip some of these values downstream

common: {
hideUsernames: false,
watermark: false,
},
post: {
hideCommunity: false,
},
comment: {
includePostContent: false,
includePostDetails: false,
allParentComments: false,
},
};
105 changes: 74 additions & 31 deletions src/features/share/asImage/ShareAsImage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { webviewServerUrl } from "#/services/nativeFetch";

import AddRemoveButtons from "./AddRemoveButtons";
import { ShareAsImageData } from "./ShareAsImageModal";
import { useShareAsImagePreferences } from "./ShareAsImagePreferences";
import Watermark from "./Watermark";
import includeStyleProperties from "./includeStyleProperties";

Expand Down Expand Up @@ -163,22 +164,28 @@ interface ShareAsImageProps {
export default function ShareAsImage({ data, header }: ShareAsImageProps) {
const presentToast = useAppToast();

const [hideUsernames, setHideUsernames] = useState(false);
const [hideCommunity, setHideCommunity] = useState(false);
const [includePostDetails, setIncludePostDetails] = useState(
!("comment" in data),
);
Comment on lines -168 to -170
Copy link
Contributor Author

@sharunkumar sharunkumar Oct 5, 2024

Choose a reason for hiding this comment

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

@aeharding I guess this was the cause of the issue, and its not browser dependent. I tried it in incognito window in edge and its reproducing

includePostDetails is currently false by default in my config

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I believe I have fixed the issue in my latest commit: c8af557 (#1581)

let me know if there's anything else

const [includePostText, setIncludePostText] = useState(true);
const [watermark, setWatermark] = useState(false);

const [blob, setBlob] = useState<Blob | undefined>();
const [imageSrc, setImageSrc] = useState("");

const [minDepth, setMinDepth] = useState(
("comment" in data
const {
shareAsImagePreferences: {
comment: { includePostContent, includePostDetails, allParentComments },
common: { hideUsernames, watermark },
post: { hideCommunity },
},
setShareAsImagePreferences,
} = useShareAsImagePreferences();

const isComment = "comment" in data;

// eslint-disable-next-line no-nested-ternary
const defaultMinDepth = allParentComments
? 0
: isComment
? getDepthFromComment(data.comment.comment)
: undefined) ?? 0,
);
: 0;

const [minDepth, setMinDepth] = useState(defaultMinDepth ?? 0);

const hasPostBody = data.post.post.body || data.post.post.url;

Expand All @@ -191,7 +198,7 @@ export default function ShareAsImage({ data, header }: ShareAsImageProps) {
}, [blob]);

const filteredComments = (() => {
if (!("comment" in data)) return [];
if (!isComment) return [];

const filtered = data.comments
.filter(
Expand Down Expand Up @@ -239,15 +246,15 @@ export default function ShareAsImage({ data, header }: ShareAsImageProps) {
watermark,
hideUsernames,
hideCommunity,
includePostContent,
includePostDetails,
includePostText,
allParentComments,
]);

async function onShare() {
if (!blob) return;

const apId =
"comment" in data ? data.comment.comment.ap_id : data.post.post.ap_id;
const apId = isComment ? data.comment.comment.ap_id : data.post.post.ap_id;

const filename = `${apId
.replace(/^https:\/\//, "")
Expand Down Expand Up @@ -307,23 +314,31 @@ export default function ShareAsImage({ data, header }: ShareAsImageProps) {
)}

<StyledIonList inset lines="full">
{"comment" in data && (
{isComment && (
<>
<IonItem>
<IonToggle
checked={includePostDetails}
onIonChange={(e) => setIncludePostDetails(e.detail.checked)}
onIonChange={({ detail: { checked } }) =>
setShareAsImagePreferences({
comment: { includePostDetails: checked },
})
}
>
Include Post Details
</IonToggle>
</IonItem>
{includePostDetails && hasPostBody ? (
{(isComment ? includePostDetails : true) && hasPostBody ? (
<IonItem>
<IonToggle
checked={includePostText}
onIonChange={(e) => setIncludePostText(e.detail.checked)}
checked={includePostContent}
onIonChange={({ detail: { checked } }) =>
setShareAsImagePreferences({
comment: { includePostContent: checked },
})
}
>
Include Post Text
Include Post Content
</IonToggle>
</IonItem>
) : undefined}
Expand All @@ -341,8 +356,30 @@ export default function ShareAsImage({ data, header }: ShareAsImageProps) {
removeDisabled={
minDepth === getDepthFromComment(data.comment.comment)
}
onAdd={() => setMinDepth((minDepth) => minDepth - 1)}
onRemove={() => setMinDepth((minDepth) => minDepth + 1)}
onAdd={() => {
setMinDepth((minDepth) => {
const newValue = minDepth - 1;
if (newValue === 0) {
setShareAsImagePreferences({
comment: { allParentComments: true },
});
}
return newValue;
});
}}
onRemove={() => {
setMinDepth((minDepth) => {
const newValue = minDepth + 1;
if (
newValue === getDepthFromComment(data.comment.comment)
) {
setShareAsImagePreferences({
comment: { allParentComments: false },
});
}
return newValue;
});
}}
/>
</ParentCommentValues>
</IonItem>
Expand All @@ -353,7 +390,9 @@ export default function ShareAsImage({ data, header }: ShareAsImageProps) {
<IonItem>
<IonToggle
checked={hideCommunity}
onIonChange={(e) => setHideCommunity(e.detail.checked)}
onIonChange={({ detail: { checked } }) =>
setShareAsImagePreferences({ post: { hideCommunity: checked } })
}
>
Hide Community
</IonToggle>
Expand All @@ -362,15 +401,19 @@ export default function ShareAsImage({ data, header }: ShareAsImageProps) {
<IonItem>
<IonToggle
checked={hideUsernames}
onIonChange={(e) => setHideUsernames(e.detail.checked)}
onIonChange={({ detail: { checked } }) =>
setShareAsImagePreferences({ common: { hideUsernames: checked } })
}
>
Hide Usernames
</IonToggle>
</IonItem>
<IonItem lines="none">
<IonToggle
checked={watermark}
onIonChange={(e) => setWatermark(e.detail.checked)}
onIonChange={({ detail: { checked } }) =>
setShareAsImagePreferences({ common: { watermark: checked } })
}
>
Watermark
</IonToggle>
Expand All @@ -385,16 +428,16 @@ export default function ShareAsImage({ data, header }: ShareAsImageProps) {
<ShareImageContext.Provider
value={{ capturing: true, hideUsernames, hideCommunity }}
>
{includePostDetails && (
{(isComment ? includePostDetails : true) && (
<PostHeader
className={!("comment" in data) ? hideBottomBorderCss : ""}
className={!isComment ? hideBottomBorderCss : ""}
post={data.post}
showPostText={includePostText}
showPostText={isComment ? includePostContent : true}
showPostActions={false}
constrainHeight={false}
/>
)}
{"comment" in data && (
{isComment && (
<>
{includePostDetails && <PostCommentSpacer />}
<CommentTree
Expand Down
90 changes: 90 additions & 0 deletions src/features/share/asImage/ShareAsImagePreferences.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { PayloadAction, createSlice } from "@reduxjs/toolkit";
import { useCallback, useEffect } from "react";

import { DeepPartial } from "#/helpers/deepPartial";
import { db } from "#/services/db";

import { AppDispatch, useAppDispatch, useAppSelector } from "../../../store";
import { defaultPreferences } from "./DefaultPreferences";

export interface ShareAsImagePreferences {
common: {
hideUsernames: boolean;
watermark: boolean;
};
post: {
hideCommunity: boolean;
};
comment: {
includePostDetails: boolean;
includePostContent: boolean;
allParentComments: boolean;
};
}

const initialState = defaultPreferences;

const { reducer, actions } = createSlice({
name: "shareAsImagePreferences",
initialState,
reducers: {
setShareAsImagePreferences: (
state,
action: PayloadAction<DeepPartial<ShareAsImagePreferences>>,
) => {
const { post, comment, common } = action.payload;

state.common.hideUsernames =
common?.hideUsernames ?? state.common.hideUsernames;
state.common.watermark = common?.watermark ?? state.common.watermark;

state.post.hideCommunity =
post?.hideCommunity ?? state.post.hideCommunity;

state.comment.includePostContent =
comment?.includePostContent ?? state.comment.includePostContent;
state.comment.includePostDetails =
comment?.includePostDetails ?? state.comment.includePostDetails;
state.comment.allParentComments =
comment?.allParentComments ?? state.comment.allParentComments;

db.setSetting(
"share_as_image_preferences",
JSON.parse(JSON.stringify(state)),
Copy link
Contributor Author

@sharunkumar sharunkumar Aug 11, 2024

Choose a reason for hiding this comment

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

Alternative to this was

Suggested change
JSON.parse(JSON.stringify(state)),
{
post: { ...state.post },
comment: { ...state.comment },
}

passing state directly, or using structuredClone didn't work either.

);
},
},
});

export default reducer;

export const useShareAsImagePreferences = () => {
const dispatch = useAppDispatch();

useEffect(() => {
// Load settings from DB on mount
dispatch(async (dispatchImpl: AppDispatch) => {
const share_as_image_preferences = await db.getSetting(
"share_as_image_preferences",
);
dispatchImpl(
actions.setShareAsImagePreferences(
share_as_image_preferences ?? initialState,
),
);
});
}, [dispatch]);

const shareAsImagePreferences = useAppSelector(
(state) => state.shareAsImagePreferences,
);

const setShareAsImagePreferences = useCallback(
(payload: DeepPartial<ShareAsImagePreferences>) => {
dispatch(actions.setShareAsImagePreferences(payload));
},
[dispatch],
);

return { shareAsImagePreferences, setShareAsImagePreferences };
};
7 changes: 7 additions & 0 deletions src/helpers/deepPartial.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export type DeepPartial<T> = {
[P in keyof T]?: T[P] extends Array<infer U>
? Array<DeepPartial<U>>
: T[P] extends ReadonlyArray<infer U>
? ReadonlyArray<DeepPartial<U>>
: DeepPartial<T[P]>;
};
3 changes: 3 additions & 0 deletions src/services/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {

import { COMMENT_SORTS } from "#/features/comment/CommentSort";
import { ALL_POST_SORTS } from "#/features/feed/PostSort";
import { ShareAsImagePreferences } from "#/features/share/asImage/ShareAsImagePreferences";
import { arrayOfAll } from "#/helpers/array";

export interface IPostMetadata {
Expand Down Expand Up @@ -360,6 +361,7 @@ export interface GlobalSettingValueTypes {
remember_community_comment_sort: boolean;
remember_community_post_sort: boolean;
remember_post_appearance_type: boolean;
share_as_image_preferences: ShareAsImagePreferences;
show_collapsed_comment: boolean;
show_comment_images: boolean;
show_community_icons: boolean;
Expand Down Expand Up @@ -456,6 +458,7 @@ export const ALL_GLOBAL_SETTINGS = arrayOfAll<keyof GlobalSettingValueTypes>()([
"user_instance_url_display",
"vote_display_mode",
"votes_theme",
"share_as_image_preferences",
]);

export interface ISettingItem<T extends keyof SettingValueTypes> {
Expand Down
2 changes: 2 additions & 0 deletions src/store.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import settingsSlice, {
getFilteredKeywords,
getFilteredWebsites,
} from "./features/settings/settingsSlice";
import ShareAsImagePreferencesSlice from "./features/share/asImage/ShareAsImagePreferences";
import spoilerSlice from "./features/shared/markdown/components/spoiler/spoilerSlice";
import uploadImageSlice from "./features/shared/markdown/editing/uploadImageSlice";
import userTagSlice from "./features/tags/userTagSlice";
Expand Down Expand Up @@ -77,6 +78,7 @@ const store = configureStore({
redgifs: redgifsSlice,
resolve: resolveSlice,
settings: settingsSlice,
shareAsImagePreferences: ShareAsImagePreferencesSlice,
site: siteSlice,
spoiler: spoilerSlice,
thumbnail: thumbnailSlice,
Expand Down