├── .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 |
7 |
8 |
9 |
10 |
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 |
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 = ''
4 | const IconAlignCenter = ''
5 | const IconAlignRight = ''
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 |
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 |
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 |
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 |
13 |
--------------------------------------------------------------------------------
/src/components/todo-meter/media/arrow.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
--------------------------------------------------------------------------------
/src/components/todo-meter/media/check.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
--------------------------------------------------------------------------------
/src/components/todo-meter/media/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
15 |
--------------------------------------------------------------------------------
/src/components/todo-meter/media/pause.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/components/todo-meter/media/plus.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/components/todo-meter/media/resume.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
13 |
--------------------------------------------------------------------------------
/src/components/todo-meter/media/x.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/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 |
33 | );
34 | }
35 | }
36 |
37 |
--------------------------------------------------------------------------------
/src/components/todo-meter/modules/Item.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styles from "../styles/Item.module.scss";
3 |
4 | // Individual todo item
5 | export default class Item extends React.Component{
6 | constructor(props){
7 | super(props);
8 | this.state = {
9 | item: props.item,
10 | text: props.item.text,
11 | paused: props.item.status === "paused",
12 | completed: props.item.status === "completed"
13 | }
14 | }
15 |
16 | componentWillReceiveProps(nextProps){
17 | if(nextProps !== this.props){
18 | this.props = nextProps;
19 | this.setState({
20 | item: nextProps.item,
21 | text: nextProps.item.text,
22 | paused: nextProps.item.status === "paused",
23 | completed: nextProps.item.status === "completed"
24 | })
25 | }
26 | }
27 |
28 | render(){
29 | return (
30 |
31 |
{this.state.text}
32 |
35 |
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 |

56 |
57 | )}
58 |
59 | {this.state.paused.length > 0 && (
60 |
61 |
62 |
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
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 |
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 |
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 |
94 |
--------------------------------------------------------------------------------
/src/components/work-space/media/search.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
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 |

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 |
--------------------------------------------------------------------------------