-
-
Notifications
You must be signed in to change notification settings - Fork 10
/
renderer.js
323 lines (268 loc) · 9.51 KB
/
renderer.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
const successAudio = new Audio('./assets/sound/success.mp3')
const errorAudio = new Audio('./assets/sound/error.mp3')
const trashAudio = new Audio('./assets/sound/trash.mp3')
const storageLimit = 10
const maxCharsThatCanBeCopied = 500
class Clipper extends React.Component {
constructor(props) {
super(props)
this.state = {
history: [],
interval: -1,
showStorageExceedToast: false,
enableAudio: true,
shouldCapture: true
}
}
playSuccessAudio = () => {
if(this.state.enableAudio) {
successAudio.currentTime = 0
successAudio.play()
}
}
playErrorAudio = () => {
if(this.state.enableAudio) {
errorAudio.currentTime = 0
errorAudio.play()
}
}
playTrashAudio = () => {
if(this.state.enableAudio) {
trashAudio.currentTime = 0
trashAudio.play()
}
}
getNewItemId = () => {
if (this.state.history.length === 0) return 1
return this.state.history[0].id + 1
}
loadHistory = () => {
// Get all history from localstorage
const historyFromLocalstorage = JSON.parse(window.localStorage.getItem('clipper')) || []
return historyFromLocalstorage
}
updateLocalstorage = () => {
// Clear the exisitng histories
window.localStorage.removeItem('clipper')
// Update with new history data
window.localStorage.setItem('clipper', JSON.stringify(this.state.history))
}
setLastCopiedText = (text) => window.localStorage.setItem('clipper:last-copied', text)
getLastCopiedText = () => window.localStorage.getItem('clipper:last-copied')
startClipboardWatch = () => {
// Start listening for new texts
this.state.interval = setInterval(() => {
const text = window.checkClipboard().trim()
if(!this.state.shouldCapture) {
return
}
// Don't process for empty string
if (text.trim() === "") {
return
}
// Check if showStorageExceedToast is set to true or not
if (this.state.showStorageExceedToast) {
// Play error audio
this.playErrorAudio()
// Open the main window if its is closed
window.openMainWindow()
M.toast({ html: 'Your storage limit is exceeded!' })
// reset it to false so that it doesn't repeat again and again
this.setState({ showStorageExceedToast: false })
return
}
// Don't show notification if the same text is being copied
if (this.getLastCopiedText() === text) {
return
}
// Sync with last copied text in storage
this.setLastCopiedText(text)
// Block copying of text which has chars more than maxCharsSpecified
if(text.trim().length > maxCharsThatCanBeCopied) {
// Play error audio
this.playErrorAudio()
// Open the main window if its is closed
window.openMainWindow()
M.toast({ html: `Cannot copy text of characters more than ${maxCharsThatCanBeCopied}` })
return
}
// Limit the max storage limit
if (this.state.history.length >= storageLimit) {
// Set the toast condition to true so that
// it triggers the condition above in next interval
this.setState({ showStorageExceedToast: true })
return
}
// Check if this text is already in the clipboard history
const isAlreadyInHistory = this.state.history
.findIndex(({ text: historyText }) => historyText === text) !== -1
// Check the condition
if (this.state.history.length === 0 || !isAlreadyInHistory) {
// Add this text to history
this.setState({
history: [{
id: this.getNewItemId(),
text
},
...this.state.history]
}, () => {
// Sync with storage
this.updateLocalstorage()
})
this.playSuccessAudio()
}
}, 500)
}
componentWillMount() {
// Get All previously added histories
this.state.history = this.loadHistory()
// Start the watcher
if(this.state.shouldCapture) {
this.startClipboardWatch()
}
}
componentWillUnmount() {
// Clear the interval
clearInterval(this.state.interval)
}
handleTextClick = (e) => {
// Clear the last copied text to avoid conflicts
window.localStorage.removeItem('clipper:last-copied')
const { text } = e.currentTarget.dataset
const remainingHistory = this.state.history.filter(item => item.text !== text)
// Update the state with remaining texts
this.setState({
history: [
...remainingHistory
]
}, () => {
this.updateLocalstorage()
})
window.copyToClipboard(e.currentTarget.dataset.text)
}
handleDeleteSingleText = (e, id) => {
// Stop event propagation to the main li tag
e.stopPropagation()
const remainingHistory = this.state.history.filter(item => item.id !== id)
// Update the state with remaining texts
this.setState({
history: [
...remainingHistory
]
}, () => {
this.updateLocalstorage()
})
}
handleFooterClick = () => {
window.openExternalUrl('https://akashrajpurohit.com')
}
handleModalClick = () => {
const elem = document.querySelector('#settings-modal')
const instance = M.Modal.init(elem)
instance.open()
}
handleAudioSwitch = () => {
this.setState((prevState) => ({ enableAudio: !prevState.enableAudio }));
}
handleClipboardSwitch = () => {
this.setState((prevState) => ({ shouldCapture: !prevState.shouldCapture }));
}
handleClearStorage = () => {
// Play trash audio
this.playTrashAudio()
// Reset the state
this.setState({ history: [] }, () => {
// Reset the storage
this.updateLocalstorage()
})
// Remove the contents from clipboard
window.clearClipboard()
// Clear the last copied text to avoid conflicts
window.localStorage.removeItem('clipper:last-copied')
}
handleAppQuit = () => {
window.quitApp();
}
handleAppMinimize = () => {
window.minimizeApp();
}
render() {
return (
<React.Fragment>
<nav>
<div class="nav-wrapper brown darken-3">
<a class="brand-logo">Clipper 📋</a>
<ul class="right">
<li><a onClick={this.handleAppMinimize}><i class="material-icons">remove</i></a></li>
<li><a onClick={this.handleAppQuit}><i class="material-icons">clear</i></a></li>
</ul>
</div>
</nav>
<div className="container m-tb">
<div className="information">
<h6>Storage Limit: <b>{this.state.history.length} / {storageLimit}</b></h6>
<span onClick={this.handleClearStorage} className="waves-effect waves-light red darken-4 btn"><i class="material-icons right">delete_forever</i>Clear</span>
<span onClick={this.handleModalClick} className="waves-effect waves-light modal-trigger btn" ><i className="material-icons">settings</i></span>
</div>
{this.state.history.length > 0
? <ul className="collection no-border clickable">
{
this.state.history.map(({ id, text }) => {
return (
<div className="collection-item__div">
<li key={id} data-text={text} className="collection-item hoverable m-tb" onClick={this.handleTextClick}><div>{text}</div></li>
<div className="collection-item__options">
<a onClick={(e) => this.handleDeleteSingleText(e, id)} className="secondary-content red-text text-darken-3"><i class="material-icons">delete</i></a>
</div>
</div>
)
})
}
</ul>
: <div className="center-align m-tb30">
<img height="200" width="200" src="./assets/no_data.svg" alt="no data" />
</div>}
<div id="settings-modal" className="modal bottom-sheet">
<div className="modal-content">
<h4>Settings</h4>
<div className="switch">
<span>Enable Audio</span>
<label>
<input type="checkbox" checked={this.state.enableAudio} onChange={this.handleAudioSwitch} />
<span className="lever"></span>
</label>
</div>
<br />
<div className="switch">
<span>Capture Clipboard</span>
<label>
<input type="checkbox" checked={this.state.shouldCapture} onChange={this.handleClipboardSwitch} />
<span className="lever"></span>
</label>
</div>
<br />
<br />
<div className="divider"></div>
<p><b>Note: </b>You can save text of maximum {maxCharsThatCanBeCopied} characters only.</p>
</div>
<div className="modal-footer">
<a href="#!" className="modal-close waves-effect waves-green btn-flat">Close</a>
</div>
</div>
<div className="footer-copyright">
© {
new Date().getFullYear() == "2020"
? "2020"
: "2020 - " + new Date().getFullYear()
} • Clipper
<span className="black-text text-darken-4 right clickable" onClick={this.handleFooterClick}>With 💖 Akash Rajpurohit</span>
</div>
</div>
</React.Fragment>
);
}
}
ReactDOM.render(
<Clipper />,
document.getElementById('root')
)