Git地址:https://github.com/sunxiuguo/VisualClipboardhtml
女票:有的時候複製粘貼過的內容還想再看一下,然而又忘了原來的內容是在哪了,找起來還挺麻煩的node
我:看爸爸給你寫個app,允你免費試用!react
女票:??給你臉了?git
咳咳 是動手開始寫代碼, 不是被女票動手打
github
雖然歷來沒寫過electron,可是記得這貨是支持剪貼板API的,那就擼袖子開始幹,就當練練手了!canvas
首先明確咱們的目標:api
監聽系統剪貼板,暫時的實現是定時去讀剪貼板當前的內容,定時任務使用的是node-schedule,能夠很方便地設置頻率。app
// 這裏是每秒都去拿一次剪貼板的內容,而後進行存儲 startWatching = () => { if (!this.watcherId) { this.watcherId = schedule.scheduleJob('* * * * * *', () => { Clipboard.writeImage(); Clipboard.writeHtml(); }); } return clipboard; };
目前只是本地應用,尚未作多端的同步,因此直接用了indexDB來作存儲。
上面代碼中的Clipboard.writeImage()
以及Clipboard.writeHtml()
就是向indexDB中寫入。electron
static writeHtml() { if (Clipboard.isDiffText(this.previousText, clipboard.readText())) { this.previousText = clipboard.readText(); Db.add('html', { createTime: Date.now(), html: clipboard.readHTML(), content: this.previousText }); } }
老哥們若是有更好的方法歡迎提出,我學習一波。由於我是第一次寫,賊菜,實在沒想到其餘的方法...
async
static writeImage() { const nativeImage = clipboard.readImage(); const jpegBufferLow = nativeImage.toJPEG(jpegQualityLow); const md5StringLow = md5(jpegBufferLow); if (Clipboard.isDiffText(this.previousImageMd5, md5StringLow)) { this.previousImageMd5 = md5StringLow; if (!nativeImage.isEmpty()) { const jpegBuffer = nativeImage.toJPEG(jpegQualityHigh); const md5String = md5(jpegBuffer); const now = Date.now(); const pathByDate = `${hostPath}/${DateFormat.format( now, 'YYYYMMDD' )}`; xMkdirSync(pathByDate); const path = `${pathByDate}/${md5String}.jpeg`; const pathLow = `${pathByDate}/${md5StringLow}.jpeg`; fs.writeFileSync(pathLow, jpegBufferLow); Db.add('image', { createTime: now, content: path, contentLow: pathLow }); fs.writeFile(path, jpegBuffer, err => { if (err) { console.error(err); } }); } } }
startWatching = () => { if (!this.deleteSchedule) { this.deleteSchedule = schedule.scheduleJob('* * 1 * * *', () => { Clipboard.deleteExpiredRecords(); }); } return clipboard; }; static deleteExpiredRecords() { const now = Date.now(); const expiredTimeStamp = now - 1000 * 60 * 60 * 24 * 7; // delete record in indexDB Db.deleteByTimestamp('html', expiredTimeStamp); Db.deleteByTimestamp('image', expiredTimeStamp); // remove jpg with fs const dateDirs = fs.readdirSync(hostPath); dateDirs.forEach(dirName => { if ( Number(dirName) <= Number(DateFormat.format(expiredTimeStamp, 'YYYYMMDD')) ) { rimraf(`${hostPath}/${dirName}`, error => { if (error) { console.error(error); } }); } }); }
上面已經完成了定時的寫入db,接下來咱們要作的是實時展現db中存儲的內容。
1. 定義userInterval來準備定時刷新
/** * react hooks - useInterval * https://overreacted.io/zh-hans/making-setinterval-declarative-with-react-hooks/ */ import { useEffect, useRef } from 'react'; export default function useInterval(callback, delay) { const savedCallback = useRef(); useEffect(() => { savedCallback.current = callback; }); useEffect(() => { function tick() { savedCallback.current(); } // 當delay === null時, 暫停interval if (delay !== null) { const timer = setInterval(tick, delay); return () => clearInterval(timer); } }, [delay]); }
2. 使用userInterval展現列表
const [textList, setTextList] = React.useState([]); useInterval(() => { const getTextList = async () => { let textArray = await Db.get(TYPE_MAP.HTML); if (searchWords) { textArray = textArray.filter( item => item.content.indexOf(searchWords) > -1 ); } if (JSON.stringify(textArray) !== JSON.stringify(textList)) { setTextList(textArray); } }; if (type === TYPE_MAP.HTML) { getTextList(); } }, 500);
咱們的列表項中須要包含
const renderTextItem = props => { const { columnIndex, rowIndex, data, style } = props; const index = 2 * rowIndex + columnIndex; const item = data[index]; if (!item) { return null; } if (rowIndex > 3) { setScrollTopBtn(true); } else { setScrollTopBtn(false); } return ( <Card className={classes.textCard} key={index} style={{ ...style, left: style.left, top: style.top + recordItemGutter, height: style.height - recordItemGutter, width: style.width - recordItemGutter }} > <CardActionArea> <CardMedia component="img" className={classes.textMedia} image={bannerImage} /> <CardContent className={classes.textItemContentContainer}> ... </CardContent> </CardActionArea> <CardActions style={{ display: 'flex', justifyContent: 'space-between' }} > <Chip variant="outlined" icon={<AlarmIcon />} label={DateFormat.format(item.createTime)} /> <Button size="small" color="primary" variant="contained" onClick={() => handleClickText(item.content)} > 複製 </Button> </CardActions> </Card> ); };
從剪貼板中讀到的內容,須要按照原有格式展現
剛好clipboard.readHTML([type])
能夠直接讀到html內容,那麼咱們只須要正確展現html內容便可。
<div dangerouslySetInnerHTML={{ __html: item.html }} style={{ height: 300, maxHeight: 300, width: '100%', overflow: 'scroll', marginBottom: 10 }} />
列表太長,還得加一個回到頂部的按鈕
<Zoom in={showScrollTopBtn}> <div onClick={handleClickScrollTop} role="presentation" className={classes.scrollTopBtn} > <Fab color="secondary" size="small" aria-label="scroll back to top" > <KeyboardArrowUpIcon /> </Fab> </div> </Zoom> const handleClickScrollTop = () => { const options = { top: 0, left: 0, behavior: 'smooth' }; if (textListRef.current) { textListRef.current.scroll(options); } else if (imageListRef.current) { imageListRef.current.scroll(options); } };
列表元素太多,瀏覽時間長了會卡頓,使用react-window來優化列表展現,可視區域內只展現固定元素數量。
import { FixedSizeList, FixedSizeGrid } from 'react-window'; const renderDateImageList = () => ( <AutoSizer> {({ height, width }) => ( <FixedSizeList height={height} width={width} itemSize={400} itemCount={imageList.length} itemData={imageList} innerElementType={listInnerElementType} outerRef={imageListRef} > {renderDateImageItem} </FixedSizeList> )} </AutoSizer> );
雖然這玩意最後勉強能用,可是還有不少的不足,尤爲圖像處理的那塊,後期想改爲用canvas壓縮存儲圖片。
並且說到底也沒怎麼用到electron的知識,畢竟直接使用了electron-react-boilerplate,剩下的就是在堆砌代碼。
說說這個visualClipboard最後的下場吧!
好吧,就是很雞肋,我和女票只是使用了幾天,後來發現使用場景仍是很少,就棄置了。
說到底我只是想多折騰折騰罷了。