Skip to content

Commit

Permalink
feat: 首页增加编辑动图
Browse files Browse the repository at this point in the history
  • Loading branch information
027xiguapi committed Jan 2, 2024
1 parent a0486a2 commit 0c77147
Show file tree
Hide file tree
Showing 10 changed files with 164 additions and 18 deletions.
4 changes: 4 additions & 0 deletions packages/web/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# @pear-rec/web

## 1.3.13

feat: 首页增加编辑动图

## 1.3.12

fix: 编辑动图初始化渲染bug
Expand Down
58 changes: 58 additions & 0 deletions packages/web/src/components/card/editGifCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import React, { useImperativeHandle, forwardRef, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { EditOutlined, GifOutlined } from '@ant-design/icons';
import { Space, Card, Dropdown, Modal } from 'antd';

const EditGifCard = forwardRef((props: any, ref: any) => {
const { t } = useTranslation();
const fileRef = useRef(null);

function handleUploadFile(event) {
const file = event.target.files[0];
if (window.electronAPI) {
window.electronAPI.sendEiOpenWin({ imgUrl: file.path });
} else {
const imgUrl = window.URL.createObjectURL(file);
Modal.confirm({
title: '提示',
content: `是否编辑${file.name}`,
okText: t('modal.ok'),
cancelText: t('modal.cancel'),
onOk() {
window.open(`/editGif.html?imgUrl=${encodeURIComponent(imgUrl)}`);
},
});
}
event.target.value = '';
}

function handleClipScreenClick(type) {
if (window.isElectron) {
window.electronAPI.sendCsOpenWin({ type });
window.electronAPI.sendMaCloseWin();
} else {
location.href = `/recorderScreen.html?type=${type}`;
}
}

return (
<Card hoverable bordered={false} style={{ maxWidth: 300, minWidth: 140, height: 130 }}>
<span className="extra" onClick={() => fileRef.current.click()}>
{t('home.edit')}
</span>
<div className="cardContent">
<GifOutlined className="cardIcon" onClick={() => handleClipScreenClick('gif')} />
<div className="cardTitle">{t('home.gif')}</div>
</div>
<input
type="file"
ref={fileRef}
accept=".gif"
className="fileRef"
onChange={handleUploadFile}
/>
</Card>
);
});

export default EditGifCard;
40 changes: 35 additions & 5 deletions packages/web/src/components/card/viewImageCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,18 @@ import { Space, Card, Dropdown, Modal } from 'antd';

const ViewImageCard = forwardRef((props: any, ref: any) => {
useImperativeHandle(ref, () => ({
handleViewImage,
// handleViewImage,
}));

const { t } = useTranslation();
const fileRef = useRef(null);
const imgRef = useRef(null);
const directoryRef = useRef(null);

function handleViewImage(e: any) {
window.electronAPI ? window.electronAPI.sendViOpenWin() : (location.href = '/viewImage.html');
e.stopPropagation();
}
// function handleViewImage(e: any) {
// window.electronAPI ? window.electronAPI.sendViOpenWin() : (location.href = '/viewImage.html');
// e.stopPropagation();
// }

const onClick: MenuProps['onClick'] = ({ key }) => {
if (key == 'file') {
Expand Down Expand Up @@ -57,6 +58,25 @@ const ViewImageCard = forwardRef((props: any, ref: any) => {
event.target.value = '';
}

function handleUploadImg(event) {
const file = event.target.files[0];
if (window.electronAPI) {
window.electronAPI.sendEiOpenWin({ imgUrl: file.path });
} else {
const imgUrl = window.URL.createObjectURL(file);
Modal.confirm({
title: '提示',
content: `是否编辑${file.name}`,
okText: t('modal.ok'),
cancelText: t('modal.cancel'),
onOk() {
window.open(`/editImage.html?imgUrl=${encodeURIComponent(imgUrl)}`);
},
});
}
event.target.value = '';
}

function handleUploadDirectory(event) {
const file = event.target.files[0];
if (window.electronAPI) {
Expand All @@ -67,6 +87,9 @@ const ViewImageCard = forwardRef((props: any, ref: any) => {

return (
<Card hoverable bordered={false} style={{ maxWidth: 300, minWidth: 140, height: 130 }}>
<span className="extra" onClick={() => imgRef.current.click()}>
{t('home.edit')}
</span>
<div className="cardContent">
<Dropdown menu={{ items, onClick }}>
<Space>
Expand All @@ -83,6 +106,13 @@ const ViewImageCard = forwardRef((props: any, ref: any) => {
className="fileRef"
onChange={handleUploadFile}
/>
<input
type="file"
ref={imgRef}
accept="image/png,image/jpeg,.webp"
className="fileRef"
onChange={handleUploadImg}
/>
<input
type="file"
ref={directoryRef}
Expand Down
2 changes: 0 additions & 2 deletions packages/web/src/components/editGif/GifConverter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,6 @@ export default function VideoToGifConverter({ videoFrames, user }) {

gif.on('finished', (blob) => {
gifBlobRef.current = blob;
const gifUrl = URL.createObjectURL(blob);
gifRef.current.src = gifUrl;
});

gif.on('progress', (progress) => {
Expand Down
7 changes: 5 additions & 2 deletions packages/web/src/i18n/locales/de-DE.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@
"viewImage": "Bild anzeigen",
"playAudio": "Audio abspielen",
"watchVideo": "Video abspielen",
"history": "Verlauf"
"history": "Verlauf",
"editImg": "edit image",
"gif": "gif",
"edit": "edit"
},
"editImage": {
"save": "speichern"
Expand Down Expand Up @@ -74,4 +77,4 @@
"close": "schließen",
"download": "Download"
}
}
}
6 changes: 4 additions & 2 deletions packages/web/src/i18n/locales/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@
"playAudio": "play audio",
"watchVideo": "watch video",
"history": "history",
"editImg": "edit image"
"editImg": "edit image",
"gif": "gif",
"edit": "edit"
},
"editImage": {
"save": "save"
Expand Down Expand Up @@ -82,4 +84,4 @@
"openServer": "open server",
"serverPath": "server path"
}
}
}
6 changes: 4 additions & 2 deletions packages/web/src/i18n/locales/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@
"playAudio": "查看音频",
"watchVideo": "查看视频",
"history": "历史",
"editImg": "修改图片"
"editImg": "编辑图片",
"gif": "动图",
"edit": "编辑"
},
"editImage": {
"save": "保存"
Expand Down Expand Up @@ -82,4 +84,4 @@
"openServer": "打开服务",
"serverPath": "服务地址"
}
}
}
6 changes: 4 additions & 2 deletions packages/web/src/pages/editGif/index.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@
position: relative;
img {
padding: 5px;
height: calc(100% - 15px);
margin: 0 5px;
height: calc(100% - 16px);
border: 1px solid #ccc;
}
.info {
position: absolute;
Expand All @@ -61,7 +63,7 @@
-1px 1px 1px black;
}
}
.current {
.current img {
background-color: rgb(203 232 246);
}
}
Expand Down
49 changes: 48 additions & 1 deletion packages/web/src/pages/editGif/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,55 @@ const EditGif = () => {
const searchParams = new URLSearchParams(paramsString);
let videoUrl = searchParams.get('videoUrl');
let filePath = searchParams.get('filePath');
let imgUrl = searchParams.get('imgUrl');

videoUrl ? loadVideo(videoUrl) : loadVideoFrames(filePath);
videoUrl && loadVideo(videoUrl);
filePath && loadVideoFrames(filePath);
imgUrl && loadImg(imgUrl);
}

const fetchImageByteStream = async (imgUrl: string) => {
const response = await fetch(imgUrl);
return response.body!;
};

const createImageDecoder = async (imageByteStream: ReadableStream<Uint8Array>) => {
const imageDecoder = new (window as any).ImageDecoder({
data: imageByteStream,
type: 'image/gif',
});
await imageDecoder.tracks.ready;
await imageDecoder.completed;
return imageDecoder;
};

async function loadImg(imgUrl) {
const imageByteStream = await fetchImageByteStream(imgUrl);
const imageDecoder = await createImageDecoder(imageByteStream);
const { image: imageFrame } = await imageDecoder.decode({ frameIndex: 0 });
const frameCount = imageDecoder.tracks.selectedTrack!.frameCount;
const frameDuration = imageFrame.duration! / 1000;
let _videoFrames = [];

for (let frameIndex = 0; frameIndex < frameCount; frameIndex++) {
const result = await imageDecoder.decode({ frameIndex });
await saveImg(result.image, frameIndex);
result.image.close();
}
setVideoFrames(_videoFrames);

async function saveImg(videoFrame, frameIndex) {
const canvas = new OffscreenCanvas(videoFrame.displayWidth, videoFrame.displayHeight);
const context = canvas.getContext('2d');
context.drawImage(videoFrame, 0, 0);
const blob = await canvas.convertToBlob({ type: 'image/jpeg' });
const url = URL.createObjectURL(blob);
_videoFrames.push({
url: url,
filePath: frameIndex,
index: frameIndex,
});
}
}

function loadVideo(videoUrl) {
Expand Down
4 changes: 2 additions & 2 deletions packages/web/src/pages/home/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import RecordAudioCard from '../../components/card/recordAudioCard';
import ViewImageCard from '../../components/card/viewImageCard';
import ViewVideoCard from '../../components/card/viewVideoCard';
import ViewAudioCard from '../../components/card/viewAudioCard';
import EditImageCard from '../../components/card/editImageCard';
import EditGifCard from '../../components/card/editGifCard';
import ininitApp from '../../pages/main';
import { useUserApi } from '../../api/user';
import styles from './index.module.scss';
Expand Down Expand Up @@ -107,7 +107,7 @@ const Home: React.FC = () => {
<ViewAudioCard />
</Col>
<Col span={6}>
<EditImageCard />
<EditGifCard />
</Col>
</Row>
</div>
Expand Down

0 comments on commit 0c77147

Please sign in to comment.