diff --git a/.gitignore b/.gitignore index 4d29575..800f3a8 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ # production /build +/dist # misc .DS_Store diff --git a/icon.ico b/icon.ico new file mode 100644 index 0000000..54f784c Binary files /dev/null and b/icon.ico differ diff --git a/main.js b/main.js index 05aa781..fb9062c 100644 --- a/main.js +++ b/main.js @@ -1,35 +1,37 @@ const { app, BrowserWindow, ipcMain } = require('electron'); const path = require('path'); const url = require('url'); +const { install: storeInstall } = require('./store/index'); let win = null; ipcMain.on('size-change', (event, flag) => { + win.setOpacity(0); const [x, y] = win.getPosition(); if (flag) { - win.setSize(500, 300, true); - win.setPosition(x, y - 300 + 75, true); + win.setBounds({ + x, + y: y - 300 + 75, + width: 500, + height: 300, + }); } else { - win.setSize(120, 75, true); - win.setPosition(x, y + 300 - 75, true); + win.setBounds({ + x, + y: y + 300 - 75, + width: 120, + height: 75, + }); } + setTimeout(() => { + win.setOpacity(1); + }, 300); }); ipcMain.on('pos-change', (event, { x, y }) => { win.setPosition(x, y, true); }); -const { getTodo, setTodo } = require('./store'); - -ipcMain.handle('get-todo', () => { - console.log(getTodo()); - return getTodo(); -}); - -ipcMain.on('set-todo', (e, value) => { - setTodo(value); -}); - function createWindow() { win = new BrowserWindow({ width: 120, @@ -39,7 +41,7 @@ function createWindow() { frame: false, transparent: true, webPreferences: { - preload: path.join(__dirname, 'preload.js'), + preload: path.join(__dirname, 'preload/preload.js'), }, }); @@ -57,5 +59,6 @@ function createWindow() { } app.whenReady().then(() => { + storeInstall(); createWindow(); }); diff --git a/package-lock.json b/package-lock.json index 200c248..66587cf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4713,6 +4713,11 @@ "whatwg-url": "^8.0.0" } }, + "dayjs": { + "version": "1.10.8", + "resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.10.8.tgz", + "integrity": "sha512-wbNwDfBHHur9UOzNUjeKUOJ0fCb0a52Wx0xInmQ7Y8FstyajiV1NmK1e00cxsr9YrE9r7yAChE0VvpuY5Rnlow==" + }, "debounce-fn": { "version": "4.0.0", "resolved": "https://registry.npmmirror.com/debounce-fn/-/debounce-fn-4.0.0.tgz", diff --git a/package.json b/package.json index 7562c97..becab88 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "@testing-library/jest-dom": "^5.16.2", "@testing-library/react": "^12.1.3", "@testing-library/user-event": "^13.5.0", + "dayjs": "^1.10.8", "electron-store": "^8.0.1", "react": "^17.0.2", "react-dom": "^17.0.2", @@ -22,7 +23,7 @@ "eject": "react-scripts eject", "make": "node --max-old-space-size=8192 && electron-builder --win --x64", "electron": "electron .", - "electron:build": "electron-packager ./build TimeCat --platform=win32 --arch=x64 --out=./../out --asar --app-version=0.1.0" + "electron:build": "electron-packager ./build TimeCat --platform=win32 --download.mirrorOptions.mirror=https://npmmirror.com/mirrors/electron/ --arch=x64 --out=./../out --asar --app-version=0.1.0" }, "eslintConfig": { "extends": [ @@ -49,11 +50,12 @@ }, "build": { "appId": "time-cat-v0.1.0", - "productName": "timecat", + "productName": "time cat", "win": { "target": [ "nsis" - ] + ], + "icon": "icon.ico" }, "nsis": { "oneClick": false, diff --git a/preload.js b/preload/preload.js similarity index 84% rename from preload.js rename to preload/preload.js index 1bbacb0..c020099 100644 --- a/preload.js +++ b/preload/preload.js @@ -4,5 +4,6 @@ contextBridge.exposeInMainWorld('electron', { ipcRenderer: { ...ipcRenderer, on: ipcRenderer.on, + invoke: ipcRenderer.invoke, }, }); diff --git a/public/main.js b/public/electron.js similarity index 62% rename from public/main.js rename to public/electron.js index bd4dec8..9b95233 100644 --- a/public/main.js +++ b/public/electron.js @@ -1,35 +1,37 @@ const { app, BrowserWindow, ipcMain } = require('electron'); const path = require('path'); const url = require('url'); +const { install: storeInstall } = require('./store/index'); let win = null; ipcMain.on('size-change', (event, flag) => { + win.setOpacity(0); const [x, y] = win.getPosition(); if (flag) { - win.setSize(500, 300, true); - win.setPosition(x, y - 300 + 75, true); + win.setBounds({ + x, + y: y - 300 + 75, + width: 500, + height: 300, + }); } else { - win.setSize(120, 75, true); - win.setPosition(x, y + 300 - 75, true); + win.setBounds({ + x, + y: y + 300 - 75, + width: 120, + height: 75, + }); } + setTimeout(() => { + win.setOpacity(1); + }, 300); }); ipcMain.on('pos-change', (event, { x, y }) => { win.setPosition(x, y, true); }); -const { getTodo, setTodo } = require('./store'); - -ipcMain.handle('get-todo', () => { - console.log(getTodo()); - return getTodo(); -}); - -ipcMain.on('set-todo', (e, value) => { - setTodo(value); -}); - function createWindow() { win = new BrowserWindow({ width: 120, @@ -39,13 +41,13 @@ function createWindow() { frame: false, transparent: true, webPreferences: { - preload: path.join(__dirname, 'preload.js'), + preload: path.join(__dirname, 'preload/preload.js'), }, }); win.loadURL( url.format({ - pathname: path.join(__dirname, './index.html'), + pathname: path.join(__dirname, 'build/index.html'), protocol: 'file:', slashes: true, }) @@ -57,5 +59,6 @@ function createWindow() { } app.whenReady().then(() => { + storeInstall(); createWindow(); }); diff --git a/public/favicon.ico b/public/favicon.ico index a11777c..d07ecff 100644 Binary files a/public/favicon.ico and b/public/favicon.ico differ diff --git a/public/package.json b/public/package.json deleted file mode 100644 index 84ea42a..0000000 --- a/public/package.json +++ /dev/null @@ -1,52 +0,0 @@ -{ - "name": "time-cat", - "version": "0.1.0", - "private": true, - "main": "main.js", - "homepage": ".", - "dependencies": { - "@testing-library/jest-dom": "^5.16.2", - "@testing-library/react": "^12.1.3", - "@testing-library/user-event": "^13.5.0", - "electron-drag": "^2.0.0", - "electron-store": "^8.0.1", - "react": "^17.0.2", - "react-dom": "^17.0.2", - "react-scripts": "5.0.0", - "web-vitals": "^2.1.4" - }, - "scripts": { - "start": "react-scripts start", - "build": "react-scripts build", - "test": "react-scripts test", - "eject": "react-scripts eject", - "electron": "electron .", - "electron:build": "electron-packager ./build TimeCat --platform=win32 --arch=x64 --out=./../out --ar --app-version=0.1.0 --electron-version=17.1.0" - }, - "eslintConfig": { - "extends": [ - "react-app", - "react-app/jest" - ] - }, - "browserslist": { - "production": [ - ">0.2%", - "not dead", - "not op_mini all" - ], - "development": [ - "last 1 chrome version", - "last 1 firefox version", - "last 1 safari version" - ] - }, - "devDependencies": { - "electron": "^17.1.0", - "electron-builder": "^22.14.13", - "electron-packager": "^15.4.0" - }, - "build": { - "appId": "time-cat-v0.1.0" - } -} diff --git a/public/preload.js b/public/preload/preload.js similarity index 84% rename from public/preload.js rename to public/preload/preload.js index 1bbacb0..c020099 100644 --- a/public/preload.js +++ b/public/preload/preload.js @@ -4,5 +4,6 @@ contextBridge.exposeInMainWorld('electron', { ipcRenderer: { ...ipcRenderer, on: ipcRenderer.on, + invoke: ipcRenderer.invoke, }, }); diff --git a/public/store.js b/public/store.js deleted file mode 100644 index 2bf4118..0000000 --- a/public/store.js +++ /dev/null @@ -1,20 +0,0 @@ -const Store = require('electron-store'); -const store = new Store(); - -function initStore() { - if (!store.has('todo')) { - store.set('todo', []); - } -} - -initStore(); - -module.exports = { - getTodo() { - return store.get('todo'); - }, - setTodo(value) { - console.log(store.path); - return store.set('todo', value); - }, -}; diff --git a/public/store/index.js b/public/store/index.js new file mode 100644 index 0000000..b9fa242 --- /dev/null +++ b/public/store/index.js @@ -0,0 +1,29 @@ +const { ipcMain } = require('electron'); +const { TodoStore } = require('./store'); +const store = new TodoStore(); + +module.exports.install = function install() { + ipcMain.handle('get-todo', (e) => { + return store.getTodo(); + }); + + ipcMain.handle('set-todo', (e, id, value) => { + store.setTodo(id, value); + return store.getTodo(); + }); + + ipcMain.handle('add-todo', (e, value) => { + store.addTodo(value); + return store.getTodo(); + }); + + ipcMain.handle('del-todo', (e, id) => { + store.delTodo(id); + return store.getTodo(); + }); + + ipcMain.handle('clear-todo', (e, id, status) => { + store.setClear(id, status); + return store.getTodo(); + }); +}; diff --git a/public/store/store.js b/public/store/store.js new file mode 100644 index 0000000..d7e2676 --- /dev/null +++ b/public/store/store.js @@ -0,0 +1,102 @@ +const Store = require('electron-store'); +const store = new Store(); + +const { isDef, isDate } = require('../utils/def'); + +class Todo { + constructor({ id, title, time, isClear, clearTime }) { + this.id = id; + this.title = title; + this.time = isDate(time) ? new Date(time).getTime() : Date.now(); + this.isClear = isDef(isClear) ? isClear : false; + this.clearTime = isDate(clearTime) ? new Date(clearTime).getTime() : 0; + } +} + +class TodoStore { + constructor() { + this.init(); + } + + init() { + if (!store.has('todo')) { + store.set('todo', []); + } + } + + getTodo() { + return store.get('todo'); + } + + setTodo(id, value) { + const list = this.getTodo(); + const i = list.findIndex((d) => d.id === id); + + if (i > -1) { + list[i] = new Todo({ + ...value, + id, + }); + + store.set('todo', list); + return true; + } else { + return false; + } + } + + addTodo(value) { + const list = this.getTodo(); + const last = list[list.length - 1] || {}; + const id = last.id ? `${last.id + 1}` : '1'; + const newData = new Todo({ + id, + ...value, + }); + + list.push(newData); + + store.set('todo', list); + return newData; + } + + delTodo(id) { + const list = this.getTodo(); + const i = list.findIndex((d) => d.id === id); + + if (i > -1) { + list.splice(i, 1); + + store.set('todo', list); + return true; + } else { + return false; + } + } + + clearAll() { + store.set('todo', []); + return true; + } + + setClear(id, status) { + const list = this.getTodo(); + const i = list.findIndex((d) => d.id === id); + + if (i > -1) { + list[i].isClear = isDef(status) ? status : !list[i].isClear; + if (list[i].isClear) { + list[i].clearTime = Date.now(); + } else { + list[i].clearTime = 0; + } + + store.set('todo', list); + return true; + } else { + return false; + } + } +} + +module.exports.TodoStore = TodoStore; diff --git a/public/utils/def.js b/public/utils/def.js new file mode 100644 index 0000000..b1028ad --- /dev/null +++ b/public/utils/def.js @@ -0,0 +1,12 @@ +module.exports.isDef = function isDef(v) { + return v !== undefined && v !== null; +}; + +module.exports.isUnDef = function isUnDef(v) { + return v === undefined || v === null; +}; + +module.exports.isDate = function isDate(d) { + const D = new Date(d); + return !D.toString().includes('Invalid'); +}; diff --git a/src/App.js b/src/App.js index 4a0724c..f2b51a5 100644 --- a/src/App.js +++ b/src/App.js @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; import './App.css'; import './styles/index.css'; @@ -8,15 +8,17 @@ import Todo from './components/todo'; import catImg from './assets/img/cat.png'; function App() { - const initData = JSON.parse(localStorage.getItem('todo') || '[]'); const [show, setShow] = useState(false); - const [data, setData] = useState(initData); + const [data, setData] = useState([]); const { ipcRenderer } = window.electron; - console.log(data); + useEffect(() => { + ipcRenderer.invoke('get-todo').then((data) => { + setData(data); + }); + }, [show]); function handleMouseDown(e) { - console.log('down'); const mouseX = e.pageX; const mouseY = e.pageY; @@ -27,7 +29,6 @@ function App() { function handleMove(e) { if (!isDrag) { - console.log('move'); const cMouseX = e.pageX; const cMouseY = e.pageY; @@ -35,7 +36,6 @@ function App() { (cMouseX - mouseX) ** 2 + (cMouseY - mouseY) ** 2 ); if (dis > 10) { - console.log('drag'); isDrag = true; } } else { @@ -59,8 +59,37 @@ function App() { } function changeSize() { - setShow(!show); ipcRenderer.send('size-change', !show); + setTimeout( + () => { + setShow(!show); + }, + show ? 0 : 300 + ); + } + + function handleAdd(data) { + ipcRenderer.invoke('add-todo', data).then((result) => { + setData(result); + }); + } + + function handleEdit(data) { + ipcRenderer.invoke('set-todo', data.id, data).then((result) => { + setData(result); + }); + } + + function handleDel(id) { + ipcRenderer.invoke('del-todo', id).then((result) => { + setData(result); + }); + } + + function handleClear(id) { + ipcRenderer.invoke('clear-todo', id).then((result) => { + setData(result); + }); } return ( @@ -72,7 +101,15 @@ function App() { src={catImg} onMouseDown={handleMouseDown} /> - {show && } + {show && ( + + )} ); } diff --git a/src/assets/logo.png b/src/assets/logo.png new file mode 100644 index 0000000..b6a359e Binary files /dev/null and b/src/assets/logo.png differ diff --git a/src/components/todo-form/index.css b/src/components/todo-form/index.css index 4032983..9415771 100644 --- a/src/components/todo-form/index.css +++ b/src/components/todo-form/index.css @@ -44,4 +44,14 @@ .form-item__input { width: 100%; + + border: 1px solid #767676; + border-radius: 2px; + outline: none; + + box-sizing: border-box; +} + +.form-item__input:focus { + border: 1px solid #6a8af1; } diff --git a/src/components/todo-form/index.js b/src/components/todo-form/index.js index 5e2a290..c58690b 100644 --- a/src/components/todo-form/index.js +++ b/src/components/todo-form/index.js @@ -7,8 +7,12 @@ export function TodoForm(props) { function handleSubmit(e) { props.submit({ - title, - time, + data: { + ...props.data, + title, + time, + }, + status: props.status, }); props.setIsShowForm(false); e.preventDefault(); diff --git a/src/components/todo/index.js b/src/components/todo/index.js index bc84cce..2f3e5af 100644 --- a/src/components/todo/index.js +++ b/src/components/todo/index.js @@ -1,18 +1,16 @@ import React, { useState } from 'react'; +import dayjs from 'dayjs'; import './index.css'; import { TodoForm } from '../todo-form'; function Todo(props) { const [isShowForm, setIsShowForm] = useState(false); - const [editData, setEditData] = useState({ - title: '', - time: '', - }); - - const { ipcRenderer } = window.electron; + const [editData, setEditData] = useState({}); + const [status, setStatus] = useState('add'); function handleAdd() { + setStatus('add'); setEditData({ title: '', time: '', @@ -20,31 +18,32 @@ function Todo(props) { setIsShowForm(true); } - function handleEdit({ title, time }, e) { + function handleEdit({ id, title, time }, e) { + setStatus('edit'); setEditData({ + id, title, time, }); setIsShowForm(true); } - function handleSubmit(data) { - props.setData([...props.data, data]); - ipcRenderer.send('set-todo', [...props.data, data]); - localStorage.setItem('todo', JSON.stringify([...props.data, data])); + function handleSubmit({ data, status }) { + props[status](data); } - function handleDel(index) { - const temp = props.data.filter((item, i) => i !== index); - props.setData(temp); - ipcRenderer.send('set-todo', temp); - localStorage.setItem('todo', JSON.stringify(temp)); + function handleDel(id) { + props.del(id); + } + + function handleClear(id) { + props.clear(id); } return (
-

