Skip to content

Commit

Permalink
web: save image compression upload setting
Browse files Browse the repository at this point in the history
Signed-off-by: 01zulfi <[email protected]>
  • Loading branch information
01zulfi committed Dec 11, 2024
1 parent 6098935 commit 801138b
Show file tree
Hide file tree
Showing 11 changed files with 2,041 additions and 1,844 deletions.
19 changes: 19 additions & 0 deletions apps/web/__e2e__/models/editor.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,4 +227,23 @@ export class EditorModel {
.replace(" words", "")
);
}

async attachImage() {
await this.page
.context()
.grantPermissions(["clipboard-read", "clipboard-write"]);
await this.page.evaluate(async () => {
const resp = await fetch("https://dummyjson.com/image/150");
const blob = await resp.blob();
window.navigator.clipboard.write([
new ClipboardItem({
"image/png": new Blob([blob], { type: "image/png" })
})
]);
});

await this.page.keyboard.down("Control");
await this.page.keyboard.press("KeyV");
await this.page.keyboard.up("Control");
}
}
11 changes: 11 additions & 0 deletions apps/web/__e2e__/models/settings-view.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,4 +139,15 @@ export class SettingsViewModel {

await waitForDialog(this.page, "Restoring backup");
}

async selectImageCompression(option: { value: string; label: string }) {
const item = await this.navigation.findItem("Behaviour");
await item?.click();

const imageCompressionDropdown = this.page
.locator(getTestId("setting-image-compression"))
.locator("select");

await imageCompressionDropdown.selectOption(option);
}
}
76 changes: 76 additions & 0 deletions apps/web/__e2e__/settings.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
This file is part of the Notesnook project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import { test } from "@playwright/test";
import { AppModel } from "./models/app.model";
import { NOTE } from "./utils";

test("ask for image compression during image upload when 'Image Compression' setting is 'Ask every time'", async ({
page
}) => {
const app = new AppModel(page);
await app.goto();
const settings = await app.goToSettings();
await settings.selectImageCompression({
value: "0",
label: "Ask every time"
});
await settings.close();

const notes = await app.goToNotes();
await notes.createNote(NOTE);
await notes.editor.attachImage();
await page.getByText("Enable compression").waitFor({ state: "visible" });
});

test("do not ask for image compression during image upload when 'Image Compression' setting is 'Enable (Recommended)'", async ({
page
}) => {
const app = new AppModel(page);
await app.goto();
const settings = await app.goToSettings();
await settings.selectImageCompression({
value: "1",
label: "Enable (Recommended)"
});
await settings.close();

const notes = await app.goToNotes();
await notes.createNote(NOTE);
await notes.editor.attachImage();
await page.getByText("Enable compression").waitFor({ state: "hidden" });
});

test("do not ask for image compression during image upload when 'Image Compression' setting is 'Disable'", async ({
page
}) => {
const app = new AppModel(page);
await app.goto();
const settings = await app.goToSettings();
await settings.selectImageCompression({
value: "2",
label: "Disable"
});
await settings.close();

const notes = await app.goToNotes();
await notes.createNote(NOTE);
await notes.editor.attachImage();
await page.getByText("Enable compression").waitFor({ state: "hidden" });
});
44 changes: 38 additions & 6 deletions apps/web/src/components/editor/picker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,16 @@ import {
hashStream,
writeEncryptedFile
} from "../../interfaces/fs";
import Config from "../../utils/config";
import { compressImage, FileWithURI } from "../../utils/image-compressor";

const FILE_SIZE_LIMIT = 500 * 1024 * 1024;
const IMAGE_SIZE_LIMIT = 50 * 1024 * 1024;

function getImageCompressionConfig() {
return Config.get("imageCompression", 0);
}

