├── .env ├── .github └── FUNDING.yml ├── .gitignore ├── .rescriptsrc.js ├── .webpack.config.js ├── LICENSE.md ├── README.md ├── assets ├── ScreenShotTab2.png ├── ScreenShotTab3.png ├── ScreenShotsTab1.png └── ScreenshotRelease.png ├── package.json ├── public ├── electron.js ├── icon.icns ├── icon.png ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json ├── robots.txt └── store.js ├── src ├── App.css ├── App.js ├── App.test.js ├── Todo.css ├── Todo.js ├── components │ ├── imge-resize │ │ ├── DefaultOptions.js │ │ ├── ImageResize.js │ │ └── modules │ │ │ ├── BaseModule.js │ │ │ ├── DisplaySize.js │ │ │ ├── Resize.js │ │ │ └── Toolbar.js │ ├── react-sticky-notes │ │ ├── buttons │ │ │ └── index.js │ │ ├── icon-styles │ │ │ ├── flUhRq6tzZclQEJ-Vdg-IuiaDsNc.woff2 │ │ │ └── iconStyle.css │ │ ├── icons │ │ │ └── index.js │ │ ├── index.js │ │ ├── index.scss │ │ ├── modals │ │ │ ├── index.js │ │ │ ├── upload-modal.js │ │ │ └── upload-modal.scss │ │ ├── navbar │ │ │ ├── index.js │ │ │ └── index.scss │ │ ├── partials │ │ │ ├── note-body.js │ │ │ ├── note-body.scss │ │ │ ├── note-bubble.js │ │ │ ├── note-bubble.scss │ │ │ ├── note-dot.js │ │ │ ├── note-draggable.js │ │ │ ├── note-header.js │ │ │ ├── note-header.scss │ │ │ ├── note-maximized.js │ │ │ ├── note-maximized.scss │ │ │ ├── note-menu.js │ │ │ ├── note-menu.scss │ │ │ ├── note-minimized.js │ │ │ ├── note-minimized.scss │ │ │ ├── note-text.js │ │ │ ├── note-text.scss │ │ │ ├── note.js │ │ │ ├── note.scss │ │ │ ├── notes.js │ │ │ └── notes.scss │ │ ├── reducers │ │ │ └── reducer.js │ │ ├── utils │ │ │ ├── color-codes.js │ │ │ ├── draggable.js │ │ │ ├── get-current-datetime.js │ │ │ ├── get-element-style.js │ │ │ ├── get-note-title.js │ │ │ ├── get-notes.js │ │ │ ├── get-uuid.js │ │ │ ├── h.js │ │ │ ├── index.js │ │ │ ├── nl-to-br.js │ │ │ └── parse-csv.js │ │ └── views │ │ │ ├── bubble-view.js │ │ │ ├── bubble-view.scss │ │ │ ├── fullscreen-view.js │ │ │ ├── index.js │ │ │ ├── normal-view.js │ │ │ └── page-view.js │ ├── spotlight-search │ │ ├── Spotlight.js │ │ ├── modules │ │ │ ├── HitDetail.js │ │ │ ├── HitList.js │ │ │ ├── Hits.js │ │ │ ├── Overlay.js │ │ │ ├── SearchBar.js │ │ │ ├── SearchIcon.js │ │ │ ├── SearchInput.js │ │ │ └── SpotlightContext.js │ │ └── utils │ │ │ └── media.js │ ├── title-bar │ │ ├── index.js │ │ ├── media │ │ │ ├── close.svg │ │ │ ├── closeLight.svg │ │ │ ├── hamburger.svg │ │ │ ├── history.svg │ │ │ ├── maximize.svg │ │ │ ├── minimize.svg │ │ │ ├── next.svg │ │ │ ├── previous.svg │ │ │ └── restore.svg │ │ ├── modules │ │ │ ├── FlatPickr.js │ │ │ ├── OptionsMenu.js │ │ │ ├── TitleBar.js │ │ │ └── WindowsControl.js │ │ └── styles │ │ │ └── TitleBar.css │ ├── todo-meter │ │ ├── index.js │ │ ├── media │ │ │ ├── alldone.svg │ │ │ ├── arrow.svg │ │ │ ├── check.svg │ │ │ ├── logo.svg │ │ │ ├── pause.svg │ │ │ ├── plus.svg │ │ │ ├── resume.svg │ │ │ └── x.svg │ │ ├── modules │ │ │ ├── AddItemForm.js │ │ │ ├── Item.js │ │ │ ├── ItemList.js │ │ │ ├── Progress.js │ │ │ └── TodoMeter.js │ │ └── styles │ │ │ ├── AddItemForm.module.scss │ │ │ ├── Item.module.scss │ │ │ ├── ItemList.module.scss │ │ │ ├── Progress.module.scss │ │ │ └── variables.scss │ └── work-space │ │ ├── index.js │ │ ├── media │ │ ├── checklist.svg │ │ ├── icon.png │ │ ├── list.svg │ │ ├── notebook.svg │ │ ├── search.svg │ │ └── stickyNotes.svg │ │ ├── modules │ │ ├── AboutWindow.js │ │ ├── Editor.js │ │ └── WorkSpace.js │ │ └── styles │ │ ├── About.scss │ │ ├── Editor.css │ │ ├── WorkSpace.css │ │ └── variables.scss ├── index.css ├── index.js ├── serviceWorker.js └── setupTests.js └── yarn.lock /.env: -------------------------------------------------------------------------------- 1 | BROWSER=none -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: ashishBharadwaj 2 | ko_fi: ashishbharadwajj 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* -------------------------------------------------------------------------------- /.rescriptsrc.js: -------------------------------------------------------------------------------- 1 | module.exports = [require.resolve("./.webpack.config.js")]; -------------------------------------------------------------------------------- /.webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = config => { 2 | config.target = "electron-renderer"; 3 | return config; 4 | }; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Flawesome (An Organiser With Diary Workflow) (v0.2.3 Pre-Release) 2 | Flawesome is a cross platform modern productivity tool that will help you organise your day-today work and thoughts. 3 |
4 | All the three application components i.e. The Notebook, The Sticky Notes and The Todolist are driven by the Calendar. 5 | 6 | Application Tab 1 Screenshot 7 | 8 | Application Tab 2 Screenshot 9 | 10 | Application Tab 3 Screenshot 11 | 12 | # Download 13 | 14 | 15 | [**Flawesome v0.2.3 Pre-Release For Linux (.deb)**](https://github.com/ashishBharadwaj/flawesome/releases/download/v0.2.3/flawesome_0.2.3_amd64.deb) 16 | 17 | [**Flawesome v0.2.3 Pre-Release For Linux (.AppImage)**](https://github.com/ashishBharadwaj/flawesome/releases/download/v0.2.3/Flawesome-0.2.3.AppImage) 18 | 19 | [**Flawesome v0.2.3 Pre-Release For Windows 64-Bit**](https://github.com/ashishBharadwaj/flawesome/releases/download/v0.2.3/Flawesome.Setup.0.2.3.exe) 20 | 21 | Go to [**Releases**](https://github.com/ashishBharadwaj/flawesome/releases) to see all the releases. 22 | 23 | ** Currently I have only generated the package for Linux and Windows, soon it will be available for macos. 24 | 25 | # Build the setup yourself: 26 | 27 | ** Prequisite: Git, Node, Yarn package manager 28 | 29 | - Clone the repo: 30 | 31 | ```bash 32 | $ git clone https://github.com/ashishBharadwaj/flawesome.git 33 | ``` 34 | 35 | - Install the dependencies: 36 | 37 | ```bash 38 | $ yarn install 39 | ``` 40 | 41 | - Build for production: 42 | 43 | ```bash 44 | $ yarn build 45 | ``` 46 | 47 | - Create Package: 48 | 49 | ```bash 50 | $ yarn package 51 | ``` 52 | 53 | ## License 54 | GNU General Public License v3.0 (c) 2020 Ashish Bharadwaj J 55 | Refer to License.md file for details 56 | -------------------------------------------------------------------------------- /assets/ScreenShotTab2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashishBharadwaj/flawesome/731d4caf9a00754d39c150e1af457c787f8dee4b/assets/ScreenShotTab2.png -------------------------------------------------------------------------------- /assets/ScreenShotTab3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashishBharadwaj/flawesome/731d4caf9a00754d39c150e1af457c787f8dee4b/assets/ScreenShotTab3.png -------------------------------------------------------------------------------- /assets/ScreenShotsTab1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashishBharadwaj/flawesome/731d4caf9a00754d39c150e1af457c787f8dee4b/assets/ScreenShotsTab1.png -------------------------------------------------------------------------------- /assets/ScreenshotRelease.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashishBharadwaj/flawesome/731d4caf9a00754d39c150e1af457c787f8dee4b/assets/ScreenshotRelease.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "flawesome", 3 | "version": "0.2.3", 4 | "private": false, 5 | "license": "GNU General Public License v3.0", 6 | "author": { 7 | "name": "Ashish Bharadwaj J", 8 | "email": "ashishbharadwaj@icloud.com" 9 | }, 10 | "main": "public/electron.js", 11 | "homepage": "./", 12 | "icon": "public/icon.png", 13 | "description": "Flawesome is a modern productivity tool that will help you organise your day-today work and thoughts.", 14 | "dependencies": { 15 | "@testing-library/jest-dom": "^4.2.4", 16 | "@testing-library/react": "^9.3.2", 17 | "@testing-library/user-event": "^7.1.2", 18 | "quill": "1.3.7", 19 | "react": "^16.12.0", 20 | "react-dom": "^16.12.0", 21 | "react-scripts": "^3.4.0", 22 | "styled-components": "^5.0.1" 23 | }, 24 | "scripts": { 25 | "start": "PORT=4041 rescripts start", 26 | "build": "rescripts build", 27 | "test": "react-scripts test", 28 | "electron-start": "set ELECTRON_START_URL=http://localhost:4041 && set IS_DEV=true && electron .", 29 | "electron-start-mac": "export ELECTRON_START_URL=http://localhost:4041 && export IS_DEV=true && electron .", 30 | "electron-start-linux": "export ELECTRON_START_URL=http://localhost:4041 && export IS_DEV=true && electron .", 31 | "pre-package": "yarn build", 32 | "package": "electron-builder build --win --publish never", 33 | "package-linux": "electron-builder build --linux --publish never", 34 | "package-publish": "electron-builder build --win --mac --linux --publish always" 35 | }, 36 | "eslintConfig": { 37 | "extends": "react-app" 38 | }, 39 | "browserslist": { 40 | "production": [ 41 | ">0.2%", 42 | "not dead", 43 | "not op_mini all" 44 | ], 45 | "development": [ 46 | "last 1 chrome version", 47 | "last 1 firefox version", 48 | "last 1 safari version" 49 | ] 50 | }, 51 | "devDependencies": { 52 | "@blueprintjs/core": "^3.25.0", 53 | "@fortawesome/fontawesome-svg-core": "^1.2.27", 54 | "@fortawesome/free-solid-svg-icons": "^5.12.1", 55 | "@fortawesome/react-fontawesome": "^0.1.9", 56 | "@reach/accordion": "^0.10.0", 57 | "@rescripts/cli": "^0.0.14", 58 | "@rescripts/rescript-env": "^0.0.12", 59 | "draft-js": "^0.11.4", 60 | "electron": "^9.4.0", 61 | "electron-builder": "^22.7.0", 62 | "flatpickr": "^4.6.3", 63 | "lodash-es": "^4.17.15", 64 | "node-sass": "^4.13.1", 65 | "quill-image-drop-module": "https://github.com/ashishBharadwaj/quill-image-drop-module", 66 | "react-quill": "^1.3.5" 67 | }, 68 | "pre-push": [], 69 | "build": { 70 | "appId": "com.flawesome.app", 71 | "productName": "Flawesome", 72 | "copyright": "Copyright © 2020 Ashish Bharadwaj J", 73 | "files": [ 74 | "build/**/*", 75 | "node_modules/**/*", 76 | "build/icon.*" 77 | ], 78 | "publish": { 79 | "provider": "github", 80 | "repo": "flawesome", 81 | "owner": "ashishbharadwaj", 82 | "private": false 83 | }, 84 | "win": { 85 | "target": "NSIS", 86 | "icon": "./build/icon.png" 87 | }, 88 | "mac": { 89 | "category": "public.app-category.productivity", 90 | "icon": "./build/icon.icns" 91 | }, 92 | "linux": { 93 | "icon": "./build/icon.icns", 94 | "category": "Productivity", 95 | "packageCategory": "Productivity", 96 | "target": [ 97 | { 98 | "target": "deb" 99 | }, 100 | { 101 | "target": "AppImage" 102 | } 103 | ] 104 | } 105 | }, 106 | "rescripts": [ 107 | "env" 108 | ] 109 | } 110 | -------------------------------------------------------------------------------- /public/electron.js: -------------------------------------------------------------------------------- 1 | const {app, BrowserWindow, Menu, ipcMain, screen } = require('electron'); 2 | 3 | const Store = require('./store'); 4 | const join = require('path').join; 5 | let store = new Store({ 6 | name: process.env.IS_DEV ? 'flawesomeDbDev' : 'flawesomeDb' 7 | }); 8 | app.on('ready', createWindow); 9 | app.on('window-all-closed', () => { 10 | if (process.platform !== 'darwin') { 11 | app.quit(); 12 | } 13 | }) 14 | function createWindow () { 15 | var win = new BrowserWindow({ 16 | width: 1200, 17 | height: 810, 18 | show: false, 19 | webPreferences: { nodeIntegration: true}, 20 | frame: false, 21 | titleBarStyle: 'hidden', 22 | resizable:true, 23 | icon: join(__dirname, 'icon.png') 24 | }); 25 | 26 | const startUrl = process.env.ELECTRON_START_URL || join('file://',__dirname, './index.html') 27 | win.loadURL(startUrl); 28 | win.webContents.on('did-finish-load', () => { 29 | win.show(); 30 | }); 31 | win.on('closed', () => { 32 | store.save(); 33 | win = null; 34 | }); 35 | ipcMain.on('getAppState', (event, arg) => { 36 | event.returnValue = store.get(arg); 37 | }); 38 | ipcMain.on('storeAppState', (event,dateKey, appState) =>{ 39 | // console.log(appState) 40 | store.set(dateKey, appState); 41 | }); 42 | // Event handler for asynchronous incoming messages 43 | ipcMain.handle('getSearchResult', async (event, searchTerm) => { 44 | const result = await filterSearchTerm(searchTerm) 45 | return result 46 | }) 47 | 48 | ipcMain.on('getSavedDates', (event, arg) => { 49 | let data = store.getAll(); 50 | event.returnValue = Object.keys(data); 51 | }); 52 | const selectionMenu = Menu.buildFromTemplate([ 53 | {role: 'copy'}, 54 | {type: 'separator'}, 55 | {role: 'selectall'}, 56 | ]) 57 | 58 | const inputMenu = Menu.buildFromTemplate([ 59 | {role: 'undo'}, 60 | {role: 'redo'}, 61 | {type: 'separator'}, 62 | {role: 'cut'}, 63 | {role: 'copy'}, 64 | {role: 'paste'}, 65 | {type: 'separator'}, 66 | {role: 'selectall'}, 67 | ]) 68 | 69 | win.webContents.on('context-menu', (e, props) => { 70 | const { selectionText, isEditable } = props; 71 | if (isEditable) { 72 | inputMenu.popup(win); 73 | } else if (selectionText && selectionText.trim() !== '') { 74 | selectionMenu.popup(win); 75 | } 76 | }) 77 | if(process.env.IS_DEV){ 78 | //win.webContents.openDevTools(); 79 | } 80 | // and load the index.html of the app. win.loadFile('index.html') 81 | } 82 | async function filterSearchTerm(searchTerm){ 83 | let data = store.getAll(); 84 | data = getArrangedData(data); 85 | data = data.filter((dat)=>{ 86 | return dat.searchContent.toLowerCase().includes(searchTerm.toLowerCase()) 87 | }) 88 | return data; 89 | } 90 | function getArrangedData(data){ 91 | let newData = []; 92 | for(let key in data) 93 | { 94 | if(data.hasOwnProperty(key)){ 95 | let dat = data[key]; 96 | if(dat.editorState) 97 | { 98 | let cleanState = dat.editorState.replace(/\s*\<.*?\>\s*/g, " "); 99 | if(cleanState){ 100 | newData.push( 101 | { 102 | dateKey: key, 103 | searchContent: cleanState, 104 | elementType: 'noteBook', 105 | elementProps:{} 106 | } 107 | ); 108 | } 109 | } 110 | for(let todoKey in dat.todoState){ 111 | if(dat.todoState.hasOwnProperty(todoKey)){ 112 | if(dat.todoState[todoKey].text){ 113 | newData.push( 114 | { 115 | dateKey: key, 116 | searchContent: dat.todoState[todoKey].text, 117 | elementType: 'toDo', 118 | elementProps: { 119 | index: todoKey, 120 | status: dat.todoState[todoKey].status 121 | } 122 | } 123 | ); 124 | } 125 | } 126 | } 127 | for(let noteKey in dat.notes){ 128 | if(dat.notes.hasOwnProperty(noteKey)){ 129 | let note = dat.notes[noteKey]; 130 | if(note.text){ 131 | newData.push( 132 | { 133 | dateKey: key, 134 | searchContent: note.text, 135 | elementType: 'stickyNotes', 136 | elementProps: { 137 | id: note.id, 138 | index: noteKey 139 | } 140 | } 141 | ); 142 | } 143 | } 144 | } 145 | 146 | } 147 | } 148 | 149 | return newData; 150 | } -------------------------------------------------------------------------------- /public/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashishBharadwaj/flawesome/731d4caf9a00754d39c150e1af457c787f8dee4b/public/icon.icns -------------------------------------------------------------------------------- /public/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashishBharadwaj/flawesome/731d4caf9a00754d39c150e1af457c787f8dee4b/public/icon.png -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | flawesome 28 | 29 | 30 | 31 | 32 | 37 |
38 | 39 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashishBharadwaj/flawesome/731d4caf9a00754d39c150e1af457c787f8dee4b/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashishBharadwaj/flawesome/731d4caf9a00754d39c150e1af457c787f8dee4b/public/logo512.png -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Flawesome", 3 | "name": "Flawesome", 4 | "start_url": "./index.html", 5 | "display": "standalone", 6 | "theme_color": "#000000", 7 | "background_color": "#ffffff" 8 | } 9 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | -------------------------------------------------------------------------------- /public/store.js: -------------------------------------------------------------------------------- 1 | //const LZString = import('./lz-string'); 2 | const electron = require('electron'); 3 | const path = require('path'); 4 | const fs = require('fs'); 5 | class Store { 6 | 7 | constructor(opts) { 8 | const userDataPath = (electron.app || electron.remote.app).getPath('userData'); 9 | this.path = path.join(userDataPath, opts.name + '.json'); 10 | this.data = parseDataFile(this.path); 11 | // console.log("Store Initilixed with value :"); 12 | // console.log(this.data); 13 | } 14 | 15 | get(key) { 16 | if(this.data[key] !== undefined){ 17 | // console.log("Key "+ key + " is defined with value : ") 18 | this.data[key].date = new Date(key); 19 | return this.data[key]; 20 | // console.log(this.data[key]); 21 | } 22 | else{ 23 | return; 24 | } 25 | } 26 | getAll() 27 | { 28 | return this.data; 29 | } 30 | // ...and this will set it 31 | set(key, val) { 32 | this.data[key] = JSON.parse(val); 33 | } 34 | save() 35 | { 36 | if(this.data && Object.keys(this.data).length > 0){ 37 | fs.writeFileSync(this.path, JSON.stringify(this.data)); 38 | } 39 | } 40 | } 41 | 42 | function parseDataFile(filePath) { 43 | 44 | try { 45 | let dat = JSON.parse(fs.readFileSync(filePath)); 46 | return dat != null ? dat : {} ; 47 | } catch(error) { 48 | return {}; 49 | } 50 | } 51 | 52 | 53 | module.exports = Store; -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | .footer{ 2 | background-color: #182026; 3 | box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25); 4 | } 5 | .ab_SP_Search{ 6 | animation: slidedown 1s; 7 | } 8 | 9 | #root{ 10 | display: grid; 11 | grid-template-rows: 35px auto 20px; 12 | width: 100vw; 13 | height: 100vh; 14 | box-sizing: border-box; 15 | } 16 | 17 | @keyframes slidedown { 18 | 0% { 19 | opacity: 0; 20 | transform: translateY(-10px); 21 | } 22 | 100% { 23 | opacity: 1; 24 | transform: translateY(0); 25 | } 26 | } 27 | 28 | /* Spotlight search Styles overwrites*/ 29 | 30 | .sc-AxjAm::-webkit-scrollbar { 31 | width: 6px; 32 | height: 6px; 33 | background-color: #F5F5F5; 34 | border-radius: 0.5em; 35 | } 36 | .sc-AxjAm::-webkit-scrollbar-thumb { 37 | background-color: rgb(132, 147, 165); 38 | border-radius: 0.5em; 39 | } 40 | .sc-AxjAm::-webkit-scrollbar-track { 41 | box-shadow: inset 0 0 6px rgba(0,0,0,0.3); 42 | background-color: #F5F5F5; 43 | border-radius:.5em; 44 | } 45 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import TitleBar from './components/title-bar'; 3 | import WorkSpace from './components/work-space'; 4 | import './App.css'; 5 | import { library } from "@fortawesome/fontawesome-svg-core"; 6 | import { faSearch } from "@fortawesome/free-solid-svg-icons"; 7 | import Spotlight from "./components/spotlight-search/Spotlight"; 8 | const ipcRenderer = window.electron.ipcRenderer; 9 | library.add(faSearch); 10 | export default class App extends React.Component 11 | { 12 | constructor(props) 13 | { 14 | super(props) 15 | this.state = ipcRenderer.sendSync('getAppState', this.getDateKeyString(new Date())) || this.getDefaultState(new Date()); 16 | this.state = {...this.state, doOpen: false, defaultTab: "noteBook", aboutIsOpen: false } 17 | this.onDateChange = this.onDateChange.bind(this); 18 | this.onNoteChange = this.onNoteChange.bind(this); 19 | this.onEditorChange = this.onEditorChange.bind(this); 20 | this.onTodoChange = this.onTodoChange.bind(this); 21 | this.searchHit = this.searchHit.bind(this); 22 | this.onDoOpenSearch = this.onDoOpenSearch.bind(this); 23 | this.toggleDoOpen = this.toggleDoOpen.bind(this); 24 | this.serilizeAndStoreState = this.serilizeAndStoreState.bind(this); 25 | this.onUpdateDefaultTab = this.onUpdateDefaultTab.bind(this); 26 | this.onAboutConfigChange = this.onAboutConfigChange.bind(this); 27 | this.hasStateChanged = this.hasStateChanged.bind(this); 28 | this.hasNotebookChanged = this.hasNotebookChanged.bind(this); 29 | this.hasNotesChanged = this.hasNotesChanged.bind(this); 30 | this.hasTodoChanged = this.hasTodoChanged.bind(this); 31 | } 32 | getDefaultState(date){ 33 | return { 34 | date: date, 35 | editorState: '', 36 | notes:[], 37 | todoState: [] 38 | } 39 | } 40 | 41 | onDateChange (newDate, tabToOpen) { 42 | this.serilizeAndStoreState() 43 | let nextstate = ipcRenderer.sendSync('getAppState',this.getDateKeyString(newDate)) || this.getDefaultState(newDate); 44 | if(tabToOpen){ 45 | this.setState({...nextstate, defaultTab: tabToOpen}); 46 | }else{ 47 | this.setState(nextstate); 48 | } 49 | } 50 | onEditorChange(newContent, delta, source, editor) 51 | { 52 | this.setState({ editorState: newContent}, () => { this.serilizeAndStoreState() }); 53 | } 54 | onNoteChange (type, payload, newNotes) { 55 | this.setState({ notes: newNotes}, () => { this.serilizeAndStoreState() }); 56 | } 57 | onTodoChange(newTodoState) 58 | { 59 | this.setState({todoState: newTodoState}, () => { this.serilizeAndStoreState() }); 60 | } 61 | onUpdateDefaultTab(newDefaultTab){ 62 | this.setState({defaultTab: newDefaultTab}); 63 | } 64 | //persits the app State 65 | serilizeAndStoreState(){ 66 | if(this.hasStateChanged()){ 67 | let val = Object.assign({},this.state); 68 | delete val.doOpen; 69 | delete val.defaultTab; 70 | delete val.aboutWinConfig; 71 | ipcRenderer.send('storeAppState', this.getDateKeyString(val.date),JSON.stringify(val)); 72 | } 73 | } 74 | 75 | hasStateChanged(){ 76 | let ret = false; 77 | let lastSavedState = ipcRenderer.sendSync('getAppState',this.getDateKeyString(this.state.date)); 78 | switch(this.state.defaultTab){ 79 | case 'noteBook': 80 | ret = this.hasNotebookChanged(lastSavedState); 81 | break; 82 | case 'stickyNotes': 83 | ret = this.hasNotesChanged(lastSavedState); 84 | break; 85 | case 'toDo': 86 | ret = this.hasTodoChanged(lastSavedState); 87 | break; 88 | default: 89 | ret = false; 90 | break; 91 | } 92 | return ret; 93 | } 94 | hasNotebookChanged(lastSavedState){ 95 | let currentEditorState = this.state.editorState.replace(/\s*\<.*?\>\s*/g, "").trim(); 96 | if(lastSavedState){ 97 | let lastEditorState = lastSavedState.editorState.replace(/\s*\<.*?\>\s*/g, "").trim(); 98 | return currentEditorState !== lastEditorState; 99 | } 100 | else{ 101 | return currentEditorState ? true : false; 102 | } 103 | } 104 | hasNotesChanged(lastSavedState){ 105 | let ret = false; 106 | if(lastSavedState){ 107 | if(this.state.notes.length === 1){ 108 | if(this.state.notes.length === lastSavedState.notes.length){ 109 | ret = this.hasNoteChanged(lastSavedState.notes[0], this.state.notes[0]); 110 | }else{ 111 | ret = true; 112 | } 113 | } 114 | else if(this.state.notes.length > 1){ 115 | ret = true; 116 | } 117 | } 118 | else{ 119 | if(this.state.notes.length === 1){ 120 | ret = this.state.notes[0].text.trim() ? true : false; 121 | } 122 | else if(this.state.notes.length > 1){ 123 | ret = true; 124 | } 125 | } 126 | return ret; 127 | } 128 | hasNoteChanged(prevNote, nextNote){ 129 | let keysToCompare = ['color', 'text', 'hidden', 'width', 'height', 'position'] 130 | for(let key in nextNote){ 131 | if(keysToCompare.includes(key)){ 132 | if(prevNote.hasOwnProperty(key)){ 133 | if(key === 'position'){ 134 | if(prevNote[key].x !== nextNote[key].x || prevNote[key].y !== nextNote[key].y){ 135 | return true; 136 | } 137 | } 138 | else{ 139 | if(nextNote[key] !== prevNote[key]){ 140 | return true; 141 | } 142 | } 143 | } 144 | else{ 145 | return true; 146 | } 147 | 148 | } 149 | } 150 | return false; 151 | } 152 | hasTodoChanged(lastSavedState){ 153 | if(lastSavedState){ 154 | return true; 155 | }else{ 156 | return this.state.todoState.length > 0; 157 | } 158 | } 159 | 160 | getDateKeyString(date) 161 | { 162 | return (date.getFullYear() + "-" + (date.getMonth() + 1) + "-" + date.getDate()) 163 | } 164 | 165 | // callback func when search itrem is clicked 166 | searchHit(searchItem){ 167 | this.onDateChange(new Date(searchItem.dateKey), searchItem.elementType); 168 | } 169 | //callback func to open Spotlight Search when search icon is clicked in sidebar 170 | onDoOpenSearch(){ 171 | this.setState({doOpen: !this.state.doOpen}); 172 | } 173 | //callback func to toggle search open state when it is changed in Spotlight component 174 | toggleDoOpen(newDoOpen){ 175 | this.setState({doOpen: newDoOpen}); 176 | } 177 | onAboutConfigChange(newAboutIsOpen){ 178 | this.setState({ aboutIsOpen: newAboutIsOpen }); 179 | } 180 | render(){ 181 | return( 182 | [ 183 | , 184 | , 200 |
, 201 | , 202 | 203 | ] 204 | ); 205 | } 206 | } -------------------------------------------------------------------------------- /src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | import App from './App'; 4 | 5 | test('renders learn react link', () => { 6 | const { getByText } = render(); 7 | const linkElement = getByText(/learn react/i); 8 | expect(linkElement).toBeInTheDocument(); 9 | }); 10 | -------------------------------------------------------------------------------- /src/Todo.css: -------------------------------------------------------------------------------- 1 | .todo-container{ 2 | width: 92%; 3 | border-radius: 0.5em; 4 | padding: 20px 13px; 5 | color: white; 6 | border: 2px solid rgb(132, 147, 165); 7 | box-shadow: 1px 1px 10px rgb(114, 112, 112); 8 | } 9 | 10 | .task{ 11 | border-radius: 0.25em; 12 | padding: 0.5em; 13 | margin: 0.5em; 14 | border: 1px solid white; 15 | box-shadow: 1px 1px 10px rgb(114, 112, 112); 16 | color: black; 17 | } 18 | 19 | .task button{ 20 | background: rgb(12, 124, 251); 21 | border-radius: 0.5em; 22 | margin: 0px 5px; 23 | padding: 3px 5px; 24 | border: none; 25 | cursor: pointer; 26 | color: white; 27 | float: right; 28 | } 29 | 30 | .header{ 31 | margin: 0.5em; 32 | font-size: 2em; 33 | text-align: center; 34 | color: white; 35 | } 36 | .create-task input[type=text]{ 37 | margin: 2.5em 2em; 38 | width: 80%; 39 | outline: none; 40 | border: none; 41 | padding: 0.7em; 42 | border: 2px solid white; 43 | box-shadow: 1px 1px 10px rgb(114, 112, 112); 44 | border-radius: 0.25em; 45 | } 46 | .todo_item_text 47 | { 48 | word-wrap: break-word; 49 | } -------------------------------------------------------------------------------- /src/Todo.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import './Todo.css'; 3 | 4 | 5 | function Task({ task, index, completeTask, removeTask }) { 6 | return ( 7 |
11 | completeTask(index)}/> 12 | {task.title} 13 | 14 |
15 | ); 16 | } 17 | 18 | function CreateTask({ addTask }) { 19 | const [value, setValue] = useState(""); 20 | 21 | const handleSubmit = e => { 22 | e.preventDefault(); 23 | if (!value) return; 24 | addTask(value); 25 | setValue(""); 26 | } 27 | return ( 28 |
29 | setValue(e.target.value)} 35 | /> 36 |
37 | ); 38 | } 39 | 40 | function Todo(props) { 41 | const [tasksRemaining, setTasksRemaining] = useState(props.todoContent.tasksRemaining); 42 | const [tasks, setTasks] = useState(props.todoContent.tasks); 43 | 44 | useEffect(() => { setTasksRemaining(tasks.filter(task => !task.completed).length) }, [tasks]); 45 | 46 | useEffect(() => { 47 | setTasks(props.todoContent.tasks); 48 | }, [props.todoContent.tasks]); 49 | 50 | 51 | const addTask = title => { 52 | const newTasks = [...tasks, { title, completed: false }]; 53 | setTasks(newTasks); 54 | props.taskChanged({tasksRemaining:newTasks.filter(task => !task.completed).length, tasks:newTasks}) 55 | }; 56 | 57 | const completeTask = index => { 58 | const newTasks = [...tasks]; 59 | newTasks[index].completed = !newTasks[index].completed; 60 | setTasks(newTasks); 61 | props.taskChanged({tasksRemaining:newTasks.filter(task => !task.completed).length, tasks:newTasks}) 62 | }; 63 | 64 | const removeTask = index => { 65 | const newTasks = [...tasks]; 66 | newTasks.splice(index, 1); 67 | setTasks(newTasks); 68 | props.taskChanged({tasksRemaining:newTasks.filter(task => !task.completed), tasks:newTasks}) 69 | }; 70 | return ( 71 |
72 |
Pending tasks ({tasksRemaining})
73 |
74 | {tasks.map((task, index) => ( 75 | 82 | ))} 83 |
84 |
85 | 86 |
87 |
88 | ); 89 | } 90 | 91 | export default Todo; -------------------------------------------------------------------------------- /src/components/imge-resize/DefaultOptions.js: -------------------------------------------------------------------------------- 1 | export default { 2 | modules: [ 3 | 'DisplaySize', 4 | 'Toolbar', 5 | 'Resize', 6 | ], 7 | overlayStyles: { 8 | position: 'absolute', 9 | boxSizing: 'border-box', 10 | border: '1px dashed #444', 11 | }, 12 | handleStyles: { 13 | position: 'absolute', 14 | height: '12px', 15 | width: '12px', 16 | backgroundColor: 'white', 17 | border: '1px solid #777', 18 | boxSizing: 'border-box', 19 | opacity: '0.80', 20 | }, 21 | displayStyles: { 22 | position: 'absolute', 23 | font: '12px/1.0 Arial, Helvetica, sans-serif', 24 | padding: '4px 8px', 25 | textAlign: 'center', 26 | backgroundColor: 'white', 27 | color: '#333', 28 | border: '1px solid #777', 29 | boxSizing: 'border-box', 30 | opacity: '0.80', 31 | cursor: 'default', 32 | }, 33 | toolbarStyles: { 34 | position: 'absolute', 35 | top: '-12px', 36 | right: '0', 37 | left: '0', 38 | height: '0', 39 | minWidth: '100px', 40 | font: '12px/1.0 Arial, Helvetica, sans-serif', 41 | textAlign: 'center', 42 | color: '#333', 43 | boxSizing: 'border-box', 44 | cursor: 'default', 45 | }, 46 | toolbarButtonStyles: { 47 | display: 'inline-block', 48 | width: '24px', 49 | height: '24px', 50 | background: 'white', 51 | border: '1px solid #999', 52 | verticalAlign: 'middle', 53 | }, 54 | toolbarButtonSvgStyles: { 55 | fill: '#444', 56 | stroke: '#444', 57 | strokeWidth: '2', 58 | }, 59 | }; 60 | -------------------------------------------------------------------------------- /src/components/imge-resize/ImageResize.js: -------------------------------------------------------------------------------- 1 | import defaultsDeep from 'lodash/defaultsDeep'; 2 | import DefaultOptions from './DefaultOptions'; 3 | import { DisplaySize } from './modules/DisplaySize'; 4 | import { Toolbar } from './modules/Toolbar'; 5 | import { Resize } from './modules/Resize'; 6 | 7 | const knownModules = { DisplaySize, Toolbar, Resize }; 8 | 9 | /** 10 | * Custom module for quilljs to allow user to resize elements 11 | * (Works on Chrome, Edge, Safari and replaces Firefox's native resize behavior) 12 | * @see https://quilljs.com/blog/building-a-custom-module/ 13 | */ 14 | export default class ImageResize { 15 | 16 | constructor(quill, options = {}) { 17 | // save the quill reference and options 18 | this.quill = quill; 19 | 20 | // Apply the options to our defaults, and stash them for later 21 | // defaultsDeep doesn't do arrays as you'd expect, so we'll need to apply the classes array from options separately 22 | let moduleClasses = false; 23 | if (options.modules) { 24 | moduleClasses = options.modules.slice(); 25 | } 26 | 27 | // Apply options to default options 28 | this.options = defaultsDeep({}, options, DefaultOptions); 29 | 30 | // (see above about moduleClasses) 31 | if (moduleClasses !== false) { 32 | this.options.modules = moduleClasses; 33 | } 34 | 35 | // disable native image resizing on firefox 36 | document.execCommand('enableObjectResizing', false, 'false'); 37 | 38 | // respond to clicks inside the editor 39 | this.quill.root.addEventListener('click', this.handleClick, false); 40 | this.quill.root.addEventListener('blur', this.handleFocusOut); 41 | 42 | 43 | this.quill.root.parentNode.style.position = this.quill.root.parentNode.style.position || 'relative'; 44 | 45 | // setup modules 46 | this.moduleClasses = this.options.modules; 47 | 48 | this.modules = []; 49 | } 50 | 51 | initializeModules = () => { 52 | this.removeModules(); 53 | 54 | this.modules = this.moduleClasses.map( 55 | ModuleClass => new (knownModules[ModuleClass] || ModuleClass)(this), 56 | ); 57 | 58 | this.modules.forEach( 59 | (module) => { 60 | module.onCreate(); 61 | }, 62 | ); 63 | 64 | this.onUpdate(); 65 | }; 66 | 67 | onUpdate = () => { 68 | this.repositionElements(); 69 | this.modules.forEach( 70 | (module) => { 71 | module.onUpdate(); 72 | }, 73 | ); 74 | }; 75 | 76 | removeModules = () => { 77 | this.modules.forEach( 78 | (module) => { 79 | module.onDestroy(); 80 | }, 81 | ); 82 | 83 | this.modules = []; 84 | }; 85 | 86 | handleFocusOut = (evn) => { 87 | if(this.img){ 88 | this.hideOverlay(); 89 | } 90 | } 91 | 92 | handleClick = (evt) => { 93 | if (evt.target && evt.target.tagName && evt.target.tagName.toUpperCase() === 'IMG') { 94 | evt.target.classList.remove('keepHTML'); 95 | evt.target.classList.add('keepHTML'); 96 | if (this.img === evt.target) { 97 | // we are already focused on this image 98 | return; 99 | } 100 | if (this.img) { 101 | // we were just focused on another image 102 | this.hide(); 103 | } 104 | // clicked on an image inside the editor 105 | this.show(evt.target); 106 | } else if (this.img) { 107 | // clicked on a non image 108 | this.hide(); 109 | } 110 | }; 111 | 112 | show = (img) => { 113 | // keep track of this img element 114 | this.img = img; 115 | 116 | this.showOverlay(); 117 | 118 | this.initializeModules(); 119 | }; 120 | 121 | showOverlay = () => { 122 | if (this.overlay) { 123 | this.hideOverlay(); 124 | } 125 | 126 | this.quill.setSelection(null); 127 | 128 | // prevent spurious text selection 129 | this.setUserSelect('none'); 130 | 131 | // listen for the image being deleted or moved 132 | document.addEventListener('keyup', this.checkImage, true); 133 | this.quill.root.addEventListener('input', this.checkImage, true); 134 | 135 | // Create and add the overlay 136 | this.overlay = document.createElement('div'); 137 | Object.assign(this.overlay.style, this.options.overlayStyles); 138 | 139 | this.quill.root.parentNode.appendChild(this.overlay); 140 | 141 | this.repositionElements(); 142 | }; 143 | 144 | hideOverlay = () => { 145 | if (!this.overlay) { 146 | return; 147 | } 148 | 149 | // Remove the overlay 150 | this.quill.root.parentNode.removeChild(this.overlay); 151 | this.overlay = undefined; 152 | 153 | // stop listening for image deletion or movement 154 | document.removeEventListener('keyup', this.checkImage); 155 | this.quill.root.removeEventListener('input', this.checkImage); 156 | 157 | // reset user-select 158 | this.setUserSelect(''); 159 | }; 160 | 161 | repositionElements = () => { 162 | if (!this.overlay || !this.img) { 163 | return; 164 | } 165 | 166 | // position the overlay over the image 167 | const parent = this.quill.root.parentNode; 168 | const imgRect = this.img.getBoundingClientRect(); 169 | const containerRect = parent.getBoundingClientRect(); 170 | 171 | Object.assign(this.overlay.style, { 172 | left: `${imgRect.left - containerRect.left - 1 + parent.scrollLeft}px`, 173 | top: `${imgRect.top - containerRect.top + parent.scrollTop}px`, 174 | width: `${imgRect.width}px`, 175 | height: `${imgRect.height}px`, 176 | }); 177 | }; 178 | 179 | hide = () => { 180 | this.hideOverlay(); 181 | this.removeModules(); 182 | this.img = undefined; 183 | }; 184 | 185 | setUserSelect = (value) => { 186 | [ 187 | 'userSelect', 188 | 'mozUserSelect', 189 | 'webkitUserSelect', 190 | 'msUserSelect', 191 | ].forEach((prop) => { 192 | // set on contenteditable element and 193 | this.quill.root.style[prop] = value; 194 | document.documentElement.style[prop] = value; 195 | }); 196 | }; 197 | 198 | checkImage = (evt) => { 199 | if (this.img) { 200 | if (evt.keyCode == 46 || evt.keyCode == 8) { 201 | window.Quill.find(this.img).deleteAt(0); 202 | } 203 | this.hide(); 204 | } 205 | }; 206 | } 207 | 208 | if (window.Quill) { 209 | const ImageFormatAttributesList = [ 210 | 'alt', 211 | 'height', 212 | 'width', 213 | 'style' 214 | ]; 215 | 216 | var BaseImageFormat = window.Quill.import('formats/image'); 217 | class ImageFormat extends BaseImageFormat { 218 | static formats(domNode) { 219 | return ImageFormatAttributesList.reduce(function(formats, attribute) { 220 | if (domNode.hasAttribute(attribute)) { 221 | formats[attribute] = domNode.getAttribute(attribute); 222 | } 223 | return formats; 224 | }, {}); 225 | } 226 | format(name, value) { 227 | if (ImageFormatAttributesList.indexOf(name) > -1) { 228 | if (value) { 229 | this.domNode.setAttribute(name, value); 230 | } else { 231 | this.domNode.removeAttribute(name); 232 | } 233 | } else { 234 | super.format(name, value); 235 | } 236 | } 237 | } 238 | 239 | window.Quill.register(ImageFormat, true); 240 | //END allow image alignment styles 241 | 242 | 243 | //Add support for IE 11 244 | if (typeof Object.assign != 'function') { 245 | Object.assign = function(target) { 246 | 'use strict'; 247 | if (target == null) { 248 | throw new TypeError('Cannot convert undefined or null to object'); 249 | } 250 | 251 | target = Object(target); 252 | for (var index = 1; index < arguments.length; index++) { 253 | var source = arguments[index]; 254 | if (source != null) { 255 | for (var key in source) { 256 | if (Object.prototype.hasOwnProperty.call(source, key)) { 257 | target[key] = source[key]; 258 | } 259 | } 260 | } 261 | } 262 | return target; 263 | }; 264 | } 265 | window.Quill.register('modules/imageResize', ImageResize); 266 | } 267 | -------------------------------------------------------------------------------- /src/components/imge-resize/modules/BaseModule.js: -------------------------------------------------------------------------------- 1 | export class BaseModule { 2 | constructor(resizer) { 3 | this.overlay = resizer.overlay; 4 | this.img = resizer.img; 5 | this.options = resizer.options; 6 | this.requestUpdate = resizer.onUpdate; 7 | } 8 | /* 9 | requestUpdate (passed in by the library during construction, above) can be used to let the library know that 10 | you've changed something about the image that would require re-calculating the overlay (and all of its child 11 | elements) 12 | 13 | For example, if you add a margin to the element, you'll want to call this or else all the controls will be 14 | misaligned on-screen. 15 | */ 16 | 17 | /* 18 | onCreate will be called when the element is clicked on 19 | 20 | If the module has any user controls, it should create any containers that it'll need here. 21 | The overlay has absolute positioning, and will be automatically repositioned and resized as needed, so you can 22 | use your own absolute positioning and the 'top', 'right', etc. styles to be positioned relative to the element 23 | on-screen. 24 | */ 25 | onCreate = () => {}; 26 | 27 | /* 28 | onDestroy will be called when the element is de-selected, or when this module otherwise needs to tidy up. 29 | 30 | If you created any DOM elements in onCreate, please remove them from the DOM and destroy them here. 31 | */ 32 | onDestroy = () => {}; 33 | 34 | /* 35 | onUpdate will be called any time that the element is changed (e.g. resized, aligned, etc.) 36 | 37 | This frequently happens during resize dragging, so keep computations light while here to ensure a smooth 38 | user experience. 39 | */ 40 | onUpdate = () => {}; 41 | } 42 | -------------------------------------------------------------------------------- /src/components/imge-resize/modules/DisplaySize.js: -------------------------------------------------------------------------------- 1 | import { BaseModule } from './BaseModule'; 2 | 3 | export class DisplaySize extends BaseModule { 4 | onCreate = () => { 5 | // Create the container to hold the size display 6 | this.display = document.createElement('div'); 7 | 8 | // Apply styles 9 | Object.assign(this.display.style, this.options.displayStyles); 10 | 11 | // Attach it 12 | this.overlay.appendChild(this.display); 13 | }; 14 | 15 | onDestroy = () => {}; 16 | 17 | onUpdate = () => { 18 | if (!this.display || !this.img) { 19 | return; 20 | } 21 | 22 | const size = this.getCurrentSize(); 23 | this.display.innerHTML = size.join(' × '); 24 | if (size[0] > 120 && size[1] > 30) { 25 | // position on top of image 26 | Object.assign(this.display.style, { 27 | right: '4px', 28 | bottom: '4px', 29 | left: 'auto', 30 | }); 31 | } 32 | else if (this.img.style.float == 'right') { 33 | // position off bottom left 34 | const dispRect = this.display.getBoundingClientRect(); 35 | Object.assign(this.display.style, { 36 | right: 'auto', 37 | bottom: `-${dispRect.height + 4}px`, 38 | left: `-${dispRect.width + 4}px`, 39 | }); 40 | } 41 | else { 42 | // position off bottom right 43 | const dispRect = this.display.getBoundingClientRect(); 44 | Object.assign(this.display.style, { 45 | right: `-${dispRect.width + 4}px`, 46 | bottom: `-${dispRect.height + 4}px`, 47 | left: 'auto', 48 | }); 49 | } 50 | }; 51 | 52 | getCurrentSize = () => [ 53 | this.img.width, 54 | Math.round((this.img.width / this.img.naturalWidth) * this.img.naturalHeight), 55 | ]; 56 | } 57 | -------------------------------------------------------------------------------- /src/components/imge-resize/modules/Resize.js: -------------------------------------------------------------------------------- 1 | import { BaseModule } from './BaseModule'; 2 | 3 | export class Resize extends BaseModule { 4 | onCreate = () => { 5 | // track resize handles 6 | this.boxes = []; 7 | 8 | // add 4 resize handles 9 | this.addBox('nwse-resize'); // top left 10 | this.addBox('nesw-resize'); // top right 11 | this.addBox('nwse-resize'); // bottom right 12 | this.addBox('nesw-resize'); // bottom left 13 | 14 | this.positionBoxes(); 15 | }; 16 | 17 | onDestroy = () => { 18 | // reset drag handle cursors 19 | this.setCursor(''); 20 | }; 21 | 22 | positionBoxes = () => { 23 | const handleXOffset = `${-parseFloat(this.options.handleStyles.width) / 2}px`; 24 | const handleYOffset = `${-parseFloat(this.options.handleStyles.height) / 2}px`; 25 | 26 | // set the top and left for each drag handle 27 | [ 28 | { left: handleXOffset, top: handleYOffset }, // top left 29 | { right: handleXOffset, top: handleYOffset }, // top right 30 | { right: handleXOffset, bottom: handleYOffset }, // bottom right 31 | { left: handleXOffset, bottom: handleYOffset }, // bottom left 32 | ].forEach((pos, idx) => { 33 | Object.assign(this.boxes[idx].style, pos); 34 | }); 35 | }; 36 | 37 | addBox = (cursor) => { 38 | // create div element for resize handle 39 | const box = document.createElement('div'); 40 | 41 | // Star with the specified styles 42 | Object.assign(box.style, this.options.handleStyles); 43 | box.style.cursor = cursor; 44 | 45 | // Set the width/height to use 'px' 46 | box.style.width = `${this.options.handleStyles.width}px`; 47 | box.style.height = `${this.options.handleStyles.height}px`; 48 | 49 | // listen for mousedown on each box 50 | box.addEventListener('mousedown', this.handleMousedown, false); 51 | // add drag handle to document 52 | this.overlay.appendChild(box); 53 | // keep track of drag handle 54 | this.boxes.push(box); 55 | }; 56 | 57 | handleMousedown = (evt) => { 58 | // note which box 59 | this.dragBox = evt.target; 60 | // note starting mousedown position 61 | this.dragStartX = evt.clientX; 62 | // store the width before the drag 63 | this.preDragWidth = this.img.width || this.img.naturalWidth; 64 | // set the proper cursor everywhere 65 | this.setCursor(this.dragBox.style.cursor); 66 | // listen for movement and mouseup 67 | document.addEventListener('mousemove', this.handleDrag, false); 68 | document.addEventListener('mouseup', this.handleMouseup, false); 69 | }; 70 | 71 | handleMouseup = () => { 72 | // reset cursor everywhere 73 | this.setCursor(''); 74 | // stop listening for movement and mouseup 75 | document.removeEventListener('mousemove', this.handleDrag); 76 | document.removeEventListener('mouseup', this.handleMouseup); 77 | }; 78 | 79 | handleDrag = (evt) => { 80 | if (!this.img) { 81 | // image not set yet 82 | return; 83 | } 84 | // update image size 85 | const deltaX = evt.clientX - this.dragStartX; 86 | if (this.dragBox === this.boxes[0] || this.dragBox === this.boxes[3]) { 87 | // left-side resize handler; dragging right shrinks image 88 | this.img.width = Math.round(this.preDragWidth - deltaX); 89 | } else { 90 | // right-side resize handler; dragging right enlarges image 91 | this.img.width = Math.round(this.preDragWidth + deltaX); 92 | } 93 | this.requestUpdate(); 94 | }; 95 | 96 | setCursor = (value) => { 97 | [ 98 | document.body, 99 | this.img, 100 | ].forEach((el) => { 101 | el.style.cursor = value; // eslint-disable-line no-param-reassign 102 | }); 103 | }; 104 | } 105 | -------------------------------------------------------------------------------- /src/components/imge-resize/modules/Toolbar.js: -------------------------------------------------------------------------------- 1 | import { BaseModule } from './BaseModule'; 2 | 3 | const IconAlignLeft = ' \n \n \n \n ' 4 | const IconAlignCenter = ' \n \n \n \n ' 5 | const IconAlignRight = ' \n \n \n \n ' 6 | const Parchment = window.Quill.imports.parchment; 7 | const FloatStyle = new Parchment.Attributor.Style('float', 'float'); 8 | const MarginStyle = new Parchment.Attributor.Style('margin', 'margin'); 9 | const DisplayStyle = new Parchment.Attributor.Style('display', 'display'); 10 | 11 | export class Toolbar extends BaseModule { 12 | onCreate = () => { 13 | // Setup Toolbar 14 | this.toolbar = document.createElement('div'); 15 | Object.assign(this.toolbar.style, this.options.toolbarStyles); 16 | this.overlay.appendChild(this.toolbar); 17 | 18 | // Setup Buttons 19 | this._defineAlignments(); 20 | this._addToolbarButtons(); 21 | }; 22 | 23 | // The toolbar and its children will be destroyed when the overlay is removed 24 | onDestroy = () => {}; 25 | 26 | // Nothing to update on drag because we are are positioned relative to the overlay 27 | onUpdate = () => {}; 28 | 29 | _defineAlignments = () => { 30 | this.alignments = [ 31 | { 32 | icon: IconAlignLeft, 33 | apply: () => { 34 | DisplayStyle.add(this.img, 'inline'); 35 | FloatStyle.add(this.img, 'left'); 36 | MarginStyle.add(this.img, '0 1em 1em 0'); 37 | }, 38 | isApplied: () => FloatStyle.value(this.img) == 'left', 39 | }, 40 | { 41 | icon: IconAlignCenter, 42 | apply: () => { 43 | DisplayStyle.add(this.img, 'block'); 44 | FloatStyle.remove(this.img); 45 | MarginStyle.add(this.img, 'auto'); 46 | }, 47 | isApplied: () => MarginStyle.value(this.img) == 'auto', 48 | }, 49 | { 50 | icon: IconAlignRight, 51 | apply: () => { 52 | DisplayStyle.add(this.img, 'inline'); 53 | FloatStyle.add(this.img, 'right'); 54 | MarginStyle.add(this.img, '0 0 1em 1em'); 55 | }, 56 | isApplied: () => FloatStyle.value(this.img) == 'right', 57 | }, 58 | ]; 59 | }; 60 | 61 | _addToolbarButtons = () => { 62 | const buttons = []; 63 | this.alignments.forEach((alignment, idx) => { 64 | const button = document.createElement('span'); 65 | buttons.push(button); 66 | button.innerHTML = alignment.icon; 67 | button.addEventListener('click', () => { 68 | // deselect all buttons 69 | buttons.forEach(button => button.style.filter = ''); 70 | if (alignment.isApplied()) { 71 | // If applied, unapply 72 | FloatStyle.remove(this.img); 73 | MarginStyle.remove(this.img); 74 | DisplayStyle.remove(this.img); 75 | } else { 76 | // otherwise, select button and apply 77 | this._selectButton(button); 78 | alignment.apply(); 79 | } 80 | // image may change position; redraw drag handles 81 | this.requestUpdate(); 82 | }); 83 | Object.assign(button.style, this.options.toolbarButtonStyles); 84 | if (idx > 0) { 85 | button.style.borderLeftWidth = '0'; 86 | } 87 | Object.assign(button.children[0].style, this.options.toolbarButtonSvgStyles); 88 | if (alignment.isApplied()) { 89 | // select button if previously applied 90 | this._selectButton(button); 91 | } 92 | this.toolbar.appendChild(button); 93 | }); 94 | }; 95 | 96 | _selectButton = (button) => { 97 | button.style.filter = 'invert(20%)'; 98 | }; 99 | 100 | } 101 | -------------------------------------------------------------------------------- /src/components/react-sticky-notes/buttons/index.js: -------------------------------------------------------------------------------- 1 | import { h, getNoteTitle } from '../utils'; 2 | 3 | export function ButtonAdd({prefix, data, icons, callbacks}){ 4 | return h('button',{ 5 | key: `${prefix}--button__add`, 6 | className:`${prefix}--button ${prefix}--button__add`, 7 | onClick:(e)=>callbacks.addItem(e, {id: data?data.id:null, position:data?data.position:null, selected: true}) 8 | }, 9 | icons.add 10 | ) 11 | } 12 | 13 | export function ButtonTitle({prefix, data, targetRef, callbacks }){ 14 | return h('button',{ 15 | key: `${prefix}--button__title`, 16 | className:`${prefix}--button ${prefix}--button__title`, 17 | ref: targetRef, 18 | onClick:(e)=>callbacks.updateItem(e, { id: data?data.id:null, menu: false, selected: true, hidden: false }) 19 | }, 20 | data.title?data.title: getNoteTitle(data) 21 | ) 22 | } 23 | 24 | export function ButtonMenu({prefix, data, icons, callbacks }){ 25 | return h('button',{ 26 | key: `${prefix}--button__menu`, 27 | className:`${prefix}--button ${prefix}--button__menu`, 28 | onClick:(e)=>callbacks.updateItem(e, {id: data?data.id:null, menu: !data.menu, selected: true}) 29 | }, 30 | icons.menu 31 | ); 32 | } 33 | 34 | export function ButtonHideShow({prefix, data, icons, callbacks }){ 35 | return h('button',{ 36 | key: `${prefix}--button__hideshow`, 37 | className:`${prefix}--button ${prefix}--button__hideshow`, 38 | onClick:(e)=>callbacks.updateItem(e, {id: data?data.id:null, hidden: !data.hidden}) 39 | }, 40 | data.hidden?icons.hide:icons.show 41 | ); 42 | } 43 | 44 | export function ButtonTrash({prefix, data, icons, callbacks }){ 45 | return h('button',{ 46 | key: `${prefix}--button__trash`, 47 | className:`${prefix}--button ${prefix}--button__trash`, 48 | onClick:(e)=>callbacks.deleteItem(e, {id: data?data.id:null}) 49 | }, 50 | icons.trash 51 | ); 52 | } 53 | 54 | export function ButtonPageView({prefix, icons, callbacks, viewSize }){ 55 | return h('button',{ 56 | key: `${prefix}--button__pageview`, 57 | className:`${prefix}--button ${prefix}--button__pageview`, 58 | onClick:(e)=> callbacks.changeView(e) 59 | }, 60 | icons[viewSize]?icons[viewSize]:`icons.${viewSize}` 61 | ); 62 | } 63 | 64 | export function ButtonUpload({prefix, icons, callbacks }){ 65 | return h('button',{ 66 | key: `${prefix}--button__upload`, 67 | className:`${prefix}--button ${prefix}--button__upload`, 68 | onClick:(e)=> callbacks.changeModal(e, 'upload') 69 | }, 70 | icons.upload 71 | ); 72 | } 73 | -------------------------------------------------------------------------------- /src/components/react-sticky-notes/icon-styles/flUhRq6tzZclQEJ-Vdg-IuiaDsNc.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashishBharadwaj/flawesome/731d4caf9a00754d39c150e1af457c787f8dee4b/src/components/react-sticky-notes/icon-styles/flUhRq6tzZclQEJ-Vdg-IuiaDsNc.woff2 -------------------------------------------------------------------------------- /src/components/react-sticky-notes/icon-styles/iconStyle.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Material Icons'; 3 | font-style: normal; 4 | font-weight: 400; 5 | src: url(./flUhRq6tzZclQEJ-Vdg-IuiaDsNc.woff2) format('woff2'); 6 | } 7 | 8 | .material-icons { 9 | font-family: 'Material Icons'; 10 | font-weight: normal; 11 | font-style: normal; 12 | font-size: 24px; 13 | line-height: 1; 14 | letter-spacing: normal; 15 | text-transform: none; 16 | display: inline-block; 17 | white-space: nowrap; 18 | word-wrap: normal; 19 | direction: ltr; 20 | -moz-font-feature-settings: 'liga'; 21 | -moz-osx-font-smoothing: grayscale; 22 | } -------------------------------------------------------------------------------- /src/components/react-sticky-notes/icons/index.js: -------------------------------------------------------------------------------- 1 | import { h, getElementStyle } from '../utils'; 2 | import '../icon-styles/iconStyle.css' 3 | const iconsClassName = "material-icons"; 4 | export const add = h('i',{ className: iconsClassName, style: getElementStyle('icon') },'add'); 5 | export const trash = h('i',{ className: iconsClassName, style: getElementStyle('icon') },'delete_outlined'); 6 | export const menu = h('i',{ className: iconsClassName, style: getElementStyle('icon') },'more_horiz'); 7 | export const hide = h('i',{ className: iconsClassName, style: getElementStyle('icon') },'visibility_off'); 8 | export const show = h('i',{ className: iconsClassName, style: getElementStyle('icon') },'minimize'); 9 | export const normalview = h('i',{ className: iconsClassName, style: getElementStyle('icon') },'widgets'); 10 | export const bubbleview = h('i',{ className: iconsClassName, style: getElementStyle('icon') },'grain'); 11 | export const pageview = h('i',{ className: iconsClassName, style: getElementStyle('icon') },'fullscreen'); 12 | export const upload = h('i',{ className: iconsClassName, style: getElementStyle('icon') },'cloud_upload'); 13 | export const fullscreen = h('i',{ className: iconsClassName, style: getElementStyle('icon') },'fullscreen_exit'); 14 | -------------------------------------------------------------------------------- /src/components/react-sticky-notes/index.js: -------------------------------------------------------------------------------- 1 | import { Component } from 'react'; 2 | import reducer from './reducers/reducer'; 3 | import * as icons from './icons'; 4 | import { h, getColorCodes, getNotes, getUUID } from './utils'; 5 | import { NormalView, BubbleView, PageView, FullscreenView } from './views' ; 6 | import { UploadModal } from './modals' ; 7 | class ReactStickyNotes extends Component { 8 | static defaultProps = { 9 | useCSS: true, 10 | prefix: 'rs-notes', 11 | colorCodes: getColorCodes(), 12 | navbar: true, 13 | sessionKey: '', 14 | noteWidth: 220, 15 | noteHeight: 220, 16 | containerWidth: '100%', 17 | containerHeight: '94%', 18 | icons, 19 | useMaterialIcons: true 20 | } 21 | constructor(props) { 22 | super(props); 23 | this.state = { 24 | modal: null, 25 | viewSize: 'normalview', 26 | items: getNotes(props.colorCodes, props.notes) 27 | }; 28 | } 29 | componentDidMount(){ 30 | if(this.props.useCSS){ 31 | require('./index.scss'); 32 | } 33 | } 34 | componentWillReceiveProps(nextProps){ 35 | if(nextProps.notes && nextProps.notes.length == 0) 36 | { 37 | nextProps.notes.push({ 38 | id: getUUID(), 39 | color: this.getColor(), 40 | text: '', 41 | selected: true, 42 | position: { 43 | x: 0, 44 | y: 0 45 | }, 46 | width: 220, 47 | height:220 48 | }); 49 | } 50 | this.props = nextProps; 51 | this.setState({items: nextProps.notes}); 52 | } 53 | dispatch = (options) => { 54 | let { type, payload } = options; 55 | if(this.props.onBeforeChange){ 56 | payload = this.props.onBeforeChange(type, payload, [...this.state.items]) 57 | } 58 | this.setState( 59 | reducer(this.state, { type, payload }), 60 | ()=>{ 61 | if(this.props.onChange){ 62 | this.props.onChange(type, payload, [...this.state.items]) 63 | } 64 | } 65 | ) 66 | } 67 | getColor() { 68 | return this.props.colorCodes[Math.floor(Math.random() * this.props.colorCodes.length)]; 69 | } 70 | addItem = (e, data) => { 71 | const { items } = this.state; 72 | const index=data?items.findIndex(item=>item.id===data.id)+1:items.length; 73 | let lastItemX = items && items.length > 1 ? items[items.length-1].position.x : 0; 74 | let lastItemY = items && items.length > 1 ? items[items.length-1].position.y : 0; 75 | //console.log(e.currentTarget.parentElement.getBoundingClientRect()); 76 | this.dispatch({ 77 | type: 'add', 78 | payload: { 79 | index, 80 | data: { 81 | id: getUUID(), 82 | color: this.getColor(), 83 | text: '', 84 | selected: true, 85 | position: { 86 | x: lastItemX + 5, 87 | y: lastItemY + 5 88 | }, 89 | width:220, 90 | height:220 91 | } 92 | } 93 | }); 94 | } 95 | updateItem = (e, data) => { 96 | this.dispatch({ 97 | type: 'update', 98 | payload: { 99 | data 100 | } 101 | }); 102 | } 103 | deleteItem = (e, data) => { 104 | this.dispatch({ 105 | type: 'delete', 106 | payload: { 107 | data 108 | } 109 | }); 110 | } 111 | changeView = (e) => { 112 | this.dispatch({ 113 | type: 'changeview' 114 | }); 115 | } 116 | changeModal = (e, modal) => { 117 | this.dispatch({ 118 | type: 'changemodal', 119 | payload:{ 120 | modal 121 | } 122 | }); 123 | } 124 | saveJSON = (e, json) => { 125 | this.dispatch({ 126 | type: 'import', 127 | payload:{ 128 | items: getNotes(this.props.colorCodes, json) 129 | } 130 | }); 131 | } 132 | render() { 133 | const { items, viewSize, modal } = this.state; 134 | let View = null; 135 | if(modal){ 136 | switch(modal){ 137 | case "upload": 138 | View = UploadModal 139 | break; 140 | } 141 | }else{ 142 | switch(viewSize){ 143 | case "pageview": 144 | View = PageView 145 | break; 146 | case "bubbleview": 147 | View = BubbleView 148 | break; 149 | case "fullscreen": 150 | View = FullscreenView 151 | break; 152 | default: 153 | View = NormalView 154 | break; 155 | } 156 | } 157 | return h( View, { 158 | ...this.props, 159 | items, 160 | icons: { ...icons, ...this.props.icons }, 161 | viewSize, 162 | callbacks: { 163 | changeView: this.changeView, 164 | addItem: this.addItem, 165 | updateItem: this.updateItem, 166 | deleteItem: this.deleteItem, 167 | changeModal: this.changeModal, 168 | saveJSON: this.saveJSON 169 | } 170 | }) 171 | } 172 | 173 | } 174 | export default ReactStickyNotes; 175 | -------------------------------------------------------------------------------- /src/components/react-sticky-notes/index.scss: -------------------------------------------------------------------------------- 1 | @import './navbar/index.scss'; 2 | @import './partials/note.scss'; 3 | @import './partials/notes.scss'; 4 | @import './partials/note-header.scss'; 5 | @import './partials/note-text.scss'; 6 | @import './partials/note-bubble.scss'; 7 | @import './partials/note-menu.scss'; 8 | @import './modals/upload-modal.scss'; 9 | @import './views/bubble-view.scss'; 10 | @import './partials/note-body.scss'; 11 | .rs-notes{ 12 | *,*::before,*::after{ 13 | box-sizing: border-box; 14 | } 15 | @include notes(); 16 | @include noteBody(); 17 | @include note(); 18 | @include navbar(); 19 | @include noteHeader(); 20 | @include noteText(); 21 | @include noteMenu(); 22 | @include noteBubble(); 23 | @include uploadForm(); 24 | @include bubbleView(); 25 | } 26 | -------------------------------------------------------------------------------- /src/components/react-sticky-notes/modals/index.js: -------------------------------------------------------------------------------- 1 | export * from './upload-modal'; 2 | -------------------------------------------------------------------------------- /src/components/react-sticky-notes/modals/upload-modal.js: -------------------------------------------------------------------------------- 1 | import { h, getElementStyle, parseCSV } from '../utils'; 2 | import React, { Component, Fragment } from 'react'; 3 | export class UploadModal extends Component{ 4 | constructor(){ 5 | super(); 6 | this.state = { 7 | error: '', 8 | response: null, 9 | contents: null 10 | } 11 | this.jsonInput = React.createRef(); 12 | } 13 | componentDidMount(){ 14 | this.handleResponse(null, null, null); 15 | } 16 | uploadFile = (e) => { 17 | const file = e.target.files[0]; 18 | if(file){ 19 | if( file.type==='application/json' || file.type==="application/vnd.ms-excel" ){ 20 | var reader = new FileReader(); 21 | reader.onload = (readerEvent) => { 22 | let response, responseText; 23 | switch(file.type){ 24 | case "application/vnd.ms-excel": 25 | response = parseCSV(readerEvent.target.result); 26 | break; 27 | case "application/json": 28 | response = JSON.parse(readerEvent.target.result); 29 | break; 30 | } 31 | responseText = JSON.stringify(response, null, 4); 32 | this.handleResponse(null, responseText, response); 33 | }; 34 | reader.onerror = function(readerEvent) { 35 | this.handleResponse("File could not be read! Code " + readerEvent.target.error.code); 36 | }; 37 | reader.readAsText(file); 38 | }else{ 39 | this.handleResponse( "File type is not allowed. Please upload a JSON or CSV file." ); 40 | } 41 | } 42 | } 43 | handleResponse(err, contents, response){ 44 | let error = err; 45 | if(response){ 46 | if(!Array.isArray(response)){ 47 | error = "Please upload a valid JSON or CSV file." 48 | response = null; 49 | contents = null; 50 | } 51 | } 52 | this.setState({ 53 | error, 54 | contents:contents, 55 | response:response 56 | }) 57 | } 58 | saveJSON = (e) => { 59 | const { response } = this.state; 60 | if(response){ 61 | this.props.callbacks.saveJSON(e, response ) 62 | } 63 | } 64 | render(){ 65 | const { error, contents } = this.state; 66 | const props = this.props; 67 | return h('div',{ 68 | key: `${props.prefix}`, 69 | className: `${props.prefix} ${props.prefix}--file-upload` 70 | },[ 71 | contents?h('textarea',{ 72 | key: 'file-upload--contents', 73 | className: `${props.prefix}--file-preview`, 74 | readOnly: true, 75 | defaultValue: contents 76 | }):null, 77 | h('div', { 78 | key: `${props.prefix}--file-drop`, 79 | className: `${props.prefix}--file-drop ${!contents?props.prefix+'--file-drop__cover':''}` 80 | }, [ 81 | h('input', { 82 | key: 'upload-button', 83 | type: 'file', 84 | id: `${props.prefix}--file-input`, 85 | className: `${props.prefix}--file-input`, 86 | accept: ".json,.csv", 87 | onChange: (e)=>this.uploadFile(e), 88 | placeholder: 'upload file' 89 | }), 90 | h('p', { 91 | key: 'upload-link', 92 | className: `${props.prefix}--upload-link`, 93 | },[ 94 | h('label', { 95 | key: "choose-a-file", 96 | className: `${props.prefix}--file-label`, 97 | htmlFor: `${props.prefix}--file-input` 98 | }, contents?"Choose a another file":"Choose a file" ), 99 | h('span', { 100 | key: "drop-a-file" 101 | }, " or drag it here.") 102 | ]), 103 | ]), 104 | h('div',{ 105 | key: 'file-upload--actions', 106 | className: `${props.prefix}--upload-actions`, 107 | },[ 108 | error?h('p', { 109 | key: 'upload-error', 110 | className: `${props.prefix}--upload-error`, 111 | }, error ):null, 112 | contents?h(Fragment,{ 113 | key: 'file-upload--save-cancel', 114 | }, 115 | h('button', { 116 | key: 'file-upload--cancel', 117 | className: `${props.prefix}--form-cancel`, 118 | onClick: (e)=>props.callbacks.changeModal(e, null ) 119 | }, 'Cancel' ), 120 | h('button', { 121 | key: 'file-upload--save', 122 | className: `${props.prefix}--form-save`, 123 | onClick: this.saveJSON 124 | }, 'Save' ) 125 | ):h('button', { 126 | key: 'file-upload--cancel', 127 | className: `${props.prefix}--form-cancel`, 128 | onClick: (e)=>props.callbacks.changeModal(e, null ) 129 | }, 'Back to notes.' ) 130 | ]) 131 | ]) 132 | 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/components/react-sticky-notes/modals/upload-modal.scss: -------------------------------------------------------------------------------- 1 | @mixin uploadForm() { 2 | &--file-upload{ 3 | position: relative; 4 | min-height: 100%; 5 | display: flex; 6 | flex-direction: column; 7 | } 8 | p{ 9 | margin: 5px 0; 10 | } 11 | &--file-preview{ 12 | text-align: left; 13 | overflow: auto; 14 | width: 100%; 15 | border: none; 16 | resize: none; 17 | flex-grow: 1; 18 | min-height: 150px; 19 | } 20 | &--file-drop{ 21 | padding: 30px; 22 | position: relative; 23 | background-color: #eeeeee; 24 | color: #999999; 25 | outline: 2px dashed #999999; 26 | outline-offset: -10px; 27 | transition: outline-offset .15s ease-in-out, background-color .15s linear; 28 | text-align: center; 29 | &:hover{ 30 | outline-offset: -20px; 31 | outline-color: #cccccc; 32 | background-color: #ffffff; 33 | } 34 | &__cover{ 35 | flex-grow: 1; 36 | } 37 | 38 | } 39 | &--upload-link{ 40 | font-size: 1.25rem; 41 | } 42 | &--file-input{ 43 | width: 100%; 44 | height: 100%; 45 | opacity: 0; 46 | overflow: hidden; 47 | position: absolute; 48 | left: 0; 49 | top: 0; 50 | z-index: 0; 51 | } 52 | &--file-label{ 53 | position: relative; 54 | cursor: pointer; 55 | color: #666666; 56 | &:hover{ 57 | color: #999999; 58 | } 59 | } 60 | &--upload-actions{ 61 | padding: 24px; 62 | } 63 | &--form-cancel, 64 | &--form-save{ 65 | padding: 8px 15px; 66 | border: none; 67 | background: #999999; 68 | color: #ffffff; 69 | cursor: pointer; 70 | &:hover{ 71 | background: rgba(0, 0, 0, 0.15); 72 | } 73 | } 74 | &--upload-error{ 75 | background-color: #cc0000; 76 | font-size: .85em; 77 | color: #fff; 78 | padding: 3px 5px; 79 | } 80 | &--upload-actions{ 81 | text-align: center; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/components/react-sticky-notes/navbar/index.js: -------------------------------------------------------------------------------- 1 | import { h, getElementStyle } from '../utils'; 2 | import NoteHeader from '../partials/note-header'; 3 | import { ButtonAdd, ButtonTitle, ButtonMenu, ButtonUpload, ButtonTrash, ButtonPageView } from '../buttons'; 4 | function NavBar({viewSize, prefix, items, callbacks, icons}){ 5 | const buttons = [ ButtonTitle, ButtonTrash]; 6 | if(viewSize==='pageview'||viewSize==='fullscreen'){ 7 | buttons.splice(1, 0, ButtonMenu ) 8 | } 9 | return h('div',{ 10 | className:`${prefix}--navbar`, 11 | style: getElementStyle('navbar') 12 | },[ 13 | h( 'div',{ 14 | key: `${prefix}--navbar__nav`, 15 | className:`${prefix}--navbar__nav`, 16 | style: getElementStyle('navbar-nav', null, { flexGrow: 1 } ) 17 | }, 18 | items?items.map((data)=> 19 | h(NoteHeader,{ 20 | key: `navbar-item__${data.id}`, 21 | data, 22 | prefix: `${prefix}--navbar__item`, 23 | icons, 24 | callbacks, 25 | buttons: buttons 26 | }) 27 | ):null 28 | ), 29 | h('div',{ 30 | key: `navbar-item__options`, 31 | className:`${prefix}--navbar__nav` 32 | }, 33 | h( NoteHeader, { 34 | prefix: `${prefix}--navbar__item`, 35 | viewSize: viewSize, 36 | icons, 37 | callbacks, 38 | buttons: [ButtonAdd, ButtonPageView, ButtonUpload, ButtonTrash] 39 | }) 40 | ) 41 | ]); 42 | } 43 | export default NavBar; 44 | -------------------------------------------------------------------------------- /src/components/react-sticky-notes/navbar/index.scss: -------------------------------------------------------------------------------- 1 | @mixin navbar() { 2 | &--navbar{ 3 | white-space: nowrap; 4 | display: flex; 5 | justify-content: space-between; 6 | background-color: #999999; 7 | @media screen and (max-width: "800px") { 8 | flex-direction: column-reverse; 9 | } 10 | &__nav{ 11 | display: flex; 12 | flex-wrap: wrap; 13 | } 14 | &__options{ 15 | display: flex; 16 | } 17 | &__item{ 18 | display: flex; 19 | flex-grow: 1; 20 | position: relative; 21 | vertical-align: middle; 22 | overflow: hidden; 23 | &:before{ 24 | content: ''; 25 | width: 100%; 26 | height: 100%; 27 | position: absolute; 28 | left: 0; 29 | top: 0; 30 | pointer-events: none; 31 | } 32 | &--button{ 33 | cursor: pointer; 34 | opacity: .8; 35 | background: none; 36 | border: none; 37 | color:#192227; 38 | padding: 3px 5px; 39 | &__title{ 40 | text-align: left; 41 | flex-grow: 1; 42 | min-width: 80px; 43 | } 44 | &:hover,&:focus{ 45 | opacity: 1; 46 | background-color: rgba(0,0,0,.15); 47 | } 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/components/react-sticky-notes/partials/note-body.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { h, getElementStyle } from '../utils'; 3 | import NoteText from './note-text'; 4 | import NoteMenu from './note-menu'; 5 | export default class NoteBody extends React.Component{ 6 | constructor(props){ 7 | super(props); 8 | this.noteRef = React.createRef(); 9 | this.resizeObserver = null; 10 | } 11 | componentDidMount(){ 12 | this.resizeObserver = new ResizeObserver((entries) => { 13 | this.props.callbacks.updateItem(entries, {...this.props.data, width: entries[0].contentRect.width, height: entries[0].contentRect.height}) 14 | }); 15 | this.resizeObserver.observe(this.noteRef.current); 16 | } 17 | componentWillUnmount(){ 18 | if(this.resizeObserver){ 19 | this.resizeObserver.disconnect(); 20 | } 21 | } 22 | render(){ 23 | const { data, prefix } = this.props; 24 | return h('div',{ 25 | className:`${prefix}--note__body`, 26 | style: getElementStyle('note-body', this.props), 27 | ref: this.noteRef, 28 | id:data.id 29 | }, 30 | data.menu? 31 | h(NoteMenu, { 32 | key: 'note-menu', 33 | ...this.props 34 | }): 35 | h(NoteText, { 36 | key: 'note-text', 37 | ...this.props 38 | }) 39 | ) 40 | } 41 | 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/components/react-sticky-notes/partials/note-body.scss: -------------------------------------------------------------------------------- 1 | @mixin noteBody() { 2 | &--note__body{ 3 | &::-webkit-scrollbar { 4 | width: 6px; 5 | height: 6px; 6 | background-color: #F5F5F5; 7 | border-radius: 0.5em; 8 | } 9 | &::-webkit-scrollbar-thumb { 10 | background-color: rgb(132, 147, 165); 11 | border-radius: 0.5em; 12 | } 13 | &::-webkit-scrollbar-track { 14 | box-shadow: inset 0 0 6px rgba(0,0,0,0.3); 15 | background-color: #F5F5F5; 16 | border-radius:.5em; 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /src/components/react-sticky-notes/partials/note-bubble.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { h, getElementStyle, getNoteTitle } from '../utils'; 3 | import NoteDraggable from './note-draggable'; 4 | export default class NoteBubble extends React.Component{ 5 | constructor(props){ 6 | super(props); 7 | this.targetRef = React.createRef(); 8 | } 9 | render(){ 10 | const props = this.props; 11 | return h(NoteDraggable, { 12 | className:`${props.prefix}--note ${props.data.selected?props.prefix+'--note__selected':''}`, 13 | position: props.data.position, 14 | selected: props.data.selected, 15 | target: this.targetRef, 16 | onDragComplete:(pos)=> props.callbacks.updateItem(null, {id: props.data.id, position:pos }), 17 | style: getElementStyle('note', props ) 18 | }, 19 | h('button',{ 20 | className: `${props.prefix}--note__bubble`, 21 | ref: this.targetRef, 22 | title: props.data.title?props.data.title: getNoteTitle(props.data), 23 | onClick: ()=> props.callbacks.updateItem(null, {id: props.data.id, hidden: false }), 24 | style: { 25 | "--background-color": props.data.color, 26 | width: '15px', 27 | height: '15px', 28 | borderRadius: '50%', 29 | backgroundColor: props.data.color, 30 | boxShadow: '1px 1px 2px rgba(0,0,0,.15)' 31 | } 32 | }) 33 | ) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/components/react-sticky-notes/partials/note-bubble.scss: -------------------------------------------------------------------------------- 1 | @mixin noteBubble() { 2 | &--note{ 3 | &__bubble{ 4 | cursor: move; 5 | border: none; 6 | outline: none; 7 | position: relative; 8 | &::before{ 9 | display: block; 10 | opacity: 0; 11 | content: attr(title); 12 | transform: translate(-50%, -50%); 13 | transform-origin: 0 0; 14 | transition: all linear .4s; 15 | overflow: hidden; 16 | background-color: var(--background-color); 17 | white-space: nowrap; 18 | position: absolute; 19 | left: 50%; 20 | top: 50%; 21 | padding: 5px 8px; 22 | border-radius: 5px; 23 | color: rgba(255,255,255,.75); 24 | font-size: .9em; 25 | } 26 | &:hover{ 27 | &::before{ 28 | opacity: 1; 29 | } 30 | } 31 | &:focus, &:active{ 32 | opacity: .5; 33 | z-index: 9999; 34 | } 35 | } 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/components/react-sticky-notes/partials/note-dot.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { h, getElementStyle, getNoteTitle } from '../utils'; 3 | import NoteDraggable from './note-draggable'; 4 | export default class NoteDot extends React.Component{ 5 | constructor(props){ 6 | super(props); 7 | this.targetRef = React.createRef(); 8 | } 9 | render(){ 10 | const props = this.props; 11 | return h(NoteDraggable, { 12 | unit:"%", 13 | useBoundaries: true, 14 | disabledAxisX: true, 15 | className:`${props.prefix}--note ${props.data.selected?props.prefix+'--note__selected':''}`, 16 | position: props.data.position, 17 | selected: props.data.selected, 18 | target: this.targetRef, 19 | onDragComplete:(pos)=> { 20 | const index = Math.floor(pos.py*props.colorCodes.length/100); 21 | const color = props.colorCodes[index]; 22 | props.callbacks.updateItem(null, {id: props.data.id, color }) 23 | }, 24 | style: { 25 | position: 'absolute', 26 | left: props.data.position.x, 27 | top: props.data.position.y 28 | } 29 | }, 30 | h('button',{ 31 | className: `${props.prefix}--note__bubble`, 32 | ref: this.targetRef, 33 | title: props.data.title?props.data.title: getNoteTitle(props.data), 34 | onClick: ()=> props.callbacks.updateItem(null, {id: props.data.id, hidden: false }), 35 | style: { 36 | "--background-color": props.data.color, 37 | width: '15px', 38 | height: '15px', 39 | borderRadius: '50%', 40 | backgroundColor: props.data.color, 41 | boxShadow: '1px 1px 2px rgba(0,0,0,.15)' 42 | } 43 | }) 44 | ) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/components/react-sticky-notes/partials/note-draggable.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Draggable from '../utils/draggable'; 3 | import { h } from '../utils'; 4 | class NoteDraggable extends React.Component { 5 | draggable = null 6 | constructor(props) { 7 | super(props); 8 | this.state= { 9 | options: {} 10 | } 11 | this.element = React.createRef(); 12 | this.draggable = new Draggable(); 13 | } 14 | componentDidMount() { 15 | const el = this.element?this.element.current:null; 16 | const options = { 17 | element: el, 18 | unit: this.props.unit, 19 | useBoundaries: this.props.useBoundaries, 20 | disabledAxisX: this.props.disabledAxisX, 21 | position: this.props.position, 22 | onDragComplete:this.props.onDragComplete, 23 | onInit:this.props.onInit 24 | }; 25 | this.setState({options}, ()=> { 26 | this.draggable.init(options); 27 | }) 28 | } 29 | onMouseDown = (e) => { 30 | if(this.props.target&&e.target===this.props.target.current){ 31 | this.draggable.onMouseDown(e); 32 | } 33 | } 34 | render() { 35 | return ( 36 | h('div', { 37 | className: this.props.className, 38 | style: this.props.style, 39 | ref: this.element, 40 | onMouseDown:this.onMouseDown, 41 | onTouchStart:this.onMouseDown 42 | }, 43 | this.props.children 44 | ) 45 | ); 46 | } 47 | } 48 | export default NoteDraggable; 49 | -------------------------------------------------------------------------------- /src/components/react-sticky-notes/partials/note-header.js: -------------------------------------------------------------------------------- 1 | import { h, getElementStyle } from '../utils'; 2 | function NoteHeader(props) { 3 | return h('div',{ 4 | className: props.prefix, 5 | style: getElementStyle('note-header',{data: props.data}) 6 | }, 7 | props.buttons?props.buttons.map((Button,i)=> 8 | h(Button, { 9 | key: `${props.prefix}${props.data?props.data.id:'all'}__note-button__${i}`, 10 | ...props 11 | }) 12 | ):null 13 | ); 14 | } 15 | export default NoteHeader; 16 | -------------------------------------------------------------------------------- /src/components/react-sticky-notes/partials/note-header.scss: -------------------------------------------------------------------------------- 1 | @mixin noteHeader() { 2 | &--header{ 3 | position: relative; 4 | display: flex; 5 | transition: all .3s linear; 6 | border-bottom: 1px solid rgba(0, 0, 0, 0.25); 7 | svg{ 8 | line-height: 1; 9 | vertical-align: middle; 10 | } 11 | &:before{ 12 | content: ''; 13 | width: 100%; 14 | height: 100%; 15 | position: absolute; 16 | left: 0; 17 | top: 0; 18 | pointer-events: none; 19 | background-color: rgba(0,0,0,.1); 20 | } 21 | &--button{ 22 | cursor:pointer; 23 | line-height: 30px; 24 | background: none; 25 | border:none; 26 | transition: all .2s linear; 27 | padding: 5px; 28 | color: #192227; 29 | width: 32px; 30 | &__title{ 31 | flex-grow: 1; 32 | line-height: 30px; 33 | text-align: left; 34 | user-select: none; 35 | cursor: move; 36 | } 37 | &:hover,&:focus{ 38 | background-color: rgba(0,0,0,.25); 39 | outline: none; 40 | } 41 | &:disabled{ 42 | cursor: not-allowed; 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/components/react-sticky-notes/partials/note-maximized.js: -------------------------------------------------------------------------------- 1 | import { h, getElementStyle } from './../utils'; 2 | export default function NoteMaximized({ data, index, prefix, callbacks }) { 3 | return h('div', { 4 | className:`${prefix}--minimized`, 5 | onClick: () => callbacks.updateItem(index,{ id:data.id, viewSize: null }), 6 | style: getElementStyle('note-minimized', {data}) 7 | }) 8 | } 9 | -------------------------------------------------------------------------------- /src/components/react-sticky-notes/partials/note-maximized.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashishBharadwaj/flawesome/731d4caf9a00754d39c150e1af457c787f8dee4b/src/components/react-sticky-notes/partials/note-maximized.scss -------------------------------------------------------------------------------- /src/components/react-sticky-notes/partials/note-menu.js: -------------------------------------------------------------------------------- 1 | import { h, getElementStyle } from '../utils'; 2 | function NoteMenu(props) { 3 | const { data, index, prefix, colorCodes, callbacks } = props; 4 | return h('div', { 5 | className: `${prefix}--colors`, 6 | style: getElementStyle('note-menu', props) 7 | }, 8 | colorCodes.map((colorCode) => h('button', { 9 | key: colorCode, 10 | onClick: (e) => callbacks.updateItem(e, {id:data.id, color: colorCode, menu: false}), 11 | className: `${prefix}--colors__color ${data.color === colorCode ? prefix + '--colors__color--selected' : ''}`, 12 | style: getElementStyle('note-color-selector', {colorCode}) 13 | }, colorCode ) 14 | )) 15 | } 16 | export default NoteMenu; 17 | -------------------------------------------------------------------------------- /src/components/react-sticky-notes/partials/note-menu.scss: -------------------------------------------------------------------------------- 1 | @keyframes shadowanim { 2 | 0% {box-shadow:0px 0px 0px 25px inset rgba(0,0,0,0.15),0px 0px 0px 15px inset rgba(0,0,0,0.15),0px 0px 0px 5px inset rgba(0,0,0,0.15);opacity: 0.2;} 3 | 20% {opacity: 0.9; } 4 | 50% {opacity: 1; } 5 | 70% {opacity: 0.9; } 6 | 100% {box-shadow:0px 0px 0px 0px inset rgba(0,0,0,0.15); opacity: 0.2; } 7 | } 8 | @mixin noteMenu() { 9 | &--colors{ 10 | flex-grow: 1; 11 | &__color{ 12 | text-indent: -99999px; 13 | cursor: pointer; 14 | margin: 1px; 15 | width: 3.6em; 16 | height: 3.6em; 17 | border-radius: 50%; 18 | border: none; 19 | outline: none; 20 | transition: all 0.4s linear; 21 | box-shadow: 0px 0px 0px 5px inset rgba(0,0,0,.15); 22 | &:hover,&:focus{ 23 | box-shadow: 0px 0px 0px 10px inset rgba(0,0,0,.15); 24 | } 25 | &--selected{ 26 | animation-name: shadowanim; 27 | animation-duration: 2s; 28 | animation-iteration-count: 100; 29 | opacity: .75; 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/components/react-sticky-notes/partials/note-minimized.js: -------------------------------------------------------------------------------- 1 | import { h, getElementStyle } from './../utils'; 2 | export default function NoteMinimized({ data, index, prefix, callbacks }) { 3 | return h('div', { 4 | className:`${prefix}--minimized`, 5 | onClick: (e) => callbacks.updateItem(e,{ id:data.id, viewSize: null, selected: true }), 6 | style: getElementStyle('note-minimized', {data}) 7 | }) 8 | } -------------------------------------------------------------------------------- /src/components/react-sticky-notes/partials/note-minimized.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashishBharadwaj/flawesome/731d4caf9a00754d39c150e1af457c787f8dee4b/src/components/react-sticky-notes/partials/note-minimized.scss -------------------------------------------------------------------------------- /src/components/react-sticky-notes/partials/note-text.js: -------------------------------------------------------------------------------- 1 | import { h, nlToBr, getElementStyle, getCurrentDateTime } from '../utils'; 2 | function NoteText({ data, index, prefix, callbacks }) { 3 | return h('div',{ 4 | className:`${prefix}--text`, 5 | placeholder:"react-hooks", 6 | contentEditable:"true", 7 | onBlur:(e)=>callbacks.updateItem(index, { 8 | id:data.id, 9 | text: e.target.innerText 10 | }), 11 | onFocus:(e)=>callbacks.updateItem(e, {id:data.id, selected:true, datetime: getCurrentDateTime() }), 12 | dangerouslySetInnerHTML:{__html:nlToBr(data.text)}, 13 | style: getElementStyle('note-input') 14 | }) 15 | } 16 | export default NoteText; 17 | -------------------------------------------------------------------------------- /src/components/react-sticky-notes/partials/note-text.scss: -------------------------------------------------------------------------------- 1 | @mixin noteText() { 2 | &--text{ 3 | padding: 10px; 4 | font-size: 14px; 5 | width: 100%; 6 | outline: none!important; 7 | &:empty{ 8 | &::before{ 9 | color: #192227; 10 | content: 'Add your notes...'; 11 | } 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /src/components/react-sticky-notes/partials/note.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { h, getElementStyle } from '../utils'; 3 | import NoteDraggable from './note-draggable'; 4 | import NoteHeader from './note-header'; 5 | import NoteBody from './note-body'; 6 | import { ButtonAdd, ButtonTitle, ButtonMenu, ButtonHideShow, ButtonTrash } from '../buttons' ; 7 | class Note extends React.Component{ 8 | constructor(props){ 9 | super(props); 10 | this.targetRef = React.createRef(); 11 | } 12 | render(){ 13 | const props = this.props; 14 | return h(NoteDraggable, { 15 | className:`${props.prefix}--note ${props.data.selected?props.prefix+'--note__selected':''}`, 16 | position: props.data.position, 17 | selected: props.data.selected, 18 | target: this.targetRef, 19 | onDragComplete:(pos)=> props.callbacks.updateItem(null, {id: props.data.id, position:pos}), 20 | style: getElementStyle('note', props, { boxShadow: '1px 1px 2px rgba(0,0,0,.15)' } ) 21 | }, [ 22 | h(NoteHeader, { 23 | ...props, 24 | key:'note-header', 25 | targetRef: this.targetRef, 26 | prefix: `${props.prefix}--header`, 27 | buttons: [ButtonAdd, ButtonTitle, ButtonMenu, ButtonHideShow, ButtonTrash] 28 | }), 29 | h(NoteBody,{ 30 | key:'note-body', 31 | ...props 32 | }) 33 | ] 34 | ) 35 | } 36 | } 37 | export default Note; 38 | -------------------------------------------------------------------------------- /src/components/react-sticky-notes/partials/note.scss: -------------------------------------------------------------------------------- 1 | @mixin note() { 2 | &--note{ 3 | &.draggable{ 4 | z-index: 9; 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/components/react-sticky-notes/partials/notes.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Note from './note'; 3 | import { h, getElementStyle } from './../utils'; 4 | class Notes extends React.Component{ 5 | constructor(props){ 6 | super(props); 7 | this.state = { 8 | toggle:false 9 | } 10 | } 11 | setToggle = (toggle) => { 12 | this.setState({ 13 | toggle: this.state.toggle===toggle?false:toggle 14 | }); 15 | } 16 | render(){ 17 | const {prefix, items} = this.props; 18 | return h('div', { 19 | key: prefix, 20 | className: prefix, 21 | style: getElementStyle('container', this.props) 22 | }, 23 | items?items.map((data, index)=> h(Note, { 24 | key: `note-${data.id}`, 25 | data, 26 | ...this.props, 27 | toggle:this.state.toggle, 28 | setToggle:this.setToggle 29 | }) 30 | ):null 31 | ) 32 | } 33 | } 34 | export default Notes; 35 | -------------------------------------------------------------------------------- /src/components/react-sticky-notes/partials/notes.scss: -------------------------------------------------------------------------------- 1 | @mixin notes() { 2 | 3 | color: #192227!important; 4 | text-align: left; 5 | 6 | } 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/components/react-sticky-notes/reducers/reducer.js: -------------------------------------------------------------------------------- 1 | const reducer = (state, action) => { 2 | const viewSizes = ['bubbleview', 'normalview', 'pageview']; 3 | const params = action.payload&&action.payload.data?Object.keys(action.payload.data):[]; 4 | let { items, viewSize, modal } = state; 5 | switch (action.type) { 6 | case 'changemodal': 7 | modal = action.payload.modal; 8 | break; 9 | case 'import': 10 | modal = null; 11 | items = action.payload.items; 12 | break; 13 | case 'changeview': 14 | modal = null; 15 | const currentViewSize = viewSizes.indexOf(viewSize); 16 | viewSize = currentViewSize>-1&¤tViewSize{ 20 | item.selected = false; 21 | return item; 22 | }); 23 | items.splice(action.payload.index, 0, action.payload.data); 24 | break; 25 | case 'update': 26 | items = items.map((item)=>{ 27 | if(item.id===action.payload.data.id){ 28 | item = {...item, ...action.payload.data }; 29 | } 30 | if(params.indexOf('selected')!==-1){ 31 | item.selected = item.id===action.payload.data.id?action.payload.data.selected:false; 32 | } 33 | if(params.indexOf('menu')!==-1){ 34 | item.menu = item.id===action.payload.data.id?action.payload.data.menu:false; 35 | } 36 | return item; 37 | }); 38 | break; 39 | case 'delete': 40 | const index = items.findIndex(item=>action.payload.data.id===item.id); 41 | if(index!==-1){ 42 | items.splice(index,1); 43 | }else{ 44 | items.splice(0,items.length); 45 | } 46 | break; 47 | default: 48 | items = state.items; 49 | break; 50 | } 51 | return { items, viewSize, modal }; 52 | } 53 | export default reducer; 54 | -------------------------------------------------------------------------------- /src/components/react-sticky-notes/utils/color-codes.js: -------------------------------------------------------------------------------- 1 | export function getColorCodes(){ 2 | return [ 3 | '#ffeb99', 4 | '#50c2ff', 5 | '#df99ff', 6 | '#facd60', 7 | '#b48bff', 8 | '#ffcce5', 9 | '#cbf1c4', 10 | '#fdc4b7', 11 | '#a4fccd', 12 | '#a0a9f8', 13 | '#66f7f7' 14 | ]; 15 | }; -------------------------------------------------------------------------------- /src/components/react-sticky-notes/utils/draggable.js: -------------------------------------------------------------------------------- 1 | export default class Draggable { 2 | dx=0; 3 | dy=0; 4 | percentX = 0; 5 | percentY = 0; 6 | currentX = 0; 7 | currentY = 0; 8 | init(options) { 9 | this.options = options; 10 | } 11 | onMouseMove = (e) => { 12 | if (e.cancelable) { 13 | e.preventDefault(); 14 | } 15 | const el = this.options.element; 16 | const parentElement = el.parentElement; 17 | const pRect = parentElement?parentElement.getBoundingClientRect():{left:0,top:0}; 18 | const position = this.getPosition(e, this.dx, this.dy); 19 | let x = position.left - pRect.left; 20 | let y = position.top - pRect.top; 21 | this.currentX = x>0?x:0; 22 | this.currentY = y>0?y:0; 23 | 24 | if(this.options.useBoundaries){ 25 | const maxX = pRect.width-el.offsetWidth; 26 | const maxY = pRect.height-el.offsetHeight; 27 | 28 | if(this.currentX>=maxX){ 29 | this.currentX = maxX; 30 | } 31 | 32 | if(this.currentY>=maxY){ 33 | this.currentY = maxY; 34 | } 35 | } 36 | if(this.options.unit==="%"){ 37 | this.percentX = this.currentX*100/pRect.width; 38 | this.percentY = this.currentY*100/pRect.height; 39 | this.setTranslate(`${this.percentX}%`, `${this.percentY}%`); 40 | }else{ 41 | this.setTranslate(`${this.currentX}px`, `${this.currentY}px`); 42 | } 43 | 44 | 45 | } 46 | onMouseDown = (e) => { 47 | const el = this.options.element; 48 | const parentElement = el.parentElement; 49 | const rect = el.getBoundingClientRect(); 50 | const pRect = parentElement?parentElement.getBoundingClientRect():{left:0,top:0}; 51 | this.currentX = - pRect.left + rect.left; 52 | this.currentY = - pRect.top + rect.top; 53 | 54 | const position = this.getPosition(e); 55 | this.dx = position.left - rect.left; 56 | this.dy = position.top - rect.top; 57 | 58 | el.classList.add('draggable'); 59 | 60 | document.addEventListener('mousemove', this.onMouseMove, null); 61 | document.addEventListener('mouseup', this.onMouseUp, null); 62 | document.addEventListener('touchmove', this.onMouseMove, {passive: false}); 63 | document.addEventListener('touchend', this.onMouseUp, {passive: false}); 64 | 65 | } 66 | onMouseUp = (e) => { 67 | if(this.options.onDragComplete){ 68 | this.options.onDragComplete.call(this, { 69 | x: this.currentX, 70 | y: this.currentY, 71 | px: this.percentX, 72 | py: this.percentY 73 | }) 74 | } 75 | 76 | this.options.element.classList.remove('draggable'); 77 | 78 | document.removeEventListener('mousemove', this.onMouseMove); 79 | document.removeEventListener('mouseup', this.onMouseUp); 80 | document.removeEventListener('touchmove', this.onMouseMove); 81 | document.removeEventListener('touchend', this.onMouseUp); 82 | } 83 | setTranslate(x, y) { 84 | if(this.options.element){ 85 | if(!this.options.disabledAxisX){ 86 | this.options.element.style.left = x; 87 | } 88 | if(!this.options.disabledAxisY){ 89 | this.options.element.style.top = y; 90 | } 91 | } 92 | } 93 | getPosition(e, dx=0, dy=0){ 94 | if ((/touch/).test(e.type)) { 95 | return { 96 | left: e.touches[0].clientX - dx, 97 | top: e.touches[0].clientY - dy, 98 | x: e.touches[0].clientX - dx, 99 | y: e.touches[0].clientY - dy 100 | }; 101 | } else { 102 | return { 103 | left: e.clientX - dx, 104 | top: e.clientY - dy, 105 | x: e.clientX - dx, 106 | y: e.clientY - dy 107 | }; 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/components/react-sticky-notes/utils/get-current-datetime.js: -------------------------------------------------------------------------------- 1 | export function getCurrentDateTime(){ 2 | return new Date().toISOString().replace('T',' ').substring(0, 19); 3 | } 4 | -------------------------------------------------------------------------------- /src/components/react-sticky-notes/utils/get-element-style.js: -------------------------------------------------------------------------------- 1 | export function getElementStyle(nodeName, props, defaultStyle={}) { 2 | let style = defaultStyle; 3 | // eslint-disable-next-line default-case 4 | switch(nodeName){ 5 | case "container": 6 | style = { 7 | ...defaultStyle, 8 | position: 'relative', 9 | width: props.containerWidth, 10 | height: props.containerHeight, 11 | backgroundColor: props.backgroundColor, 12 | marginTop: '10px' 13 | } 14 | break; 15 | case "note": 16 | style = { 17 | ...defaultStyle, 18 | position: 'absolute', 19 | left: props.viewSize==="pageview"||props.viewSize==="fullscreen"?0:props.data.position?`${props.data.position.x}px`:0, 20 | top: props.viewSize==="pageview"||props.viewSize==="fullscreen"?0:props.data.position?`${props.data.position.y}px`:0, 21 | width: props.viewSize==="pageview"||props.viewSize==="fullscreen"?"100%":null, 22 | height: props.viewSize==="pageview"||props.viewSize==="fullscreen"?"100%":null 23 | } 24 | if(props.data.selected){ 25 | style.zIndex = 1; 26 | } 27 | break; 28 | case "note-body": 29 | style.width = props.viewSize==="pageview"||props.viewSize==="fullscreen"?"100%": (props.data.width ? props.data.width : props.noteWidth); 30 | style.height = props.viewSize==="pageview"||props.viewSize==="fullscreen"?"100%": (props.data.height ? props.data.height : props.noteHeight); 31 | style.backgroundColor= props.data.color; 32 | style.overflow = "auto"; 33 | if(props.data.selected){ 34 | // eslint-disable-next-line no-unused-expressions 35 | style.minWidth = props.noteWidth; 36 | style.resize = "both"; 37 | } 38 | break; 39 | case "note-input": 40 | style.height = "100%"; 41 | break; 42 | case "note-header": 43 | style.backgroundColor=props.data?props.data.color:''; 44 | break; 45 | case "note-minimized": 46 | style = { 47 | ...defaultStyle, 48 | backgroundColor: props.data.color, 49 | position: 'absolute', 50 | left: props.data.position?`${props.data.position.x}px`:0, 51 | top: props.data.position?`${props.data.position.y}px`:0, 52 | width: '10px', 53 | height: '10px' 54 | } 55 | break; 56 | case "note-maximized": 57 | style = { 58 | ...defaultStyle, 59 | backgroundColor: props.data.color, 60 | position: 'absolute', 61 | left: props.data.position?`${props.data.position.x}px`:0, 62 | top: props.data.position?`${props.data.position.y}px`:0, 63 | width: '10px', 64 | height: '10px' 65 | } 66 | break; 67 | case "note-menu": 68 | style.backgroundColor = "#ffffff"; 69 | style.minHeight = '100%'; 70 | break; 71 | case "note-color-selector": 72 | style = { 73 | ...defaultStyle, 74 | backgroundColor: props.colorCode 75 | } 76 | break; 77 | case "icon": 78 | style = { 79 | ...defaultStyle, 80 | verticalAlign: 'middle', 81 | width: '1em' 82 | } 83 | break; 84 | } 85 | return style; 86 | } 87 | -------------------------------------------------------------------------------- /src/components/react-sticky-notes/utils/get-note-title.js: -------------------------------------------------------------------------------- 1 | export function getNoteTitle({ title, text, limit = 10, delimiter = null } ) { 2 | let _title; 3 | if(title){ 4 | _title = String( title ); 5 | }else if(delimiter){ 6 | _title = String( text ).split(delimiter)[ 0 ]; 7 | }else{ 8 | _title = String(text).substr(0, limit); 9 | } 10 | return _title.substr(0, 1).toUpperCase() + _title.substr(1, _title.length); 11 | } 12 | -------------------------------------------------------------------------------- /src/components/react-sticky-notes/utils/get-notes.js: -------------------------------------------------------------------------------- 1 | import { getUUID } from './get-uuid'; 2 | export function getNotes(colorCodes, notes ) { 3 | let _notes = []; 4 | if(notes && notes.length > 0){ 5 | _notes = notes.map((note)=>{ 6 | note.id = note.id?note.id:getUUID(); 7 | note.position = note.position?note.position:{ x: 0, y: 0 }; 8 | note.color= note.color?note.color:colorCodes[Math.floor(Math.random() * colorCodes.length)]; 9 | note.width = note.width ? note.width : 220; 10 | note.height = note.height ? note.height : 220 ; 11 | return note; 12 | }); 13 | }else{ 14 | _notes = [ 15 | { 16 | id: getUUID(), 17 | text: '', 18 | position: { x: 0, y: 0 }, 19 | color: colorCodes[Math.floor(Math.random() * colorCodes.length)], 20 | selected:true, 21 | width: 220, 22 | height: 220 23 | } 24 | ] 25 | } 26 | return _notes; 27 | } 28 | -------------------------------------------------------------------------------- /src/components/react-sticky-notes/utils/get-uuid.js: -------------------------------------------------------------------------------- 1 | export function getUUID(){ 2 | var dt = new Date().getTime(); 3 | var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { 4 | var r = (dt + Math.random()*16)%16 | 0; 5 | dt = Math.floor(dt/16); 6 | return (c=='x' ? r :(r&0x3|0x8)).toString(16); 7 | }); 8 | return uuid; 9 | } -------------------------------------------------------------------------------- /src/components/react-sticky-notes/utils/h.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | export const h = React.createElement; -------------------------------------------------------------------------------- /src/components/react-sticky-notes/utils/index.js: -------------------------------------------------------------------------------- 1 | export * from './h'; 2 | export * from './color-codes'; 3 | export * from './get-uuid'; 4 | export * from './nl-to-br'; 5 | export * from './get-notes'; 6 | export * from './get-element-style'; 7 | export * from './get-current-datetime'; 8 | export * from './get-note-title'; 9 | export * from './parse-csv'; -------------------------------------------------------------------------------- /src/components/react-sticky-notes/utils/nl-to-br.js: -------------------------------------------------------------------------------- 1 | export function nlToBr(str) { 2 | return str?str.replace(/(?:\r\n|\r|\n)/g, '
'):''; 3 | } -------------------------------------------------------------------------------- /src/components/react-sticky-notes/utils/parse-csv.js: -------------------------------------------------------------------------------- 1 | export function parseCSV(str) { 2 | var arr = []; 3 | var quote = false; 4 | 5 | for (var row = 0, col = 0, c = 0; c < str.length; c++) { 6 | var currentCharacter = str[c], nextCharacter = str[c+1]; 7 | arr[row] = arr[row] || []; 8 | arr[row][col] = arr[row][col] || ''; 9 | 10 | if (currentCharacter == '"' && quote && nextCharacter == '"') { 11 | arr[row][col] += currentCharacter; 12 | ++c; 13 | continue; 14 | } 15 | 16 | if (currentCharacter == '"') { 17 | quote = !quote; 18 | continue; 19 | } 20 | 21 | if (currentCharacter == ',' && !quote) { 22 | ++col; 23 | continue; 24 | } 25 | 26 | if (currentCharacter == '\r' && nextCharacter == '\n' && !quote) { 27 | col = 0; 28 | ++row; 29 | ++c; 30 | continue; 31 | } 32 | 33 | if ( ( currentCharacter == '\r' || currentCharacter == '\n' )&& !quote) { 34 | ++row; 35 | col = 0; 36 | continue; 37 | } 38 | 39 | arr[row][col] += currentCharacter; 40 | } 41 | const results = []; 42 | const headers = arr[0]; 43 | for(let i = 1;i h( 'div', { 17 | key: `note--color-${i}`, 18 | className: `${props.prefix}--notes-colors__color`, 19 | style: { 20 | "--background-color": color, 21 | "--height": `${100/props.colorCodes.length}%` 22 | } 23 | } ) ) 24 | ), 25 | h('div',{ 26 | key: `${props.prefix}--notes-area`, 27 | className: `${props.prefix}--notes-area` 28 | }, 29 | props.items.map( (data, index) => { 30 | data.position = { 31 | x: `${(index*100/props.items.length)}%`, 32 | y: `${(props.colorCodes.indexOf(data.color)*100/props.colorCodes.length)}%`, 33 | } 34 | return h( NoteDot, { key: `note-${data.id}`,...props, data } ) 35 | } ) 36 | ), 37 | ]) 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /src/components/react-sticky-notes/views/bubble-view.scss: -------------------------------------------------------------------------------- 1 | @mixin bubbleView() { 2 | &--notes-area{ 3 | position: relative; 4 | width: calc(100% - 15px); 5 | height: 100%; 6 | margin-left: 15px; 7 | z-index: 1; 8 | } 9 | &--notes-colors{ 10 | position: absolute; 11 | width: 100%; 12 | height: 100%; 13 | z-index: 0; 14 | &__color{ 15 | position: relative; 16 | opacity: 0.8; 17 | width: 100%; 18 | height: var(--height); 19 | border-bottom: 1px solid var(--background-color); 20 | &::before{ 21 | background-color: var(--background-color); 22 | width: 15px; 23 | height: 100%; 24 | display: block; 25 | position: absolute; 26 | top: 1px; 27 | left: 0; 28 | content: ''; 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/components/react-sticky-notes/views/fullscreen-view.js: -------------------------------------------------------------------------------- 1 | import { h, getElementStyle } from '../utils'; 2 | import NavBar from '../navbar'; 3 | import NoteBody from '../partials/note-body'; 4 | export function FullscreenView(props){ 5 | return h('div', { 6 | style: { 7 | position: 'fixed', 8 | left: 0, 9 | top:0, 10 | width: '100vw', 11 | height: '100vh' 12 | } 13 | },[ 14 | h(NavBar, { ...props, key: 'navbar' }), 15 | h('div', { 16 | key: props.prefix, 17 | className: props.prefix, 18 | style: getElementStyle('container', props) 19 | }, 20 | props.items.map( data => h('div', { 21 | key: `note-${data.id}`, 22 | className:`${props.prefix}--note`, 23 | style: getElementStyle('note', {...props, data} ) 24 | }, 25 | h(NoteBody,{ 26 | data, 27 | ...props 28 | }) 29 | ) 30 | ) 31 | ) 32 | ]) 33 | } -------------------------------------------------------------------------------- /src/components/react-sticky-notes/views/index.js: -------------------------------------------------------------------------------- 1 | export * from './normal-view'; 2 | export * from './bubble-view'; 3 | export * from './page-view'; 4 | export * from './fullscreen-view'; -------------------------------------------------------------------------------- /src/components/react-sticky-notes/views/normal-view.js: -------------------------------------------------------------------------------- 1 | import { h, getElementStyle } from '../utils'; 2 | import NavBar from '../navbar'; 3 | import Note from '../partials/note'; 4 | import NoteBubble from '../partials/note-bubble'; 5 | export function NormalView(props){ 6 | return [ 7 | h(NavBar, { ...props, key: 'navbar' }), 8 | h('div', { 9 | key: props.prefix, 10 | className: props.prefix, 11 | style: getElementStyle('container', props) 12 | }, 13 | props.items.map( data => !data.hidden?h( Note, { key: `note-${data.id}`,...props, data } ):h( NoteBubble, { key: `note-${data.id}`,...props, data } ) ) 14 | ) 15 | ] 16 | } -------------------------------------------------------------------------------- /src/components/react-sticky-notes/views/page-view.js: -------------------------------------------------------------------------------- 1 | import { h, getElementStyle } from '../utils'; 2 | import NavBar from '../navbar'; 3 | import NoteBody from '../partials/note-body'; 4 | export function PageView(props){ 5 | return [ 6 | h(NavBar, { ...props, key: 'navbar' }), 7 | h('div', { 8 | key: props.prefix, 9 | className: props.prefix, 10 | style: getElementStyle('container', props) 11 | }, 12 | props.items.map( data => h('div', { 13 | key: `note-${data.id}`, 14 | className:`${props.prefix}--note`, 15 | style: getElementStyle('note', {...props, data} ) 16 | }, 17 | h(NoteBody,{ 18 | data, 19 | ...props 20 | }) 21 | ) 22 | ) 23 | ) 24 | ] 25 | } -------------------------------------------------------------------------------- /src/components/spotlight-search/Spotlight.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { groupBy } from "lodash-es"; 3 | 4 | import Hits from "./modules/Hits"; 5 | import Overlay from "./modules/Overlay"; 6 | import SearchBar from "./modules/SearchBar"; 7 | import SpotlightContext from "./modules/SpotlightContext"; 8 | const ipcRenderer = window.electron.ipcRenderer; 9 | 10 | 11 | const DEFAULT_STATE = { 12 | hits: {}, 13 | flatHits: [], 14 | isOpen: false, 15 | selectedResultIndex: 0 16 | }; 17 | 18 | class Spotlight extends Component { 19 | constructor(props){ 20 | super(props); 21 | ipcRenderer.on('open-search', () => { 22 | this.state.toggle(); 23 | }) 24 | } 25 | state = { 26 | ...DEFAULT_STATE, 27 | toggle: () => { 28 | this.setState({ ...DEFAULT_STATE, isOpen: !this.state.isOpen },()=>{ this.props.toggleDoOpen(this.state.isOpen) }); 29 | }, 30 | clearSearch: (close = false) => { 31 | this.setState({ ...DEFAULT_STATE, isOpen: !close },()=>{ this.props.toggleDoOpen(this.state.isOpen) }); 32 | }, 33 | selectHit: selectedResultIndex => { 34 | this.setState({ selectedResultIndex }, () => {this.props.searchHit(this.state.flatHits[selectedResultIndex]); this.state.toggle();}); 35 | }, 36 | selectUp: () => { 37 | const { flatHits, selectedResultIndex } = this.state; 38 | 39 | if (selectedResultIndex > 0) { 40 | this.setState({ selectedResultIndex: selectedResultIndex - 1 }); 41 | return; 42 | } 43 | 44 | this.setState({ selectedResultIndex: flatHits.length - 1 }); 45 | }, 46 | selectDown: () => { 47 | const { flatHits, selectedResultIndex } = this.state; 48 | 49 | if (selectedResultIndex < flatHits.length - 1) { 50 | this.setState({ selectedResultIndex: selectedResultIndex + 1 }); 51 | return; 52 | } 53 | 54 | this.setState({ selectedResultIndex: 0 }); 55 | }, 56 | handleKeyUp: input => { 57 | const { clearSearch, performSearch } = this.state; 58 | 59 | if (!input) { 60 | return; 61 | } 62 | 63 | if (!input) { 64 | clearSearch(); 65 | } else { 66 | performSearch(input); 67 | } 68 | }, 69 | handleKeyDown: event => { 70 | const { selectUp, selectDown, clearSearch } = this.state; 71 | 72 | // verificamos a tecla ↑ ↓ tab shift+tab ctrl+j ctrl+k 73 | switch (event.key) { 74 | case "ArrowUp": 75 | selectUp(); 76 | event.preventDefault(); 77 | break; 78 | case "ArrowDown": 79 | selectDown(); 80 | event.preventDefault(); 81 | break; 82 | case "k": 83 | if (event.ctrlKey) { 84 | selectUp(); 85 | } 86 | event.preventDefault(); 87 | break; 88 | case "j": 89 | if (event.ctrlKey) { 90 | selectDown(); 91 | } 92 | event.preventDefault(); 93 | break; 94 | case "Tab": 95 | if (event.shiftKey) { 96 | selectUp(); 97 | } else { 98 | selectDown(); 99 | } 100 | event.preventDefault(); 101 | break; 102 | case "Escape": 103 | clearSearch(true); 104 | event.preventDefault(); 105 | break; 106 | } 107 | }, 108 | performSearch: async term => { 109 | ipcRenderer.invoke('getSearchResult', term).then((result) => { 110 | const flatHits = result.map((card, index) => { 111 | return { 112 | ...card, 113 | flatIndex: index 114 | }; 115 | }); 116 | // adding the flatIndex property will come in handy later 117 | this.setState({ 118 | flatHits: flatHits, 119 | hits: groupBy(flatHits, "dateKey") 120 | }); 121 | }) 122 | 123 | } 124 | }; 125 | 126 | _listenKey = event => { 127 | const isCtrlSpace = event.keyCode === 32 && event.ctrlKey; 128 | if (!isCtrlSpace) { 129 | return; 130 | } 131 | 132 | this.state.toggle(); 133 | }; 134 | 135 | componentWillReceiveProps(nextProps){ 136 | if(nextProps !== this.props){ 137 | this.props = nextProps; 138 | this.setState({isOpen: nextProps.doOpen}); 139 | } 140 | } 141 | componentWillUnmount() { 142 | document.body.removeEventListener("keydown", this._listenKey); 143 | } 144 | 145 | componentDidMount() { 146 | document.body.addEventListener("keydown", this._listenKey); 147 | } 148 | render() { 149 | if (!this.state.isOpen) { 150 | return null; 151 | } 152 | 153 | return ( 154 | 155 | 156 | 157 | 158 | 159 | 160 | ); 161 | } 162 | } 163 | 164 | export default Spotlight; 165 | -------------------------------------------------------------------------------- /src/components/spotlight-search/modules/HitDetail.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled from "styled-components"; 3 | 4 | import SpotlightContext from "./SpotlightContext"; 5 | 6 | import media from "../utils/media"; 7 | 8 | const DetailContainer = styled.div` 9 | width: 390px; 10 | height: 375px; 11 | display: none; 12 | color: #ffffff; 13 | overflow-y: auto; 14 | margin-left: 290px; 15 | background-color: ${props => props.theme.detailBg}; 16 | 17 | ${media.pc`display: block;`}; 18 | `; 19 | 20 | const Detail = styled.div` 21 | padding: 8px; 22 | display: flex; 23 | align-items: center; 24 | flex-direction: column; 25 | justify-content: center; 26 | `; 27 | 28 | const Image = styled.img` 29 | height: 250px; 30 | border-radius: 15px; 31 | `; 32 | 33 | const Title = styled.h1` 34 | color: #ffffff; 35 | overflow: hidden; 36 | white-space: nowrap; 37 | text-overflow: ellipsis; 38 | `; 39 | 40 | export default () => ( 41 | 42 | {({ flatHits, selectedResultIndex }) => { 43 | const hit = flatHits[selectedResultIndex]; 44 | 45 | return ( 46 | 47 | 48 | {hit.name} 49 | {hit.name} 50 | {hit.type} 51 | {hit.setName} 52 | 53 | 54 | ); 55 | }} 56 | 57 | ); 58 | -------------------------------------------------------------------------------- /src/components/spotlight-search/modules/HitList.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled from "styled-components"; 3 | 4 | import SpotlightContext from "./SpotlightContext"; 5 | 6 | import media from "../utils/media"; 7 | 8 | const List = styled.div` 9 | width: 100%; 10 | overflow: hidden; 11 | overflow-y: auto; 12 | user-select: none; 13 | position: absolute; 14 | height: calc(100% - 55px); 15 | border-right: 1px solid #515253; 16 | background-color: rgba(0, 20, 41, 0.97); 17 | 18 | 19 | 20 | ul { 21 | min-height: calc(100% - 55px); 22 | } 23 | 24 | ul, 25 | li { 26 | margin: 0; 27 | padding: 0; 28 | width: 100%; 29 | list-style-type: none; 30 | } 31 | `; 32 | 33 | const ResultHeader = styled.div` 34 | color: #ffffff; 35 | font-size: 12px; 36 | padding: 4px 0 2px 25px; 37 | text-transform: uppercase; 38 | background-color: rgba(53, 75, 84, 0.97); 39 | `; 40 | 41 | const Hit = styled.li` 42 | color: #ffffff; 43 | cursor: pointer; 44 | font-size: 12px; 45 | padding: 6px 6px 6px 25px !important; 46 | 47 | ${props => props.selected && `color: #ffffff; background-color: #0093f8;`}; 48 | `; 49 | 50 | export default () => ( 51 | 52 | {({ hits, selectedResultIndex, selectHit }) => ( 53 | 54 |
    55 | {Object.keys(hits).map(hitKey => { 56 | if (hits[hitKey].length === 0) { 57 | return null; 58 | } 59 | 60 | return ( 61 |
  • 62 | {hitKey} 63 |
      64 | {hits[hitKey].map(hit => ( 65 | 70 | {hit.searchContent} 71 | 72 | ))} 73 |
    74 |
  • 75 | ); 76 | })} 77 |
78 |
79 | )} 80 |
81 | ); 82 | -------------------------------------------------------------------------------- /src/components/spotlight-search/modules/Hits.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled from "styled-components"; 3 | 4 | import HitList from "./HitList"; 5 | import HitDetail from "./HitDetail"; 6 | import SpotlightContext from "./SpotlightContext"; 7 | 8 | const Hits = styled.div` 9 | max-height: 0; 10 | min-height: 0; 11 | overflow: hidden; 12 | transition: all 0.3s; 13 | background-color: rgba(0, 20, 41, 0.97); 14 | 15 | ${props => 16 | props.open && 17 | ` 18 | min-height: 375px; 19 | max-height: 400px; 20 | border-top: 1px solid #515253; 21 | `}; 22 | `; 23 | 24 | export default () => ( 25 | 26 | {({ flatHits }) => { 27 | const hasResults = flatHits.length > 0; 28 | 29 | return ( 30 | 31 | {hasResults ? ( 32 | 33 | ) : null} 34 | 35 | ); 36 | }} 37 | 38 | ); 39 | -------------------------------------------------------------------------------- /src/components/spotlight-search/modules/Overlay.js: -------------------------------------------------------------------------------- 1 | import styled from "styled-components"; 2 | 3 | import media from "../utils/media"; 4 | 5 | export default styled.div` 6 | top: 30%; 7 | left: 1%; 8 | width: 98%; 9 | z-index: 100; 10 | font-size: 12px; 11 | overflow: hidden; 12 | border-radius: 6px; 13 | position: absolute; 14 | letter-spacing: 0.3px; 15 | font-family: Verdana, "Lucida Sans Unicode", sans-serif; 16 | box-shadow: 0 12px 15px 0 rgba(0, 0, 0, 0.24), 17 | 0 17px 50px 0 rgba(0, 0, 0, 0.19); 18 | 19 | ${media.pc` 20 | left: 50%; 21 | width: 680px; 22 | margin-left: -340px; 23 | `}; 24 | `; 25 | -------------------------------------------------------------------------------- /src/components/spotlight-search/modules/SearchBar.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled from "styled-components"; 3 | 4 | import SearchIcon from "./SearchIcon"; 5 | import SearchInput from "./SearchInput"; 6 | 7 | const SearchBar = styled.div` 8 | z-index: 10; 9 | height: 55px; 10 | position: relative; 11 | background-color: rgba(0, 21, 41, 0.97); 12 | `; 13 | 14 | export default () => { 15 | return ( 16 | 17 | 18 | 19 | 20 | ); 21 | }; 22 | -------------------------------------------------------------------------------- /src/components/spotlight-search/modules/SearchIcon.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled from "styled-components"; 3 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; 4 | 5 | import media from "../utils/media"; 6 | 7 | const SearchIcon = styled.div` 8 | width: 22px; 9 | height: 22px; 10 | margin: 16.5px; 11 | position: absolute; 12 | background-size: cover; 13 | 14 | ${media.pc` 15 | float: left; 16 | position: static; 17 | `}; 18 | 19 | svg { 20 | color: #a6a6a6; 21 | } 22 | `; 23 | 24 | export default () => ( 25 | 26 | 27 | 28 | ); 29 | -------------------------------------------------------------------------------- /src/components/spotlight-search/modules/SearchInput.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled from "styled-components"; 3 | import { debounce } from "lodash-es"; 4 | 5 | import media from "../utils/media"; 6 | import SpotlightContext from "./SpotlightContext"; 7 | 8 | const Input = styled.input` 9 | margin: 0; 10 | padding: 0; 11 | height: 55px; 12 | color: #ffffff; 13 | font-size: 1.7em; 14 | font-weight: 100; 15 | padding-left: 55px; 16 | padding-right: 50px; 17 | box-sizing: border-box; 18 | border: none !important; 19 | outline: none !important; 20 | max-width: 100% !important; 21 | background-color: transparent; 22 | 23 | ${media.pc` 24 | padding: 0; 25 | float: left; 26 | box-sizing: content-box; 27 | max-width: 350px !important; 28 | `}; 29 | 30 | &:-ms-input-placeholder { 31 | color: #a6a6a6; 32 | } 33 | &:-moz-placeholder { 34 | color: #a6a6a6; 35 | } 36 | &::-moz-placeholder { 37 | color: #a6a6a6; 38 | } 39 | &::-webkit-input-placeholder { 40 | color: #a6a6a6; 41 | } 42 | `; 43 | 44 | class SearchInput extends React.PureComponent { 45 | constructor(props) { 46 | super(props); 47 | this._performSearch = debounce(this._performSearch, 500); 48 | } 49 | 50 | _performSearch = searchTerm => { 51 | this.props.keyUpFunction(searchTerm); 52 | }; 53 | 54 | performSearch = event => { 55 | this._performSearch(event.target.value); 56 | }; 57 | 58 | render() { 59 | return ( 60 | 66 | ); 67 | } 68 | } 69 | 70 | export default () => ( 71 | 72 | {({ handleKeyUp, handleKeyDown }) => ( 73 | 77 | )} 78 | 79 | ); 80 | -------------------------------------------------------------------------------- /src/components/spotlight-search/modules/SpotlightContext.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export default React.createContext({}); 4 | -------------------------------------------------------------------------------- /src/components/spotlight-search/utils/media.js: -------------------------------------------------------------------------------- 1 | import { css } from "styled-components"; 2 | 3 | export default { 4 | pc: (...args) => css` 5 | @media only screen and (min-width: 680px) { 6 | ${css(...args)}; 7 | } 8 | `, 9 | 10 | tablet: (...args) => css` 11 | @media only screen and (max-width: 1279px) { 12 | ${css(...args)}; 13 | } 14 | `, 15 | 16 | phone: (...args) => css` 17 | @media only screen and (max-width: 660px) { 18 | ${css(...args)}; 19 | } 20 | ` 21 | }; 22 | -------------------------------------------------------------------------------- /src/components/title-bar/index.js: -------------------------------------------------------------------------------- 1 | import TitleBar from './modules/TitleBar'; 2 | 3 | export default TitleBar ; -------------------------------------------------------------------------------- /src/components/title-bar/media/close.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/title-bar/media/closeLight.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/title-bar/media/hamburger.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/title-bar/media/history.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 10 | 12 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /src/components/title-bar/media/maximize.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/title-bar/media/minimize.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/title-bar/media/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/title-bar/media/previous.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/title-bar/media/restore.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/title-bar/modules/FlatPickr.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import flatpickr from 'flatpickr'; 3 | import 'flatpickr/dist/flatpickr.min.css'; 4 | const ipcRenderer = window.electron.ipcRenderer; 5 | export default class Flatpickr extends React.Component { 6 | constructor(props) { 7 | super(props); 8 | this.datePicker = React.createRef(); 9 | this.onChange = this.onChange.bind(this); 10 | this.dayCreate = this.dayCreate.bind(this); 11 | this.userContentDates = this.sanatizeKeys(ipcRenderer.sendSync('getSavedDates')); 12 | this.state = { 13 | date: props.date 14 | } 15 | } 16 | sanatizeKeys(keys){ 17 | return keys.map(key=> +new Date(key)); 18 | } 19 | onChange(selectedDates, dateStr, instance) { 20 | this.props.dateChangeCallBack(new Date(selectedDates)); 21 | } 22 | componentDidMount() { 23 | this.userContentDates = this.sanatizeKeys(ipcRenderer.sendSync('getSavedDates')); 24 | flatpickr(this.datePicker.current, { 25 | defaultDate: this.state.date, 26 | onChange: this.onChange, 27 | onDayCreate: this.dayCreate 28 | }); 29 | } 30 | componentWillReceiveProps(nextProps){ 31 | if(nextProps !== this.props){ 32 | this.userContentDates = this.sanatizeKeys(ipcRenderer.sendSync('getSavedDates')); 33 | this.props = nextProps; 34 | this.setState({date: nextProps.date},()=>{ 35 | let fp = flatpickr(this.datePicker.current,{ 36 | defaultDate: this.state.date, 37 | onChange: this.onChange, 38 | onDayCreate: this.dayCreate 39 | }) 40 | fp.setDate(this.state.date, false); 41 | }); 42 | } 43 | } 44 | dayCreate(dObj, dStr, fp, dayElem){ 45 | if (this.userContentDates.indexOf(+dayElem.dateObj) !== -1) { 46 | dayElem.className += " ab_contentIndicator"; 47 | } 48 | } 49 | yesterday (date){ 50 | let dt = new Date(date); 51 | return new Date((dt.setDate(dt.getDate()-1))); 52 | } 53 | tomorrow (date){ 54 | let dt = new Date(date); 55 | return new Date((dt.setDate(dt.getDate()+1))); 56 | } 57 | render() { 58 | return( 59 |
60 |
{ this.props.dateChangeCallBack(this.yesterday(this.state.date)) }}>
61 | 62 |
{ this.props.dateChangeCallBack(this.tomorrow(this.state.date)) }}>
63 |
{ this.props.dateChangeCallBack(new Date()) }}>
64 |
65 | ); 66 | } 67 | } -------------------------------------------------------------------------------- /src/components/title-bar/modules/OptionsMenu.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Menu, MenuItem, Popover, Position} from "@blueprintjs/core"; 3 | import "@blueprintjs/core/lib/css/blueprint.css"; 4 | const shell = window.electron.shell; 5 | export default class OptionsMenu extends React.PureComponent{ 6 | constructor(props){ 7 | super(props); 8 | this.handleOpenAboutWindow = this.handleOpenAboutWindow.bind(this); 9 | this.handleReportBug = this.handleReportBug.bind(this); 10 | } 11 | handleOpenAboutWindow(e){ 12 | this.props.openAbout(true); 13 | } 14 | 15 | handleReportBug(e){ 16 | shell.openExternal("https://docs.google.com/forms/d/e/1FAIpQLSeUpOj6hMS8H8sfiju7OzADnb8Q7Frw5Bw55tmYMSIuA4NJpQ/viewform?usp=sf_link"); 17 | } 18 | 19 | render(){ 20 | const exampleMenu = ( 21 | 22 | 23 | 24 | 25 | ); 26 | return( 27 | 28 |
29 |
30 | ); 31 | } 32 | } -------------------------------------------------------------------------------- /src/components/title-bar/modules/TitleBar.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import WindowsControl from './WindowsControl' 3 | import Flatpickr from "./FlatPickr"; 4 | import OptionsMenu from './OptionsMenu'; 5 | import '../styles/TitleBar.css' 6 | class TitleBar extends React.Component{ 7 | constructor(props){ 8 | super(props); 9 | this.state = { 10 | date: props.date 11 | } 12 | } 13 | componentWillReceiveProps(nextProps){ 14 | if(nextProps !== this.props){ 15 | this.props = nextProps; 16 | this.setState({date: nextProps.date}); 17 | } 18 | } 19 | render(){ 20 | return( 21 |
22 | 23 |
24 | 25 |
26 | 27 |
28 | ); 29 | } 30 | } 31 | export default TitleBar; -------------------------------------------------------------------------------- /src/components/title-bar/modules/WindowsControl.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | const remote = window.electron.remote; 3 | export default class WindowsControl extends React.Component{ 4 | constructor(props){ 5 | super(props) 6 | this.getCurrentWindow = this.getCurrentWindow.bind(this); 7 | this.minimizeWindow = this.minimizeWindow.bind(this); 8 | this.maxUnmaxWindow = this.maxUnmaxWindow.bind(this); 9 | this.closeWindow = this.closeWindow.bind(this); 10 | this.isWindowMaximized = this.isWindowMaximized.bind(this); 11 | this.state = { 12 | isMaximized : this.isWindowMaximized() 13 | }; 14 | } 15 | getCurrentWindow() { 16 | return remote.getCurrentWindow(); 17 | } 18 | minimizeWindow(browserWindow = this.getCurrentWindow()) { 19 | if (browserWindow.minimizable) { 20 | browserWindow.minimize(); 21 | } 22 | } 23 | maxUnmaxWindow(browserWindow = this.getCurrentWindow()) { 24 | if (browserWindow.isMaximized()) { 25 | browserWindow.unmaximize(); 26 | } else { 27 | browserWindow.maximize(); 28 | } 29 | this.setState({isMaximized: this.isWindowMaximized()}) 30 | } 31 | closeWindow(browserWindow = this.getCurrentWindow()) { 32 | browserWindow.close(); 33 | } 34 | isWindowMaximized(browserWindow = this.getCurrentWindow()) { 35 | return browserWindow.isMaximized(); 36 | } 37 | render(){ 38 | return( 39 |
40 |
{ this.minimizeWindow() }}>
41 |
{ this.maxUnmaxWindow() }}>
42 |
{this.closeWindow()}}>
43 |
44 | ); 45 | } 46 | } -------------------------------------------------------------------------------- /src/components/title-bar/styles/TitleBar.css: -------------------------------------------------------------------------------- 1 | .ab_TitleBar{ 2 | background-color: #182026; 3 | box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25); 4 | display: grid; 5 | grid-template-columns: 50px auto 138px; 6 | box-sizing: border-box; 7 | -webkit-app-region: drag; 8 | } 9 | 10 | .ab_TitleBar .bp3-popover-wrapper{ 11 | height: 35px; 12 | } 13 | 14 | /* Window control Styles */ 15 | .ab_TB_WinControls 16 | { 17 | -webkit-app-region: no-drag; 18 | display: flex; 19 | flex-direction: row; 20 | } 21 | .ab_icons 22 | { 23 | justify-content: center; 24 | align-items: center; 25 | width: 46px; 26 | } 27 | .ab_minimizeIcon 28 | { 29 | background: url('../media/minimize.svg'); 30 | background-repeat: no-repeat; 31 | background-position: center center; 32 | } 33 | .ab_minimizeIcon:hover{ 34 | filter: brightness(3.5); 35 | background-color: rgba(255, 255, 255, 0.1); 36 | } 37 | .ab_maximizeIcon 38 | { 39 | background: url('../media/maximize.svg'); 40 | background-repeat: no-repeat; 41 | background-position: center center; 42 | } 43 | .ab_maximizeIcon:hover{ 44 | filter: brightness(3.5); 45 | background-color: rgba(255, 255, 255, 0.1); 46 | } 47 | .ab_restoreIcon 48 | { 49 | background: url('../media/restore.svg'); 50 | background-repeat: no-repeat; 51 | background-position: center center; 52 | } 53 | .ab_restoreIcon:hover{ 54 | filter: brightness(3.5); 55 | background-color: rgba(255, 255, 255, 0.1); 56 | } 57 | .ab_closeIcon 58 | { 59 | background: url('../media/close.svg'); 60 | background-repeat: no-repeat; 61 | background-position: center center; 62 | } 63 | .ab_closeIcon:hover{ 64 | background: url('../media/closeLight.svg'); 65 | background-color: rgba(232, 17, 35, 0.9); 66 | background-repeat: no-repeat; 67 | background-position: center center; 68 | } 69 | 70 | /* Hamburger Options Styles */ 71 | 72 | .ab_TB_Options{ 73 | -webkit-app-region: no-drag; 74 | background: url('../media/hamburger.svg'); 75 | background-repeat: no-repeat; 76 | background-position: center center; 77 | cursor: pointer; 78 | width: 50px; 79 | height: 35px; 80 | } 81 | 82 | .ab_TB_Options:hover{ 83 | filter: brightness(3.5); 84 | } 85 | 86 | /* Date Picker Styles */ 87 | .ab_TB_DatePicker 88 | { 89 | box-sizing: border-box; 90 | text-align: center; 91 | justify-items: center; 92 | } 93 | .ab_TB_DatePicker input{ 94 | -webkit-app-region: no-drag; 95 | margin-top:8px; 96 | text-align: center; 97 | border: none; 98 | border-radius: 3px; 99 | height: 22px; 100 | background-color: #394B59; 101 | color: #d4d2d2 ; 102 | } 103 | .ab_TB_DatePicker input:hover{ 104 | color: #ffff; 105 | filter: brightness(1.3); 106 | } 107 | .ab_TB_flatpickr{ 108 | display: inline-flex; 109 | } 110 | .ab_TB_resetDate{ 111 | -webkit-app-region: no-drag; 112 | cursor: pointer; 113 | height: 22px; 114 | width: 32px; 115 | border: none; 116 | border-radius: 3px; 117 | background: url("../media/history.svg"); 118 | background-repeat: no-repeat; 119 | background-position: center center; 120 | margin-top: 8px; 121 | margin-left: 5px; 122 | background-color: #394B59; 123 | } 124 | .ab_TB_resetDate:hover{ 125 | filter: brightness(1.3); 126 | } 127 | 128 | .ab_TB_PreviousDate{ 129 | -webkit-app-region: no-drag; 130 | cursor: pointer; 131 | height: 22px; 132 | width: 32px; 133 | border: none; 134 | border-radius: 3px; 135 | background: url("../media/previous.svg"); 136 | background-repeat: no-repeat; 137 | background-position: center center; 138 | background-size: 20px 20px; 139 | margin-top: 8px; 140 | margin-right: 5px; 141 | background-color: #394B59; 142 | } 143 | .ab_TB_PreviousDate:hover{ 144 | filter: brightness(1.3); 145 | } 146 | .ab_TB_NextDate{ 147 | -webkit-app-region: no-drag; 148 | cursor: pointer; 149 | height: 22px; 150 | width: 32px; 151 | border: none; 152 | border-radius: 3px; 153 | background: url("../media/next.svg"); 154 | background-repeat: no-repeat; 155 | background-position: center center; 156 | background-size: 20px 20px; 157 | margin-top: 8px; 158 | margin-left: 5px; 159 | background-color: #394B59; 160 | } 161 | .ab_TB_NextDate:hover{ 162 | filter: brightness(1.3); 163 | } 164 | .ab_contentIndicator::before{ 165 | content: ""; 166 | position: absolute; 167 | width: 5px; 168 | height: 5px; 169 | background-color: red; 170 | border-radius: 50%; 171 | bottom: 0; 172 | left: 45%; 173 | } -------------------------------------------------------------------------------- /src/components/todo-meter/index.js: -------------------------------------------------------------------------------- 1 | import TodoMeter from './modules/TodoMeter'; 2 | export default TodoMeter; -------------------------------------------------------------------------------- /src/components/todo-meter/media/alldone.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/components/todo-meter/media/arrow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/components/todo-meter/media/check.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/components/todo-meter/media/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/components/todo-meter/media/pause.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | pause 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/components/todo-meter/media/plus.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Group 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/components/todo-meter/media/resume.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/components/todo-meter/media/x.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Group 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/components/todo-meter/modules/AddItemForm.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import styles from "../styles/AddItemForm.module.scss"; 4 | 5 | // Form to populate todo items 6 | export default class AddItemForm extends React.Component{ 7 | constructor(props){ 8 | super(props); 9 | this.inputRef = React.createRef(); 10 | this.addItem = this.addItem.bind(this); 11 | } 12 | 13 | addItem(e) { 14 | const newItem = { 15 | text: this.inputRef.current.value, 16 | key: Date.now(), 17 | status: "pending" 18 | }; 19 | if (!!newItem.text.trim()) { 20 | this.props.addItemCallback(newItem) 21 | } 22 | e.preventDefault(); 23 | this.inputRef.current.value = ""; 24 | this.inputRef.current.focus(); 25 | } 26 | 27 | render(){ 28 | return ( 29 |
30 | 31 |
36 | {!this.state.paused && !this.state.completed && ( 37 | 38 | )} 39 | {this.state.paused && !this.state.completed && ( 40 | 45 | )} 46 | {!this.state.completed && ( 47 | 52 | )} 53 | 54 | 55 | ); 56 | } 57 | 58 | 59 | } -------------------------------------------------------------------------------- /src/components/todo-meter/modules/ItemList.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { 3 | Accordion, 4 | AccordionItem, 5 | AccordionButton, 6 | AccordionPanel 7 | } from "@reach/accordion"; 8 | import "@reach/accordion/styles.css"; 9 | 10 | import Progress from "./Progress"; 11 | import AddItemForm from "./AddItemForm"; 12 | import Item from "./Item"; 13 | import styles from "../styles/ItemList.module.scss"; 14 | import arrow from "../media/arrow.svg"; 15 | import alldone from "../media/alldone.svg"; 16 | import {Classes} from "@blueprintjs/core"; 17 | 18 | 19 | export default class ItemList extends React.Component { 20 | constructor(props){ 21 | super(props); 22 | this.state = { 23 | items: props.items, 24 | pending: props.pending, 25 | paused: props.paused, 26 | completed: props.completed 27 | }; 28 | } 29 | 30 | componentWillReceiveProps(nextProps){ 31 | if(nextProps !== this.props){ 32 | this.props = nextProps; 33 | this.setState({ 34 | items: nextProps.items, 35 | pending: nextProps.pending, 36 | paused: nextProps.paused, 37 | completed: nextProps.completed 38 | }); 39 | } 40 | } 41 | 42 | render(){ 43 | return ( 44 |
45 | 46 | 47 | {this.state.pending.length > 0 ? ( 48 | <> 49 | {this.state.pending.map(item => { 50 | return ; 51 | })} 52 | 53 | ) : ( 54 |
55 | Nothing to do! 56 |
57 | )} 58 | 59 | {this.state.paused.length > 0 && ( 60 | 61 | 62 | Do Later Toggle 63 | Do Later 64 | 65 | 66 | {this.state.paused && 67 | this.state.paused.map(item => { 68 | return ; 69 | })} 70 | 71 | 72 | )} 73 | {this.state.completed.length > 0 && ( 74 | 75 | 76 | Completed Toggle Completed 77 | 78 | 79 | {this.state.completed && 80 | this.state.completed.map(item => { 81 | return ; 82 | })} 83 | 84 | 85 | )} 86 | 87 | 88 | {(this.state.completed.length > 0 || this.state.paused.length > 0) && ( 89 |
90 | 97 |
98 | )} 99 |
100 | ); 101 | } 102 | 103 | } 104 | -------------------------------------------------------------------------------- /src/components/todo-meter/modules/Progress.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import styles from "../styles/Progress.module.scss"; 4 | 5 | export default class Progress extends React.Component { 6 | constructor(props){ 7 | super(props) 8 | this.state = { 9 | items: props.items, 10 | paused: props.paused, 11 | completed: props.completed, 12 | } 13 | } 14 | 15 | componentWillReceiveProps(nextProps){ 16 | if(nextProps !== this.props){ 17 | this.props = nextProps; 18 | this.setState({ 19 | items: nextProps.items, 20 | paused: nextProps.paused, 21 | completed: nextProps.completed, 22 | }); 23 | } 24 | } 25 | render(){ 26 | const totalAmount = this.state.items.length; 27 | const completedAmount = this.state.completed.length; 28 | const pausedAmount = this.state.paused.length; 29 | 30 | let completedPercentage = completedAmount / totalAmount; 31 | let pausedPercentage = pausedAmount / totalAmount + completedPercentage; 32 | 33 | if (isNaN(completedPercentage)) { 34 | completedPercentage = 0; 35 | } 36 | 37 | if (isNaN(pausedPercentage)) { 38 | pausedPercentage = 0; 39 | } 40 | 41 | return ( 42 |
43 |
47 |
51 |
52 | ); 53 | } 54 | 55 | 56 | 57 | } 58 | 59 | -------------------------------------------------------------------------------- /src/components/todo-meter/modules/TodoMeter.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ItemList from './ItemList'; 3 | export default class TodoMeter extends React.Component{ 4 | constructor(props){ 5 | super(props); 6 | this.state = { 7 | date: props.date, 8 | items: props.toDoItems, 9 | pending: props.toDoItems.filter(item => item.status === "pending"), 10 | paused: props.toDoItems.filter(item => item.status === "paused"), 11 | completed: props.toDoItems.filter(item => item.status === "completed") 12 | }; 13 | this.resetItems = this.resetItems.bind(this); 14 | this.deleteItem = this.deleteItem.bind(this); 15 | this.updateItem = this.updateItem.bind(this); 16 | this.addItem = this.addItem.bind(this); 17 | } 18 | componentWillReceiveProps(nextProps){ 19 | if(nextProps !== this.props){ 20 | this.props = nextProps; 21 | this.setState({ 22 | date: nextProps.date, 23 | items: nextProps.toDoItems, 24 | pending: nextProps.toDoItems.filter(item => item.status === "pending"), 25 | paused: nextProps.toDoItems.filter(item => item.status === "paused"), 26 | completed: nextProps.toDoItems.filter(item => item.status === "completed") 27 | }) 28 | } 29 | } 30 | resetItems(){ 31 | const newItems = this.state.items 32 | .map(i => { 33 | if (i.status === "paused" || i.status === "completed") { 34 | return Object.assign({}, i, { 35 | status: "pending" 36 | }); 37 | } 38 | return i; 39 | }); 40 | this.setState({items: newItems}, ()=>{ this.props.itemChangeCallback(this.state.items) }) 41 | } 42 | deleteItem(item){ 43 | this.setState( 44 | {items: this.state.items.filter(itm => itm.key !== item.key)}, 45 | ()=>{ this.props.itemChangeCallback(this.state.items) } 46 | ) 47 | } 48 | updateItem(item, newStatus){ 49 | const newItems = this.state.items.map(i => { 50 | if (i.key === item.key) { 51 | return Object.assign({}, i, { 52 | status: newStatus 53 | }); 54 | } 55 | return i; 56 | }); 57 | this.setState({items: newItems}, ()=>{ this.props.itemChangeCallback(this.state.items) }) 58 | } 59 | addItem(newItem){ 60 | this.setState({items: this.state.items.concat(newItem)}, ()=>{ this.props.itemChangeCallback(this.state.items) }) 61 | } 62 | 63 | render(){ 64 | return( 65 |
66 | 76 |
77 | ); 78 | } 79 | } -------------------------------------------------------------------------------- /src/components/todo-meter/styles/AddItemForm.module.scss: -------------------------------------------------------------------------------- 1 | @import "./variables"; 2 | 3 | .form { 4 | position: relative; 5 | input { 6 | margin: 0 0 10px 0; 7 | padding: 20px 70px 20px 20px; 8 | width: 100%; 9 | height: 70px; 10 | background: $input-color; 11 | border: none; 12 | border-radius: 3px; 13 | box-sizing: border-box; 14 | color: $font-color; 15 | font-size: $item-font-size; 16 | outline: none; 17 | } 18 | button { 19 | position: absolute; 20 | top: 20px; 21 | right: 20px; 22 | width: 30px; 23 | height: 30px; 24 | background: no-repeat url("../media/plus.svg"); 25 | border: none; 26 | &:after { 27 | display: block; 28 | content: ""; 29 | position: absolute; 30 | top: 50%; 31 | left: 50%; 32 | background: #fff; 33 | transform: translate(-50%, -50%); 34 | border-radius: 100%; 35 | width: 0; 36 | height: 0; 37 | } 38 | &:focus { 39 | outline: none; 40 | } 41 | &:hover:after { 42 | animation: click 0.5s; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/components/todo-meter/styles/Item.module.scss: -------------------------------------------------------------------------------- 1 | @import "./variables"; 2 | 3 | .item { 4 | display: flex; 5 | align-items: center; 6 | justify-content: space-between; 7 | margin: 0 0 10px 0; 8 | padding: 20px; 9 | width: 100%; 10 | min-height: 70px; 11 | background: $item-color; 12 | border: none; 13 | border-radius: 3px; 14 | box-sizing: border-box; 15 | font-size: $item-font-size; 16 | @media (max-width: 600px) { 17 | margin: 20px auto; 18 | } 19 | .itemName { 20 | width: calc(100% - 110px); 21 | overflow: auto; 22 | 23 | &::-webkit-scrollbar { 24 | background-color: $item-color; 25 | height: 0.75em; 26 | @media (max-width: 600px) { 27 | height: 0.25em; 28 | } 29 | } 30 | 31 | &::-webkit-scrollbar-thumb:window-inactive, 32 | &::-webkit-scrollbar-thumb { 33 | background: $bg; 34 | border: 3px solid $item-color; 35 | border-left: none; 36 | border-right: none; 37 | border-radius: 3px; 38 | } 39 | } 40 | .buttons { 41 | display: flex; 42 | justify-content: space-between; 43 | width: 100px; 44 | 45 | &.completedButtons { 46 | justify-content: flex-end; 47 | } 48 | 49 | button { 50 | position: relative; 51 | height: 24px; 52 | border: none; 53 | &.delete { 54 | width: 24px; 55 | background: no-repeat url("../media/x.svg"); 56 | &:after { 57 | background: $red; 58 | } 59 | } 60 | &.pause { 61 | width: 24px; 62 | background: no-repeat url("../media/pause.svg"); 63 | &:after { 64 | background: $yellow; 65 | } 66 | } 67 | &.resume { 68 | width: 24px; 69 | background: no-repeat url("../media/resume.svg"); 70 | &:after { 71 | background: $yellow; 72 | } 73 | } 74 | &.complete { 75 | width: 30px; 76 | background: no-repeat url("../media/check.svg"); 77 | &:after { 78 | background: $green; 79 | } 80 | } 81 | &:after { 82 | display: block; 83 | content: ""; 84 | position: absolute; 85 | top: 50%; 86 | left: 50%; 87 | transform: translate(-50%, -50%); 88 | border-radius: 100%; 89 | width: 0; 90 | height: 0; 91 | } 92 | &:focus { 93 | outline: 1px solid $item-color; 94 | &:after { 95 | animation: click 0.5s; 96 | } 97 | } 98 | &:hover:after { 99 | animation: click 0.5s; 100 | } 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/components/todo-meter/styles/ItemList.module.scss: -------------------------------------------------------------------------------- 1 | @import "./variables"; 2 | 3 | .alldone { 4 | display: flex; 5 | justify-content: center; 6 | img { 7 | margin: 20px; 8 | width: 100px; 9 | height: 100px; 10 | animation: slidedown 1s; 11 | border-radius:50%; 12 | } 13 | } 14 | 15 | .toggle { 16 | display: flex; 17 | align-items: center; 18 | margin: 1em 0 0.5em; 19 | padding: 0; 20 | border: none; 21 | background: $bg; 22 | color: $font-color; 23 | cursor: pointer; 24 | font-size: 1.5em; 25 | font-weight: bold; 26 | animation: slidedown 1s; 27 | img { 28 | padding: 0 5px 0 0; 29 | width: 1em; 30 | height: 1em; 31 | box-sizing: border-box; 32 | transition: transform 0.2s; 33 | } 34 | &[aria-expanded="true"] img { 35 | padding: 5px 0 0 0; 36 | transform: rotate(90deg); 37 | } 38 | &:focus { 39 | outline: 2px solid $bg; 40 | span { 41 | animation: blink 0.5s; 42 | } 43 | } 44 | &:hover { 45 | opacity: 0.9; 46 | } 47 | } 48 | 49 | .panel { 50 | animation: slidedown 0.2s ease; 51 | } 52 | 53 | .reset { 54 | text-align: center; 55 | button { 56 | height: 40px; 57 | border-radius: 3px; 58 | background: $item-color; 59 | border: none; 60 | color: $font-color; 61 | cursor: pointer; 62 | font-size: $item-font-size; 63 | animation: slidedown 1s; 64 | &:hover { 65 | opacity: 0.9; 66 | } 67 | } 68 | } 69 | 70 | @keyframes slidedown { 71 | 0% { 72 | opacity: 0; 73 | transform: translateY(-10px); 74 | } 75 | 100% { 76 | opacity: 1; 77 | transform: translateY(0); 78 | } 79 | } 80 | 81 | @keyframes blink { 82 | 0% { 83 | text-shadow: 0 0 2px white; 84 | } 85 | 50% { 86 | text-shadow: 0 0 10px white; 87 | } 88 | 100% { 89 | text-shadow: 0 0 2px white; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/components/todo-meter/styles/Progress.module.scss: -------------------------------------------------------------------------------- 1 | @import "./variables"; 2 | 3 | .progress { 4 | position: relative; 5 | width: 100%; 6 | height: 15px; 7 | margin: 0 0 20px; 8 | background: $input-color; 9 | border-radius: 3px; 10 | } 11 | 12 | .progressbar { 13 | position: absolute; 14 | top: 0; 15 | width: 100%; 16 | height: 100%; 17 | border-radius: 3px; 18 | transition: width 500ms; 19 | &.paused { 20 | background: $yellow; 21 | } 22 | &.completed { 23 | background: $green; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/components/todo-meter/styles/variables.scss: -------------------------------------------------------------------------------- 1 | $bg: #173341; 2 | $green: #62dca5; 3 | $yellow: #f7f879; 4 | $red: #e1675a; 5 | 6 | $input-color: #000000; 7 | $item-color: #455c67; 8 | $font-color: #fbfafb; 9 | 10 | $big-font-size: 64px; 11 | $heading-font-size: 24px; 12 | $item-font-size: 20px; 13 | 14 | @keyframes click { 15 | 0% { 16 | opacity: 0; 17 | width: 0; 18 | height: 0; 19 | } 20 | 50% { 21 | opacity: 0.5; 22 | } 23 | 100% { 24 | opacity: 0; 25 | width: 30px; 26 | height: 30px; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/components/work-space/index.js: -------------------------------------------------------------------------------- 1 | import WorkSpace from './modules/WorkSpace' 2 | 3 | export default WorkSpace; -------------------------------------------------------------------------------- /src/components/work-space/media/checklist.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 9 | 10 | 11 | 12 | 13 | 16 | 17 | 18 | 19 | 20 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 34 | 35 | 36 | 37 | 38 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 52 | 53 | 54 | 55 | 56 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 77 | 78 | 79 | 80 | 81 | 84 | 85 | 86 | 87 | 88 | 90 | 91 | 92 | 93 | 94 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | -------------------------------------------------------------------------------- /src/components/work-space/media/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ashishBharadwaj/flawesome/731d4caf9a00754d39c150e1af457c787f8dee4b/src/components/work-space/media/icon.png -------------------------------------------------------------------------------- /src/components/work-space/media/list.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/work-space/media/notebook.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 40 | 41 | 42 | 43 | 44 | 46 | 47 | 48 | 49 | 50 | 52 | 53 | 54 | 55 | 56 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /src/components/work-space/media/search.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 14 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /src/components/work-space/media/stickyNotes.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/work-space/modules/AboutWindow.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {Overlay, Classes, H4, Button, Intent } from "@blueprintjs/core"; 3 | import '../styles/About.scss' 4 | import logo from '../media/icon.png' 5 | const remote = window.electron.remote; 6 | export default class AboutWindow extends React.Component{ 7 | constructor(props){ 8 | super(props) 9 | this.state = { 10 | isOpen: props.isOpen || false, 11 | usePortal: true, 12 | productVersion: remote.app.getVersion(), 13 | electronVersion: remote.process.versions.electron, 14 | chromeVersion: remote.process.versions.chrome 15 | } 16 | this.handleOpen = this.handleOpen.bind(this); 17 | this.handleClose = this.handleClose.bind(this); 18 | } 19 | handleOpen(){ 20 | this.setState({isOpen: true}, ()=>{ this.props.onAboutChange(this.state.isOpen) }); 21 | } 22 | handleClose(){ 23 | this.setState({isOpen: false}, ()=>{ this.props.onAboutChange(this.state.isOpen) }); 24 | } 25 | componentWillReceiveProps(nextProps){ 26 | if(nextProps !== this.props){ 27 | this.props = nextProps; 28 | this.setState({isOpen: nextProps.isOpen}) 29 | } 30 | } 31 | render(){ 32 | return( 33 | 34 |
35 |
36 | logo 37 |

About Flawesome

38 |

Flawesome is a modern productivity tool that will help you organise your day-today work and thoughts.

39 |

Product Version : {this.state.productVersion}

40 |

Electron Version : {this.state.electronVersion}

41 |

Chrome Version : {this.state.chromeVersion}

42 |
© Ashish Bharadwaj J
43 | 44 |
45 | 48 |
49 |
50 |
51 |
52 | ); 53 | } 54 | } -------------------------------------------------------------------------------- /src/components/work-space/modules/Editor.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactQuill, {Quill} from 'react-quill' 3 | import 'react-quill/dist/quill.snow.css' 4 | import '../styles/Editor.css' 5 | import ImageResize from '../../imge-resize/ImageResize' 6 | import {ImageDrop} from 'quill-image-drop-module' 7 | // import QuillBetterTable from 'quill-better-table' 8 | 9 | Quill.register('modules/imageResize', ImageResize) 10 | Quill.register('modules/imageDrop', ImageDrop) 11 | // Quill.register({ 12 | // 'modules/better-table': QuillBetterTable 13 | // }, true) 14 | 15 | class Editor extends React.Component{ 16 | constructor(props){ 17 | super(props) 18 | this.handleChange = this.handleChange.bind(this); 19 | this.state = { 20 | editorContent: props.editorContent 21 | } 22 | } 23 | handleChange(html){ 24 | this.setState({editorContent: html}, ()=>{ this.props.onChangeCallback(html)}); 25 | } 26 | componentWillReceiveProps(nextProps){ 27 | if (nextProps !== this.props){ 28 | this.props = nextProps; 29 | this.setState({editorContent: nextProps.editorContent}) 30 | } 31 | } 32 | render(){ 33 | return( 34 | 41 | ) 42 | } 43 | 44 | } 45 | 46 | Editor.theme = 'snow' 47 | Editor.modules = { 48 | toolbar: [ 49 | [{ 'header': '1'}, {'header': '2'}, { 'font': [] }], 50 | [{size: []}], 51 | ['bold', 'italic', 'underline', 'strike', 'blockquote', 'code-block'], 52 | [{ 'script': 'sub' }, { 'script': 'super' }], 53 | [{'list': 'ordered'}, {'list': 'bullet'}, 54 | {'indent': '-1'}, {'indent': '+1'}], 55 | ['link', 'image', 'video'], 56 | ['clean'] 57 | ], 58 | clipboard: { 59 | // toggle to add extra line breaks when pasting HTML: 60 | matchVisual: false, 61 | }, 62 | imageResize: { 63 | modules: ['Resize', 'DisplaySize', 'Toolbar'], 64 | }, 65 | imageDrop: true, 66 | // table: false, 67 | // 'better-table': { 68 | // operationMenu: { 69 | // items: { 70 | // unmergeCells: { 71 | // text: 'Another unmerge cells name' 72 | // } 73 | // } 74 | // } 75 | // }, 76 | // keyboard: { 77 | // bindings: QuillBetterTable.keyboardBindings 78 | // }, 79 | } 80 | 81 | Editor.formats = [ 82 | 'header', 'font', 'size', 83 | 'bold', 'italic', 'underline', 'strike', 'blockquote', 'code-block', 84 | 'script', 85 | 'list', 'bullet', 'indent', 86 | 'link', 'image', 'video', 87 | 'alt','height','width','style' 88 | ] 89 | 90 | export default Editor; -------------------------------------------------------------------------------- /src/components/work-space/modules/WorkSpace.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Editor from './Editor'; 3 | import ReactStickyNotes from '../../react-sticky-notes'; 4 | import TodoMeter from '../../todo-meter'; 5 | import AboutWindow from './AboutWindow' 6 | import '../styles/WorkSpace.css'; 7 | export default class WorkSpace extends React.Component { 8 | constructor(props){ 9 | super(props); 10 | let defaultState = { 11 | selectedClass: "ab_SB_IconSelected", 12 | icons:[ 13 | { 14 | name: "noteBook", 15 | class: "ab_SB_NoteBook", 16 | isSelected: true 17 | }, 18 | { 19 | name: "stickyNotes", 20 | class: "ab_SB_StickyNotes", 21 | isSelected: false 22 | }, 23 | { 24 | name: "toDo", 25 | class: "ab_SB_TodoList", 26 | isSelected: false 27 | } 28 | ], 29 | noteBook:{ 30 | isSelected: true 31 | }, 32 | stickyNotes:{ 33 | isSelected: false 34 | }, 35 | toDo:{ 36 | isSelected: false 37 | }, 38 | notes: props.appData.notes, 39 | editorContent: props.appData.editorContent, 40 | toDoItems: props.appData.todoState, 41 | isOpen: this.props.appData.isOpen 42 | }; 43 | if(props.appData.defaultTab){ 44 | defaultState.icons = defaultState.icons.map( (i) => { 45 | return (i.name === props.appData.defaultTab ? {...i , isSelected: true} : {...i , isSelected: false}) 46 | }) 47 | defaultState.icons.forEach(a => defaultState[a.name] = {isSelected: a.isSelected}); 48 | } 49 | this.state = defaultState; 50 | this.iconClicked = this.iconClicked.bind(this); 51 | } 52 | componentWillReceiveProps(nextProps){ 53 | if(nextProps !== this.props){ 54 | this.props = nextProps; 55 | this.setState({ 56 | notes: nextProps.appData.notes, 57 | editorContent: nextProps.appData.editorContent, 58 | toDoItems: nextProps.appData.todoState, 59 | isOpen:nextProps.appData.isOpen 60 | }, () => { 61 | this.setState({ 62 | icons: this.state.icons.map(a => a.name === nextProps.appData.defaultTab ? {...a, isSelected: true } : {...a, isSelected: false } ) 63 | }, () => { 64 | this.state.icons.forEach(a => { this.setState( { [a.name]: {isSelected: a.isSelected} } ) }) 65 | }) 66 | }); 67 | } 68 | } 69 | iconClicked(e, icon){ 70 | this.setState({icons: this.state.icons.map(a => a.name === icon.name ? {...a, isSelected: true } : {...a, isSelected: false } )}, () =>{ 71 | this.state.icons.forEach(a => { this.setState( { [a.name]: {isSelected: a.isSelected} } ) }) 72 | this.props.callBacks.updateDefaultTabCallback(icon.name); 73 | }) 74 | } 75 | render(){ 76 | return( 77 |
78 |
79 |
80 | { 81 | this.state.icons.map(a => { 82 | return
{ this.iconClicked(e, a) }}>
83 | }) 84 | } 85 |
86 |
87 |
88 |
89 |
90 | {this.state.noteBook.isSelected ? : null} 91 | {this.state.stickyNotes.isSelected ? { this.setState({notes: newNotes}, ()=>{this.props.callBacks.noteChangeCallback(type, payload, newNotes) }) } } /> : null} 92 | {this.state.toDo.isSelected ? : null } 93 |
94 | 95 |
96 | 97 | ); 98 | } 99 | } -------------------------------------------------------------------------------- /src/components/work-space/styles/About.scss: -------------------------------------------------------------------------------- 1 | @import "./variables.scss"; 2 | @import "~@blueprintjs/core/src/common/react-transition"; 3 | 4 | .ab_AW_logo{ 5 | width: 150px; 6 | height: 150px; 7 | margin-top: -10px; 8 | } 9 | .ab_AW_winContent h4, .ab_AW_winContent h3{ 10 | text-align: center; 11 | } 12 | .ab_AW_copyRight{ 13 | text-align: center; 14 | color: #182026; 15 | font-size: medium; 16 | font-weight: bold; 17 | } 18 | #ab_AW_description{ 19 | text-align: center; 20 | font-size: small; 21 | color: blue; 22 | } 23 | .ab_AW_winContent p{ 24 | color: #999; 25 | text-align: center; 26 | font-size: smaller; 27 | } 28 | .ab_AW_winContent img{ 29 | margin-left: auto; 30 | margin-right: auto; 31 | display: block; 32 | } 33 | .ab_AW_closeButton{ 34 | display: flex; 35 | justify-content: center; 36 | margin-top: 5px; 37 | } 38 | .docs-overlay-example-transition { 39 | 40 | $overlay-example-width: $pt-grid-size * 40; 41 | $enter: ( 42 | transform: (translateY(-50vh) rotate(-10deg), translateY(0) rotate(0deg)) 43 | ); 44 | $leave: ( 45 | transform: (translateY(150vh) rotate(-20deg), translateY(0) rotate(0deg)) 46 | ); 47 | 48 | @include react-transition-phase( 49 | "#{$ns}-overlay", 50 | "enter", 51 | $enter, 52 | $pt-transition-duration * 3, 53 | $pt-transition-ease-bounce, 54 | $before: "&" 55 | ); 56 | @include react-transition-phase( 57 | "#{$ns}-overlay", 58 | "exit", 59 | $leave, 60 | $pt-transition-duration * 5, 61 | $before: "&" 62 | ); 63 | 64 | top: 0; 65 | left: calc(50vw - #{$overlay-example-width / 2}); 66 | margin: 30vh 0; 67 | align-items: center; 68 | justify-content: center; 69 | width: $overlay-example-width; 70 | padding: 10px; 71 | } -------------------------------------------------------------------------------- /src/components/work-space/styles/Editor.css: -------------------------------------------------------------------------------- 1 | .quill{ 2 | height: 100%; 3 | display: grid; 4 | grid-template-rows: 40px auto; 5 | background-color: #fff; 6 | animation: slidedown 1s; 7 | } 8 | .ql-editor{ 9 | background-color: #fff; 10 | } 11 | .ql-toolbar{ 12 | background-color: #f4efef; 13 | position: sticky; 14 | top: 0; 15 | z-index: 10; 16 | } 17 | @keyframes slidedown { 18 | 0% { 19 | opacity: 0; 20 | transform: translateY(-10px); 21 | } 22 | 100% { 23 | opacity: 1; 24 | transform: translateY(0); 25 | } 26 | } -------------------------------------------------------------------------------- /src/components/work-space/styles/WorkSpace.css: -------------------------------------------------------------------------------- 1 | .ab_workSpace{ 2 | background-color: white; 3 | display: grid; 4 | border: 1px solid #000000; 5 | grid-template-columns: 50px auto; 6 | overflow: hidden; 7 | } 8 | .ab_appContainer{ 9 | box-sizing: border-box; 10 | padding: 10px; 11 | background-color: #173341; 12 | overflow: auto; 13 | } 14 | 15 | .ab_appContainer::-webkit-scrollbar { 16 | width: 6px; 17 | height: 6px; 18 | background-color: #F5F5F5; 19 | border-radius: 0.5em; 20 | } 21 | .ab_appContainer::-webkit-scrollbar-thumb { 22 | background-color: rgb(132, 147, 165); 23 | border-radius: 0.5em; 24 | } 25 | .ab_appContainer::-webkit-scrollbar-track { 26 | box-shadow: inset 0 0 6px rgba(0,0,0,0.3); 27 | background-color: #F5F5F5; 28 | border-radius:.5em; 29 | } 30 | /* SideBar styles */ 31 | .ab_sideBar{ 32 | background-color: #182026; 33 | box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25); 34 | display: grid; 35 | grid-template-rows: 210px auto 70px; 36 | } 37 | .ab_SB_icons{ 38 | height: 50px; 39 | cursor: pointer; 40 | margin-bottom: 15px; 41 | display: flex; 42 | flex-direction: column; 43 | } 44 | .ab_SB_IconContainer{ 45 | margin-top: 15px; 46 | } 47 | .ab_SB_NoteBook{ 48 | background: url('../media/notebook.svg'); 49 | background-repeat: no-repeat; 50 | background-position: center center; 51 | background-size: 40px 40px; 52 | 53 | } 54 | .ab_SB_NoteBook:hover{ 55 | filter: brightness(3.5); 56 | } 57 | .ab_SB_StickyNotes{ 58 | background: url('../media/stickyNotes.svg'); 59 | background-repeat: no-repeat; 60 | background-position: center center; 61 | background-size: 40px 40px; 62 | } 63 | .ab_SB_StickyNotes:hover{ 64 | filter: brightness(3.5); 65 | } 66 | .ab_SB_TodoList{ 67 | background: url('../media/checklist.svg'); 68 | background-repeat: no-repeat; 69 | background-position: center center; 70 | background-size: 40px 40px; 71 | } 72 | .ab_SB_TodoList:hover{ 73 | filter: brightness(3.5); 74 | } 75 | .ab_SB_IconSelected{ 76 | border-left: 3px solid #0da3cf; 77 | filter: brightness(3.5) 78 | } 79 | 80 | .ab_SB_SearchIconContainer{ 81 | background: url('../media/search.svg'); 82 | background-repeat: no-repeat; 83 | background-position: center center; 84 | background-size: 40px 40px; 85 | cursor: pointer; 86 | } 87 | .ab_SB_SearchIconContainer:hover{ 88 | filter: brightness(3.5); 89 | } 90 | 91 | /* Sticky Notes Style */ 92 | .rs-notes--navbar__item--button__upload{ 93 | display: none; 94 | } 95 | .rs-notes--navbar{ 96 | animation: slidedown 1s; 97 | } 98 | .rs-notes{ 99 | animation: slidedown 1s; 100 | } 101 | 102 | .ab_TodoMeter{ 103 | animation: slidedown 1s; 104 | } 105 | 106 | @keyframes slidedown { 107 | 0% { 108 | opacity: 0; 109 | transform: translateY(-10px); 110 | } 111 | 100% { 112 | opacity: 1; 113 | transform: translateY(0); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/components/work-space/styles/variables.scss: -------------------------------------------------------------------------------- 1 | $ns: bp3 !default; 2 | $pt-grid-size: 10px !default; 3 | $pt-transition-ease: cubic-bezier(0.4, 1, 0.75, 0.9) !default; 4 | $pt-transition-duration: 100ms !default; 5 | $pt-transition-ease-bounce: cubic-bezier(0.54, 1.12, 0.38, 1.11) !default; -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: sans-serif; 4 | -webkit-font-smoothing: antialiased; 5 | -moz-osx-font-smoothing: grayscale; 6 | } 7 | code { 8 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 9 | monospace; 10 | } 11 | 12 | /* scrollbar styles */ 13 | body::-webkit-scrollbar { 14 | width: 6px; 15 | height: 6px; 16 | background-color: #F5F5F5; 17 | border-radius: 0.5em; 18 | } 19 | body::-webkit-scrollbar-thumb { 20 | background-color: rgb(132, 147, 165); 21 | border-radius: 0.5em; 22 | } 23 | body::-webkit-scrollbar-track { 24 | box-shadow: inset 0 0 6px rgba(0,0,0,0.3); 25 | background-color: #F5F5F5; 26 | border-radius:.5em; 27 | } 28 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | 6 | 7 | // import * as serviceWorker from './serviceWorker'; 8 | 9 | document.getElementById('root').style.height = window.innerHeight; 10 | ReactDOM.render(, document.getElementById('root')); 11 | 12 | 13 | // If you want your app to work offline and load faster, you can change 14 | // unregister() to register() below. Note this comes with some pitfalls. 15 | // Learn more about service workers: https://bit.ly/CRA-PWA 16 | // serviceWorker.unregister(); 17 | -------------------------------------------------------------------------------- /src/serviceWorker.js: -------------------------------------------------------------------------------- 1 | // This optional code is used to register a service worker. 2 | // register() is not called by default. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on subsequent visits to a page, after all the 7 | // existing tabs open on the page have been closed, since previously cached 8 | // resources are updated in the background. 9 | 10 | // To learn more about the benefits of this model and instructions on how to 11 | // opt-in, read https://bit.ly/CRA-PWA 12 | 13 | const isLocalhost = Boolean( 14 | window.location.hostname === 'localhost' || 15 | // [::1] is the IPv6 localhost address. 16 | window.location.hostname === '[::1]' || 17 | // 127.0.0.0/8 are considered localhost for IPv4. 18 | window.location.hostname.match( 19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 20 | ) 21 | ); 22 | 23 | export function register(config) { 24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 25 | // The URL constructor is available in all browsers that support SW. 26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); 27 | if (publicUrl.origin !== window.location.origin) { 28 | // Our service worker won't work if PUBLIC_URL is on a different origin 29 | // from what our page is served on. This might happen if a CDN is used to 30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 31 | return; 32 | } 33 | 34 | window.addEventListener('load', () => { 35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 36 | 37 | if (isLocalhost) { 38 | // This is running on localhost. Let's check if a service worker still exists or not. 39 | checkValidServiceWorker(swUrl, config); 40 | 41 | // Add some additional logging to localhost, pointing developers to the 42 | // service worker/PWA documentation. 43 | navigator.serviceWorker.ready.then(() => { 44 | console.log( 45 | 'This web app is being served cache-first by a service ' + 46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA' 47 | ); 48 | }); 49 | } else { 50 | // Is not localhost. Just register service worker 51 | registerValidSW(swUrl, config); 52 | } 53 | }); 54 | } 55 | } 56 | 57 | function registerValidSW(swUrl, config) { 58 | navigator.serviceWorker 59 | .register(swUrl) 60 | .then(registration => { 61 | registration.onupdatefound = () => { 62 | const installingWorker = registration.installing; 63 | if (installingWorker == null) { 64 | return; 65 | } 66 | installingWorker.onstatechange = () => { 67 | if (installingWorker.state === 'installed') { 68 | if (navigator.serviceWorker.controller) { 69 | // At this point, the updated precached content has been fetched, 70 | // but the previous service worker will still serve the older 71 | // content until all client tabs are closed. 72 | console.log( 73 | 'New content is available and will be used when all ' + 74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' 75 | ); 76 | 77 | // Execute callback 78 | if (config && config.onUpdate) { 79 | config.onUpdate(registration); 80 | } 81 | } else { 82 | // At this point, everything has been precached. 83 | // It's the perfect time to display a 84 | // "Content is cached for offline use." message. 85 | console.log('Content is cached for offline use.'); 86 | 87 | // Execute callback 88 | if (config && config.onSuccess) { 89 | config.onSuccess(registration); 90 | } 91 | } 92 | } 93 | }; 94 | }; 95 | }) 96 | .catch(error => { 97 | console.error('Error during service worker registration:', error); 98 | }); 99 | } 100 | 101 | function checkValidServiceWorker(swUrl, config) { 102 | // Check if the service worker can be found. If it can't reload the page. 103 | fetch(swUrl, { 104 | headers: { 'Service-Worker': 'script' } 105 | }) 106 | .then(response => { 107 | // Ensure service worker exists, and that we really are getting a JS file. 108 | const contentType = response.headers.get('content-type'); 109 | if ( 110 | response.status === 404 || 111 | (contentType != null && contentType.indexOf('javascript') === -1) 112 | ) { 113 | // No service worker found. Probably a different app. Reload the page. 114 | navigator.serviceWorker.ready.then(registration => { 115 | registration.unregister().then(() => { 116 | window.location.reload(); 117 | }); 118 | }); 119 | } else { 120 | // Service worker found. Proceed as normal. 121 | registerValidSW(swUrl, config); 122 | } 123 | }) 124 | .catch(() => { 125 | console.log( 126 | 'No internet connection found. App is running in offline mode.' 127 | ); 128 | }); 129 | } 130 | 131 | export function unregister() { 132 | if ('serviceWorker' in navigator) { 133 | navigator.serviceWorker.ready.then(registration => { 134 | registration.unregister(); 135 | }); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom/extend-expect'; 6 | --------------------------------------------------------------------------------