time cat

+

{isShowForm && ( diff --git a/src/index.js b/src/index.js index 19417bc..ef2edf8 100644 --- a/src/index.js +++ b/src/index.js @@ -3,20 +3,15 @@ import ReactDOM from 'react-dom'; import './index.css'; import App from './App'; import reportWebVitals from './reportWebVitals'; -const { ipcRenderer } = window.electron; -console.log(1); -ipcRenderer.invoke('get-todo').then((value) => { - localStorage.setItem('todo', JSON.stringify(value)); - ReactDOM.render( - - - , - document.getElementById('root') - ); +ReactDOM.render( + + + , + document.getElementById('root') +); - // If you want to start measuring performance in your app, pass a function - // to log results (for example: reportWebVitals(console.log)) - // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals - reportWebVitals(); -}); +// If you want to start measuring performance in your app, pass a function +// to log results (for example: reportWebVitals(console.log)) +// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals +reportWebVitals(); diff --git a/store.js b/store.js deleted file mode 100644 index 2bf4118..0000000 --- a/store.js +++ /dev/null @@ -1,20 +0,0 @@ -const Store = require('electron-store'); -const store = new Store(); - -function initStore() { - if (!store.has('todo')) { - store.set('todo', []); - } -} - -initStore(); - -module.exports = { - getTodo() { - return store.get('todo'); - }, - setTodo(value) { - console.log(store.path); - return store.set('todo', value); - }, -}; diff --git a/store/index.js b/store/index.js new file mode 100644 index 0000000..b9fa242 --- /dev/null +++ b/store/index.js @@ -0,0 +1,29 @@ +const { ipcMain } = require('electron'); +const { TodoStore } = require('./store'); +const store = new TodoStore(); + +module.exports.install = function install() { + ipcMain.handle('get-todo', (e) => { + return store.getTodo(); + }); + + ipcMain.handle('set-todo', (e, id, value) => { + store.setTodo(id, value); + return store.getTodo(); + }); + + ipcMain.handle('add-todo', (e, value) => { + store.addTodo(value); + return store.getTodo(); + }); + + ipcMain.handle('del-todo', (e, id) => { + store.delTodo(id); + return store.getTodo(); + }); + + ipcMain.handle('clear-todo', (e, id, status) => { + store.setClear(id, status); + return store.getTodo(); + }); +}; diff --git a/store/store.js b/store/store.js new file mode 100644 index 0000000..d7e2676 --- /dev/null +++ b/store/store.js @@ -0,0 +1,102 @@ +const Store = require('electron-store'); +const store = new Store(); + +const { isDef, isDate } = require('../utils/def'); + +class Todo { + constructor({ id, title, time, isClear, clearTime }) { + this.id = id; + this.title = title; + this.time = isDate(time) ? new Date(time).getTime() : Date.now(); + this.isClear = isDef(isClear) ? isClear : false; + this.clearTime = isDate(clearTime) ? new Date(clearTime).getTime() : 0; + } +} + +class TodoStore { + constructor() { + this.init(); + } + + init() { + if (!store.has('todo')) { + store.set('todo', []); + } + } + + getTodo() { + return store.get('todo'); + } + + setTodo(id, value) { + const list = this.getTodo(); + const i = list.findIndex((d) => d.id === id); + + if (i > -1) { + list[i] = new Todo({ + ...value, + id, + }); + + store.set('todo', list); + return true; + } else { + return false; + } + } + + addTodo(value) { + const list = this.getTodo(); + const last = list[list.length - 1] || {}; + const id = last.id ? `${last.id + 1}` : '1'; + const newData = new Todo({ + id, + ...value, + }); + + list.push(newData); + + store.set('todo', list); + return newData; + } + + delTodo(id) { + const list = this.getTodo(); + const i = list.findIndex((d) => d.id === id); + + if (i > -1) { + list.splice(i, 1); + + store.set('todo', list); + return true; + } else { + return false; + } + } + + clearAll() { + store.set('todo', []); + return true; + } + + setClear(id, status) { + const list = this.getTodo(); + const i = list.findIndex((d) => d.id === id); + + if (i > -1) { + list[i].isClear = isDef(status) ? status : !list[i].isClear; + if (list[i].isClear) { + list[i].clearTime = Date.now(); + } else { + list[i].clearTime = 0; + } + + store.set('todo', list); + return true; + } else { + return false; + } + } +} + +module.exports.TodoStore = TodoStore; diff --git a/utils/def.js b/utils/def.js new file mode 100644 index 0000000..b1028ad --- /dev/null +++ b/utils/def.js @@ -0,0 +1,12 @@ +module.exports.isDef = function isDef(v) { + return v !== undefined && v !== null; +}; + +module.exports.isUnDef = function isUnDef(v) { + return v === undefined || v === null; +}; + +module.exports.isDate = function isDate(d) { + const D = new Date(d); + return !D.toString().includes('Invalid'); +};