export async function insertAttachments(type = "*/*") {
if (!isUserPremium()) {
await BuyDialog.show({});
Expand All @@ -58,12 +64,38 @@ export async function attachFiles(files: File[]) {
}

let images = files.filter((f) => f.type.startsWith("image/"));
images =
images.length > 0
? (await ImagePickerDialog.show({
images
})) || []
: [];

switch (getImageCompressionConfig()) {
case 1:
let compressedImages: FileWithURI[] = [];
for (const image of images) {
const compressed = await compressImage(image, {
maxWidth: (naturalWidth) => Math.min(1920, naturalWidth * 0.7),
width: (naturalWidth) => naturalWidth,
height: (_, naturalHeight) => naturalHeight,
resize: "contain",
quality: 0.7
});
compressedImages.push(
new FileWithURI([compressed], image.name, {
lastModified: image.lastModified,
type: image.type
})
);
}
images = compressedImages;
break;
case 2:
break;
default:
images =
images.length > 0
? (await ImagePickerDialog.show({
images
})) || []
: [];
}

const documents = files.filter((f) => !f.type.startsWith("image/"));
const attachments: Attachment[] = [];
for (const file of [...images, ...documents]) {
Expand Down
13 changes: 1 addition & 12 deletions apps/web/src/dialogs/image-picker-dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,24 +22,13 @@ import Dialog from "../components/dialog";
import { ScrollContainer } from "@notesnook/ui";
import { Flex, Image, Label, Text } from "@theme-ui/components";
import { formatBytes } from "@notesnook/common";
import { compressImage } from "../utils/image-compressor";
import { compressImage, FileWithURI } from "../utils/image-compressor";
import { BaseDialogProps, DialogManager } from "../common/dialog-manager";
import { strings } from "@notesnook/intl";

export type ImagePickerDialogProps = BaseDialogProps<false | File[]> & {
images: File[];
};
class FileWithURI extends File {
uri: string;
constructor(
fileBits: BlobPart[],
fileName: string,
options?: FilePropertyBag
) {
super(fileBits, fileName, options);
this.uri = URL.createObjectURL(this);
}
}

export const ImagePickerDialog = DialogManager.register(
function ImagePickerDialog(props: ImagePickerDialogProps) {
Expand Down
25 changes: 25 additions & 0 deletions apps/web/src/dialogs/settings/behaviour-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,31 @@ export const BehaviourSettings: SettingsGroup[] = [
]
}
]
},
{
key: "image-compression",
title: strings.imageCompression(),
description: strings.imageCompressionDesc(),
keywords: ["compress images", "image quality"],
onStateChange: (listener) =>
useSettingStore.subscribe((s) => s.imageCompression, listener),
components: [
{
type: "dropdown",
onSelectionChanged: (value) =>
useSettingStore.getState().setImageCompression(parseInt(value)),
selectedOption: () =>
useSettingStore.getState().imageCompression.toString(),
options: [
{ value: "0", title: strings.askEveryTime() },
{
value: "1",
title: `${strings.enable()} (${strings.recommended()})`
},
{ value: "2", title: strings.disable() }
]
}
]
}
]
},
Expand Down
6 changes: 6 additions & 0 deletions apps/web/src/stores/setting-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ class SettingStore extends BaseStore<SettingStore> {

trashCleanupInterval: TrashCleanupInterval = 7;
homepage = Config.get("homepage", 0);
imageCompression = Config.get("imageCompression", 0);
desktopIntegrationSettings?: DesktopIntegration;
autoUpdates = true;
isFlatpak = false;
Expand Down Expand Up @@ -125,6 +126,11 @@ class SettingStore extends BaseStore<SettingStore> {
Config.set("homepage", homepage);
};

setImageCompression = (imageCompression: number) => {
this.set({ imageCompression });
Config.set("imageCompression", imageCompression);
};

setDesktopIntegration = async (settings: DesktopIntegration) => {
const { desktopIntegrationSettings } = this.get();

Expand Down
12 changes: 12 additions & 0 deletions apps/web/src/utils/image-compressor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,18 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

export class FileWithURI extends File {
uri: string;
constructor(
fileBits: BlobPart[],
fileName: string,
options?: FilePropertyBag
) {
super(fileBits, fileName, options);
this.uri = URL.createObjectURL(this);
}
}

type DeriveDimension = (naturalWidth: number, naturalHeight: number) => number;

interface CompressorOptions {
Expand Down
Loading

0 comments on commit 801138b

Please sign in to comment.