Compare commits

...

24 Commits

Author SHA1 Message Date
Nathan Parikh
fa54efcec6 Updates for windows and mac builds 2020-06-13 11:29:37 -05:00
Nathan Parikh
53bd6af536 Merge branch 'master' of https://github.com/nathanp/crypto-price-widget 2020-06-13 01:58:58 -05:00
Nathan Parikh
073a483bf2 v1.4.0 - updating packages, coin list, etc. 2020-06-13 01:58:27 -05:00
Nathan Parikh
af39424d7d
Updated README
updated donation addresses
2020-02-21 15:30:28 -06:00
Nathan Parikh
4b795fb375 Version 1.2.0 2017-10-16 13:33:47 -05:00
Nathan Parikh
5112fc8e8d UI updates - new icons 2017-10-13 10:14:37 -05:00
Nathan Parikh
1f381d4c25 more UI/UX improvements. Starting to test price notifications/alerts! 2017-10-12 23:55:20 -05:00
Nathan Parikh
ac6d6861bf UI improvements 2017-10-12 22:23:40 -05:00
Nathan Parikh
649a26299c code cleanup 2017-10-11 09:22:46 -05:00
Nathan Parikh
c0eb800259 The adding and removing of coins is much more smooth now. The app doesn't have to do a full reload. All new coins added are placed at the bottom. Still need to add/remove them from the portfolio screen. Code optimizations. 2017-10-11 09:18:22 -05:00
Nathan Parikh
e4bc240bf2 fonts are now local to help speed up the app load time 2017-10-11 07:52:31 -05:00
Nathan Parikh
03fe34fb08 bug fixes and added an error screen if no internet connection present 2017-10-10 16:26:35 -05:00
nathanp
d7f9c9d2d4 MacOS 1.10 2017-09-16 15:11:21 -05:00
Nathan Parikh
b34ab0f32c Windows 1.1.0 build 2017-09-16 00:21:29 -05:00
Nathan Parikh
276a52f87c 1.1.0 - Windows build. Also removed old version from repo 2017-09-16 00:01:21 -05:00
Nathan Parikh
7f0829f8ad version 1.1.0 - code only, no build yet 2017-09-15 23:57:03 -05:00
Nathan Parikh
386deb7a10 added pin to top functionality 2017-09-15 23:15:29 -05:00
Nathan Parikh
0f1b2b3d86 more portfolio updates 2017-09-13 10:03:34 -05:00
Nathan Parikh
6da0f8f5bd Added icons for main and portfolio screens. More portfolio updates. Portfolio coin order now matches main screen order. 2017-09-12 23:32:55 -05:00
Nathan Parikh
1207f6a09d Added working doughnut chart to portfolio 2017-09-12 00:22:21 -05:00
Nathan Parikh
0f92fb17f5 Added working portfolio 2017-09-11 23:26:20 -05:00
Nathan Parikh
3a8dbd63f0 Separated markup, logic, and styling. Also fixed issue #3 2017-09-11 16:17:34 -05:00
Nathan Parikh
8684427f76 Finalized macOS 1.0.0 build 2017-09-10 22:39:36 -05:00
Nathan Parikh
afb1836e1d Adding Windows 1.0.0 package 2017-09-08 23:13:24 -05:00
33 changed files with 9034 additions and 6721 deletions

3
.gitignore vendored
View File

@ -1,3 +1,4 @@
node_modules
release-builds
out/
*.code-workspace

View File

@ -23,12 +23,14 @@ Crypto Price Widget is an open source project created by [Nathan Parikh](https:/
## Donate
Crypto Price Widget is an open source side project. To support development and keep the project running, you can donate using Bitcoin, Ethereum, Litecoin, or Doge:
Crypto Price Widget is an open source side project. To support development and keep the project running, you can donate using one of the below options:
- Bitcoin: `17iENfaJkEpxGXW7mgdFh9hGMZV65R2zVL`
- Ethereum: `0x68b99868700b33A248de4A62a038a9e3b03DCA21`
- Litecoin: `La8eCVjzLq8zrJV3LgyU6WtnyQnjs76LFY`
- Doge: `DFHBdwUbcvGezfgHHbWmH8eLWjAjUhFSZ2`
- PayPal: https://www.paypal.me/nathanp
- CashApp: https://cash.app/$ndzynes
- Bitcoin: `bc1qkzrkkhmufjuyslh92mfne5yfe6trhf2u258wl4`
- Ethereum: `0x0606405c03F381EF187C413438E7efE705ec64AA`
- Litecoin: `LNBBr1iutwMCTfGcPps2Qhg66vSkAbZWhE`
- Doge: `DG1twgxAJa4Tj42e4aoHVnh654Ro8ftDGi`
## FAQ
@ -36,10 +38,6 @@ Crypto Price Widget is an open source side project. To support development and k
No, Crypto Price Widget is completely client-side and doesn't hold any keys. All code is open source.
**How will you make money?**
I won't, but donations are welcome :)
**When can I have a Linux version?**
Linux coming soon!

1
coinlist.json Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

1579
css/animate.css vendored Normal file

File diff suppressed because it is too large Load Diff

505
css/app.css Normal file
View File

@ -0,0 +1,505 @@
/*Fonts*/
@font-face {
font-family: 'heeboregular';
src: url('../fonts/heebo-regular-webfont.woff2') format('woff2'),
url('../fonts/heebo-regular-webfont.woff') format('woff');
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: 'heebothin';
src: url('../fonts/heebo-thin-webfont.woff2') format('woff2'),
url('../fonts/heebo-thin-webfont.woff') format('woff');
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: 'inconsolataregular';
src: url('../fonts/inconsolata-regular-webfont.woff2') format('woff2'),
url('../fonts/inconsolata-regular-webfont.woff') format('woff');
font-weight: normal;
font-style: normal;
}
body,
button,
#myInput,
#saveCoins,
#saveQuantities {
font-family: 'inconsolataregular', monospace;
}
.coin-list li .sym {
font-family: 'heeboregular', sans-serif;
}
body {
background: rgba(0, 0, 0, 0.95);
color: #fff;
margin-top: 35px;
}
ul {
margin: 0;
padding: 0;
}
img {
max-width: 100%;
}
.titlebar {
-webkit-user-select: none;
-webkit-app-region: drag;
opacity: 0;
position: fixed;
width: 94%;
z-index: 100;
background: rgba(0, 0, 0, 0.95);
top: 0;
left: 0;
padding: 8px 3% 3px;
}
.titlebar:hover {
opacity: 1;
}
.titlebar .controls {
float: right;
line-height: 0;
}
button {
-webkit-app-region: no-drag;
background-color: #000000;
color: #fff;
border: 1px solid #252525;
padding: 5px 10px;
margin: -1px 0px 0px 0px;
}
header button {
background: none;
border: none;
outline: none;
}
#close-btn,
#min-btn {
height: 12px;
width: 12px;
background: none;
border: 2px solid #000;
-moz-border-radius: 50px;
-webkit-border-radius: 50px;
border-radius: 50px;
padding: 0;
-webkit-transition: all 150ms ease;
-moz-transition: all 150ms ease;
-ms-transition: all 150ms ease;
-o-transition: all 150ms ease;
transition: all 150ms ease;
}
#close-btn {
border-color: #ff2626;
}
#close-btn:hover {
background: #ff2626;
}
#min-btn {
border-color: #ffbd45;
}
#min-btn:hover {
background: #ffbd45;
}
.tabs button {
padding: 0;
}
.tabs button,
.tabs button.active {
display: inline-block;
vertical-align: top;
}
.tabs button img {
width: 16px;
height: 16px;
opacity: 0.5;
-webkit-transition: all 150ms ease;
-moz-transition: all 150ms ease;
-ms-transition: all 150ms ease;
-o-transition: all 150ms ease;
transition: all 150ms ease;
}
.tabs button:hover img,
.tabs button.active img {
opacity: 1;
}
#portfolio-btn, #main-btn {
color: #fff;
}
.panel {
display: none;
}
ul {
list-style-type: none;
}
.coin-list {
margin: 0;
padding: 0;
font-size: 28px;
font-weight: 400;
}
#portfolio-list.coin-list {
font-size: 16px;
padding-top: 9px;
}
.coin-list li {
margin: 0px 0px 15px 0px;
padding: 0px 0px 15px 0px;
border-bottom: 1px solid #252525;
}
#portfolio-list.coin-list li {
margin-bottom: 10px;
padding-bottom: 0px;
}
.coin-list li:last-child {
border-bottom: none;
}
.coin-list li span {
}
.coin-list li span.draggable {
width: 100%;
}
.coin-list input[type="number"] {
border: none;
padding: 5px;
background: rgba(0, 0, 0, 0);
color: #fff;
max-width: 85px;
display: inline-block;
outline: none;
}
.coin-list .block {
display: inline-block;
vertical-align: top;
}
.coin-list .block label {
font-size: 12px;
display: block;
/*background: rgba(255, 255, 255, 0.1);*/
padding: 1px 2px 2px;
}
.coin-list .block .quantity-value {
color: #b2ff93;
}
.coin-list li .sym {
border: 1px solid #252525;
border-left-width: 2px;
padding: 0px 5px 0px;
font-size: 12px;
}
.coin-list li .sym:hover {
cursor: -webkit-grab;
border-top-color: #4c4c4c;
border-right-color: #4c4c4c;
border-bottom-color: #4c4c4c;
-webkit-transition: all 250ms ease;
-moz-transition: all 250ms ease;
-ms-transition: all 250ms ease;
-o-transition: all 250ms ease;
transition: all 250ms ease;
}
/*Symbol Colors*/
.coin-list li#coin-BTC .sym {
border-left-color: #F9A847;
}
.coin-list li#coin-XRP .sym {
border-left-color: #0997D2;
}
.coin-list li#coin-LTC .sym {
border-left-color: #F1F1F1;
}
.coin-list li#coin-NEO .sym,
.coin-list li#coin-GAS .sym {
border-left-color: #9CD115;
}
.coin-list li#coin-OMG .sym {
border-left-color: #1A53F0;
}
.coin-list li#coin-BCH .sym {
border-left-color: #F7931A;
}
.coin-list li#coin-DASH .sym {
border-left-color: #0475B6;
}
.coin-list li#coin-XMR .sym {
border-left-color: #FF6600;
}
.coin-list li#coin-ETC .sym {
border-left-color: #689274;
}
.coin-list li#coin-ZEC .sym {
border-left-color: #EFB948;
}
.coin-list li#coin-GNT .sym {
border-left-color: #00AFBF;
}
.coin-list li#coin-BAT .sym {
border-left-color: #662F92;
}
.coin-list li#coin-FCT .sym {
border-left-color: #E3A77D;
}
.coin-list li#coin-ARK .sym {
border-left-color: #CB0101;
}
.coin-list li#coin-DOGE .sym {
border-left-color: #BBA034;
}
.coin-list li#coin-CVC .sym {
border-left-color: #41BB2E;
}
.coin-list li#coin-MCO .sym {
border-left-color: #82344C;
}
.coin-list li#coin-UBQ .sym {
border-left-color: #00EA90;
}
.coin-list li#coin-DNT .sym {
border-left-color: #7CF7FA;
}
.coin-list li .change {
padding: 2px 3px 2px;
font-size: 14px;
float: right;
margin: 10px 0px 0px;
background: #000;
}
.coin-list li .change.positive {
color: #b2ff93;
}
.coin-list li .change.negative {
color: #ff6765;
}
.active {
display: block;
}
.inactive {
display: none;
}
/*Settings Page*/
#settings h3:first-child {
margin-top: 5px;
}
#myInput {
border: none;
padding: 0px 0px 10px;
font-size: 14px;
background: rgba(0, 0, 0, 0);
color: #fff;
outline: none;
border-bottom: 1px solid #252525;
display: block;
width: 100%;
}
#saveCoins,
#saveQuantities {
background-color: #000000;
color: #fff;
border: 1px solid #252525;
padding: 5px 10px;
margin: -1px 0px 0px 0px;
}
#coinlist {
margin: 15px 0px 0px 0px;
padding: 0;
max-height: 218px;
overflow-y: scroll;
}
#coinlist li {
position: relative;
margin: 0px 0px 5px 0px;
}
/* Custom checkboxes inspired by https://codepen.io/sderoij/pen/VvJJwE */
.checkbox-wrapper {
position: relative;
display: block;
margin: 15px 0px 0px;
}
#coinlist label,
.checkbox-styled-label {
height: 18px;
z-index: 0;
display: inline-block;
position: absolute;
top: 0;
left: 0;
text-indent: 24px;
overflow: hidden;
}
#coinlist label {
cursor: pointer;
}
#coinlist label div,
.checkbox-styled-label div {
height: 12px;
width: 12px;
border: solid 2px rgba(255, 255, 255, 0.6);
margin: 0;
border-radius: 50%;
transform: rotate(45deg);
transition: all 0ms ease-in-out, border 0ms ease 0ms;
position: absolute;
top: 0;
-webkit-transition: all 250ms ease;
-moz-transition: all 250ms ease;
-ms-transition: all 250ms ease;
-o-transition: all 250ms ease;
transition: all 250ms ease;
}
#coinlist input:hover + label div,
.checkbox-styled:hover + label div {
border-color: rgba(138, 255, 131, 0.9);
}
#coinlist input,
.checkbox-styled {
height: auto;
width: 18px;
margin: 0;
opacity: 0;
z-index: 1;
position: relative;
cursor: pointer;
}
#coinlist input:checked + label > div,
.checkbox-styled:checked + label > div {
border-radius: 0;
border-top: 0;
border-left: 0;
border-color: rgba(138, 255, 131, 0.9);
height: 15px;
width: 12px;
margin-top: -4px;
margin-left: 0px;
transform: rotate(40deg);
transition: all 0ms ease-in-out;
}
.checkbox-styled:checked + label > div {
width: 8px;
margin-left: 4px;
}
#tips {
font-size: 12px;
}
#tips li {
}
.creds {
font-size: 8px;
color: #252525;
margin: 15px 0px 0px 0px;
}
.creds a {
text-decoration: none;
color: #252525;
}
/*Portfolio*/
#portfolio-total-value {
text-align: center;
font-size: 1.5em;
font-weight: 600;
margin: 15px 0px;
}
#portfolioChart {
max-height: 480px;
max-width: 100%;
}
/*Scrollbar*/
::-webkit-scrollbar-corner {
background-color: #000;
}
::-webkit-scrollbar {
background-color: rgba(0, 0, 0, 100);
}
::-webkit-scrollbar {
width: .5em;
height: .5em;
}
::-webkit-scrollbar-thumb:window-inactive,
::-webkit-scrollbar-thumb {
background-color: #252525;
-webkit-border-radius: 100px;
}
::-webkit-scrollbar-thumb:hover {
background-color: #4c4c4c;
}
/*Select Boxes*/
.custom-select {
position: relative;
display: inline-block;
}
.custom-select select {
display: inline-block;
border: 1px solid #252525;
padding: 4px 3px 3px 5px;
margin: 0;
font: inherit;
outline: none;
line-height: 1.2;
background: #000000;
-webkit-appearance: none;
width: 145px;
color: #fff;
}
/* for Webkit's CSS-only solution */
@media screen and (-webkit-min-device-pixel-ratio:0) {
.custom-select select {
padding-right:30px;
}
}
/* Since we removed the default focus styles, we have to add our own */
.custom-select select:focus {
-webkit-box-shadow: 0 0 3px 1px #00afc1;
-moz-box-shadow: 0 0 3px 1px #00afc1;
box-shadow: 0 0 3px 1px #00afc1;
}
/* Select arrow styling */
.custom-select:after {
content: "▼";
position: absolute;
top: 0;
right: 0;
bottom: 0;
font-size: 60%;
line-height: 30px;
padding: 0 7px;
background: #252525;
color: white;
pointer-events: none;
}
/*Offline*/
.error {
text-align: center;
}
.error h2,
.error h4 {
font-family: Arial,Helvetica,sans-serif;
}
.error button.refresh {
cursor: pointer;
background: #41BB2E;
border: none;
color: #000;
font-weight: bold;
}

View File

@ -0,0 +1,108 @@
/* line 6, ../sass/_content.sass */
.offline-ui .offline-ui-retry:before {
content: "Reconnect";
}
/* line 11, ../sass/_content.sass */
.offline-ui.offline-ui-up .offline-ui-content:before {
content: "Your computer is connected to the internet.";
}
@media (max-width: 1024px) {
/* line 11, ../sass/_content.sass */
.offline-ui.offline-ui-up .offline-ui-content:before {
content: "Your device is connected to the internet.";
}
}
@media (max-width: 568px) {
/* line 11, ../sass/_content.sass */
.offline-ui.offline-ui-up .offline-ui-content:before {
content: "Your device is connected.";
}
}
/* line 22, ../sass/_content.sass */
.offline-ui.offline-ui-down .offline-ui-content:before {
content: "Your computer lost its internet connection.";
}
@media (max-width: 1024px) {
/* line 22, ../sass/_content.sass */
.offline-ui.offline-ui-down .offline-ui-content:before {
content: "Your device lost its internet connection.";
}
}
@media (max-width: 568px) {
/* line 22, ../sass/_content.sass */
.offline-ui.offline-ui-down .offline-ui-content:before {
content: "Your device isn't connected.";
}
}
/* line 33, ../sass/_content.sass */
.offline-ui.offline-ui-down.offline-ui-connecting .offline-ui-content:before, .offline-ui.offline-ui-down.offline-ui-connecting-2s .offline-ui-content:before {
content: "Attempting to reconnect...";
}
/* line 42, ../sass/_content.sass */
.offline-ui.offline-ui-down.offline-ui-waiting .offline-ui-content[data-retry-in-unit="second"]:before {
content: "Connection lost. Reconnecting in " attr(data-retry-in-value) " seconds...";
}
@media (max-width: 568px) {
/* line 42, ../sass/_content.sass */
.offline-ui.offline-ui-down.offline-ui-waiting .offline-ui-content[data-retry-in-unit="second"]:before {
content: "Reconnecting in " attr(data-retry-in-value) "s...";
}
}
/* line 50, ../sass/_content.sass */
.offline-ui.offline-ui-down.offline-ui-waiting .offline-ui-content[data-retry-in-unit="second"][data-retry-in-value="1"]:before {
content: "Connection lost. Reconnecting in " attr(data-retry-in-value) " second...";
}
@media (max-width: 568px) {
/* line 50, ../sass/_content.sass */
.offline-ui.offline-ui-down.offline-ui-waiting .offline-ui-content[data-retry-in-unit="second"][data-retry-in-value="1"]:before {
content: "Reconnecting in " attr(data-retry-in-value) "s...";
}
}
/* line 58, ../sass/_content.sass */
.offline-ui.offline-ui-down.offline-ui-waiting .offline-ui-content[data-retry-in-unit="minute"]:before {
content: "Connection lost. Reconnecting in " attr(data-retry-in-value) " minutes...";
}
@media (max-width: 568px) {
/* line 58, ../sass/_content.sass */
.offline-ui.offline-ui-down.offline-ui-waiting .offline-ui-content[data-retry-in-unit="minute"]:before {
content: "Reconnecting in " attr(data-retry-in-value) "m...";
}
}
/* line 66, ../sass/_content.sass */
.offline-ui.offline-ui-down.offline-ui-waiting .offline-ui-content[data-retry-in-unit="minute"][data-retry-in-value="1"]:before {
content: "Connection lost. Reconnecting in " attr(data-retry-in-value) " minute...";
}
@media (max-width: 568px) {
/* line 66, ../sass/_content.sass */
.offline-ui.offline-ui-down.offline-ui-waiting .offline-ui-content[data-retry-in-unit="minute"][data-retry-in-value="1"]:before {
content: "Reconnecting in " attr(data-retry-in-value) "m...";
}
}
/* line 74, ../sass/_content.sass */
.offline-ui.offline-ui-down.offline-ui-waiting .offline-ui-content[data-retry-in-unit="hour"]:before {
content: "Connection lost. Reconnecting in " attr(data-retry-in-value) " hours...";
}
@media (max-width: 568px) {
/* line 74, ../sass/_content.sass */
.offline-ui.offline-ui-down.offline-ui-waiting .offline-ui-content[data-retry-in-unit="hour"]:before {
content: "Reconnecting in " attr(data-retry-in-value) "h...";
}
}
/* line 82, ../sass/_content.sass */
.offline-ui.offline-ui-down.offline-ui-waiting .offline-ui-content[data-retry-in-unit="hour"][data-retry-in-value="1"]:before {
content: "Connection lost. Reconnecting in " attr(data-retry-in-value) " hour...";
}
@media (max-width: 568px) {
/* line 82, ../sass/_content.sass */
.offline-ui.offline-ui-down.offline-ui-waiting .offline-ui-content[data-retry-in-unit="hour"][data-retry-in-value="1"]:before {
content: "Reconnecting in " attr(data-retry-in-value) "h...";
}
}
/* line 90, ../sass/_content.sass */
.offline-ui.offline-ui-down.offline-ui-reconnect-failed-2s.offline-ui-waiting .offline-ui-retry {
display: none;
}
/* line 93, ../sass/_content.sass */
.offline-ui.offline-ui-down.offline-ui-reconnect-failed-2s .offline-ui-content:before {
content: "Connection attempt failed.";
}

View File

@ -0,0 +1,75 @@
/* line 3, ../sass/_offline-theme-base-indicator.sass */
.offline-ui, .offline-ui *, .offline-ui:before, .offline-ui:after, .offline-ui *:before, .offline-ui *:after {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
/* line 6, ../sass/_offline-theme-base-indicator.sass */
.offline-ui {
display: none;
position: fixed;
background: white;
z-index: 2000;
display: inline-block;
}
/* line 13, ../sass/_offline-theme-base-indicator.sass */
.offline-ui .offline-ui-retry {
display: none;
}
/* line 16, ../sass/_offline-theme-base-indicator.sass */
.offline-ui.offline-ui-up {
display: block;
}
/* line 19, ../sass/_offline-theme-base-indicator.sass */
.offline-ui.offline-ui-down {
display: block;
}
/* line 8, ../sass/offline-theme-dark-indicator.sass */
.offline-ui {
-webkit-box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.15);
-moz-box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.15);
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.15);
-webkit-border-radius: 4px 4px 0 0;
-moz-border-radius: 4px 4px 0 0;
-ms-border-radius: 4px 4px 0 0;
-o-border-radius: 4px 4px 0 0;
border-radius: 4px 4px 0 0;
font-family: "Helvetica Neue", sans-serif;
font-weight: 300;
padding: 1em;
background: black;
color: #cccccc;
bottom: 0;
left: 20px;
}
/* line 19, ../sass/offline-theme-dark-indicator.sass */
.offline-ui .offline-ui-content {
padding-left: 1.5em;
}
/* line 22, ../sass/offline-theme-dark-indicator.sass */
.offline-ui .offline-ui-content:after {
-webkit-border-radius: 50%;
-moz-border-radius: 50%;
-ms-border-radius: 50%;
-o-border-radius: 50%;
border-radius: 50%;
content: " ";
display: block;
position: absolute;
top: 0;
bottom: 0;
left: 1em;
margin: auto;
height: 0.8em;
width: 0.8em;
}
/* line 36, ../sass/offline-theme-dark-indicator.sass */
.offline-ui.offline-ui-up .offline-ui-content:after {
background: #80d580;
}
/* line 41, ../sass/offline-theme-dark-indicator.sass */
.offline-ui.offline-ui-down .offline-ui-content:after {
background: #e24949;
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
images/appbar.alert.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 982 B

BIN
images/appbar.pie.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
images/appbar.settings.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
images/appbar.stock.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
images/icons8-Home-64.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 600 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

BIN
images/offline_doge.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

View File

@ -2,348 +2,8 @@
<html>
<head>
<meta charset="UTF-8">
<title>Latest Crypto Prices</title>
<link href="https://fonts.googleapis.com/css?family=Heebo:100,400" rel="stylesheet">
<link href="https://fonts.googleapis.com/css?family=Inconsolata" rel="stylesheet">
<style type="text/css">
/*@import url('https://rsms.me/interface/interface.css');*/
body {
background: rgba(0, 0, 0, 0.95);
font-family: 'Inconsolata', monospace;
color: #fff;
margin-top: 5px;
}
ul {
margin: 0;
padding: 0;
}
.titlebar {
-webkit-user-select: none;
-webkit-app-region: drag;
opacity: 0;
}
.titlebar:hover {
opacity: 1;
}
.titlebar .controls {
float: right;
line-height: 0;
}
button {
-webkit-app-region: no-drag;
background: none;
border: none;
outline: none;
}
#close-btn,
#min-btn {
height: 12px;
width: 12px;
background: none;
border: 2px solid #000;
-moz-border-radius: 50px;
-webkit-border-radius: 50px;
border-radius: 50px;
padding: 0;
}
#close-btn {
border-color: #ff2626;
}
#close-btn:hover {
background: #ff2626;
}
#min-btn {
border-color: #ffbd45;
}
#min-btn:hover {
background: #ffbd45;
}
#settings-btn {
padding: 0;
}
#settings-btn img {
width: 24px;
opacity: 0.5;
}
#settings-btn:hover img {
opacity: 1;
}
ul {
list-style-type: none;
}
#prices {
margin: 0;
padding: 0;
font-size: 28px;
font-weight: 400;
}
#prices li {
margin: 0px 0px 15px 0px;
padding: 0px 0px 15px 0px;
border-bottom: 1px solid #252525;
}
#prices li:last-child {
border-bottom: none;
}
#prices li span {
}
#prices li span.draggable {
width: 100%;
}
#prices li .sym {
border: 1px solid #252525;
border-left-width: 2px;
padding: 0px 5px 0px;
font-family: 'Heebo', sans-serif;
font-size: 12px;
}
#prices li .sym:hover {
cursor: -webkit-grab;
}
/*Symbol Colors*/
#prices li#coin-BTC .sym {
border-left-color: #F9A847;
}
#prices li#coin-XRP .sym {
border-left-color: #0997D2;
}
#prices li#coin-LTC .sym {
border-left-color: #F1F1F1;
}
#prices li#coin-NEO .sym,
#prices li#coin-GAS .sym {
border-left-color: #9CD115;
}
#prices li#coin-OMG .sym {
border-left-color: #1A53F0;
}
#prices li#coin-BCH .sym {
border-left-color: #F7931A;
}
#prices li#coin-DASH .sym {
border-left-color: #0475B6;
}
#prices li#coin-XMR .sym {
border-left-color: #FF6600;
}
#prices li#coin-ETC .sym {
border-left-color: #689274;
}
#prices li#coin-ZEC .sym {
border-left-color: #EFB948;
}
#prices li#coin-GNT .sym {
border-left-color: #00AFBF;
}
#prices li#coin-BAT .sym {
border-left-color: #662F92;
}
#prices li#coin-FCT .sym {
border-left-color: #E3A77D;
}
#prices li#coin-ARK .sym {
border-left-color: #CB0101;
}
#prices li#coin-DOGE .sym {
border-left-color: #BBA034;
}
#prices li#coin-CVC .sym {
border-left-color: #41BB2E;
}
#prices li#coin-MCO .sym {
border-left-color: #82344C;
}
#prices li#coin-UBQ .sym {
border-left-color: #00EA90;
}
#prices li#coin-DNT .sym {
border-left-color: #7CF7FA;
}
#prices li .change {
padding: 2px 3px 2px;
font-size: 12px;
float: right;
margin: 10px 0px 0px;
background: #000;
}
#prices li .change.positive {
color: #b2ff93;
}
#prices li .change.negative {
color: #ff6765;
}
.active {
display: block;
}
.inactive {
display: none;
}
/*Settings Page*/
#settings h3:first-child {
margin-top: 5px;
}
#myInput {
font-family: 'Inconsolata', monospace;
border: none;
padding: 0px 0px 10px;
font-size: 14px;
background: rgba(0, 0, 0, 0);
color: #fff;
outline: none;
border-bottom: 1px solid #252525;
display: block;
width: 100%;
}
#saveCoins {
background-color: #000000;
color: #fff;
border: 1px solid #252525;
padding: 5px 10px;
margin: -1px 0px 0px 0px;
font-family: 'Inconsolata', monospace;
}
#coinlist {
margin: 15px 0px 0px 0px;
padding: 0;
max-height: 218px;
overflow-y: scroll;
}
#coinlist li {
position: relative;
margin: 0px 0px 5px 0px;
}
/* Custom checkboxes inspired by https://codepen.io/sderoij/pen/VvJJwE */
#coinlist label {
height: auto;
width: 100%;
z-index: 0;
display: inline-block;
position: absolute;
top: 0;
left: 0;
text-indent: 24px;
}
#coinlist label div {
height: 12px;
width: 12px;
border: solid 2px rgba(255, 255, 255, 0.6);
margin: 0;
border-radius: 50%;
transform: rotate(45deg);
transition: all 0ms ease-in-out, border 0ms ease 0ms;
position: absolute;
top: 0;
}
#coinlist input:hover + label div {
border-color: rgba(138, 255, 131, 0.9);
}
#coinlist input {
height: auto;
width: 18px;
margin: 0;
opacity: 0;
z-index: 1;
position: relative;
cursor: pointer;
}
#coinlist input:checked + label > div {
border-radius: 0;
border-top: 0;
border-left: 0;
border-color: rgba(138, 255, 131, 0.9);
height: 15px;
width: 12px;
margin-top: -4px;
margin-left: 0px;
transform: rotate(40deg);
transition: all 0ms ease-in-out;
}
#tips {
font-size: 12px;
}
#tips li {
}
.creds {
font-size: 8px;
color: #252525;
margin: 15px 0px 0px 0px;
}
.creds a {
text-decoration: none;
color: #252525;
}
/*Scrollbar*/
::-webkit-scrollbar-corner {
background-color: #000;
}
::-webkit-scrollbar {
background-color: rgba(0, 0, 0, 100);
}
::-webkit-scrollbar {
width: .5em;
height: .5em;
}
::-webkit-scrollbar-thumb:window-inactive,
::-webkit-scrollbar-thumb {
background: #252525;
-webkit-border-radius: 100px;
}
/*Select Boxes*/
.custom-select {
position: relative;
display: inline-block;
}
.custom-select select {
display: inline-block;
border: 1px solid #252525;
padding: 4px 3px 3px 5px;
margin: 0;
font: inherit;
outline: none;
line-height: 1.2;
background: #000000;
-webkit-appearance: none;
width: 145px;
color: #fff;
}
/* for Webkit's CSS-only solution */
@media screen and (-webkit-min-device-pixel-ratio:0) {
.custom-select select {
padding-right:30px;
}
}
/* Since we removed the default focus styles, we have to add our own */
.custom-select select:focus {
-webkit-box-shadow: 0 0 3px 1px #00afc1;
-moz-box-shadow: 0 0 3px 1px #00afc1;
box-shadow: 0 0 3px 1px #00afc1;
}
/* Select arrow styling */
.custom-select:after {
content: "▼";
position: absolute;
top: 0;
right: 0;
bottom: 0;
font-size: 60%;
line-height: 30px;
padding: 0 7px;
background: #252525;
color: white;
pointer-events: none;
}
</style>
<title>Crypto Price Widget</title>
<link href="css/app.css" rel="stylesheet">
</head>
<body>
@ -351,16 +11,20 @@
<div class="controls">
<button id="min-btn"></button>
<button id="close-btn"></button>
</div>
<button id="settings-btn" onclick="toggleSettings()"><img src="images/icons8-Settings.png"></button>
</div><!-- .controls -->
<div class="tabs">
<button id="main-btn" href="#main"><img src="images/appbar.stock.png"></button>
<button id="portfolio-btn" href="#portfolio"><img src="images/appbar.pie.png"></button>
<button id="settings-btn" href="#settings"><img src="images/appbar.settings.png"></button>
</div><!-- .tabs -->
</header>
<div id="main" class="panel active">
<ul id="prices">
<ul id="prices" class="coin-list">
</ul>
</div><!-- #main -->
<div id="settings" class="panel inactive">
<div id="settings" class="panel">
<h3>Choose Your Coins</h3>
<div id="coinsearch">
@ -370,7 +34,6 @@
</ul>
</div><!-- #coinsearch -->
<!-- need to figure out how to save currency selection on close/open -->
<h3>Choose Your Base Currency</h3>
<label class="custom-select">
<select id="base" onchange="setBase()">
@ -398,6 +61,14 @@
</select>
</label>
<div class="checkbox-wrapper">
<input id="pin-to-top" class="checkbox-styled" type="checkbox" name="pin-to-top">
<label class="checkbox-styled-label">
Window always on top?
<div></div>
</label>
</div>
<h3>Tip Jar</h3>
<ul id="tips">
<li>BTC: 17iENfaJkEpxGXW7mgdFh9hGMZV65R2zVL</li>
@ -406,375 +77,38 @@
<li>DOGE: DFHBdwUbcvGezfgHHbWmH8eLWjAjUhFSZ2</li>
</ul>
<div class="creds">
<div>Icons made by <a href="https://www.flaticon.com/authors/madebyoliver" title="Madebyoliver">Madebyoliver</a> from <a href="https://www.flaticon.com/" title="Flaticon">www.flaticon.com</a> is licensed by <a href="http://creativecommons.org/licenses/by/3.0/" title="Creative Commons BY 3.0" target="_blank">CC 3.0 BY</a></div>
</div>
<h4>Build Info</h4>
node <script>document.write(process.versions.node)</script>,
Chrome <script>document.write(process.versions.chrome)</script>,
and Electron <script>document.write(process.versions.electron)</script>.
</div><!-- #settings -->
<div id="portfolio" class="panel">
<div class="chart">
</div>
<ul id="portfolio-list" class="coin-list">
</ul>
<button type="button" id="saveQuantities">Save</button>
<div id="portfolio-total-value">Total Value: <span class="value"></span></div>
<!-- show what % each coin is of portfolio -->
<!-- Enter avg. purchase price for each coin -->
<!-- See % gain/loss -->
</div><!-- #portfolio -->
</body>
<script>
// You can also require other files to run in this process
require('./renderer.js')
</script>
<script>
/******************
* APP FUNCTIONALITY
******************/
//user settings
const settings = require('electron-settings');
settings.set('developer', {
first: 'Nathan',
last: 'Parikh'
});
//default coins
if(settings.has('user.coins')) {
//do nothing because coins already set
}
else {
settings.set('user', {
coins: 'BTC,ETH,LTC'
});
}
//default base currency
if(settings.has('user.currency')) {
//do nothing because currency already set
}
else {
settings.set('user.currency', 'USD');
}
(function() {
function loadJSON(callback) {
var file = 'https://www.cryptocompare.com/api/data/coinlist/';
var xobj = new XMLHttpRequest();
xobj.overrideMimeType("application/json");
xobj.open('GET', file, true);
xobj.onreadystatechange = function () {
if (xobj.readyState == 4 && xobj.status == "200") {
// Required use of an anonymous callback as .open will NOT return a value but simply returns undefined in asynchronous mode
callback(xobj.responseText);
}
};
xobj.send(null);
} //loadJSON
// Generate the list of all coins
loadJSON(function(response) {
// Parse JSON string into object
var myDiv = document.getElementById("coinlist");
var actual_JSON = JSON.parse(response);
//alert(settings.get('user.coins'));
//console.log(actual_JSON.Data);
//loop through data, get coin info, generate checkbox for each coin
Object.keys(actual_JSON.Data).forEach(function(key) {
//console.log(actual_JSON.Data[key].Name);
//console.log(actual_JSON.Data[key].CoinName);
var li = document.createElement("li");
var checkBox = document.createElement("input");
checkBox.className = "coinCode";
var label = document.createElement("label");
label.className = "coinName";
var div = document.createElement("div");
checkBox.type = "checkbox";
checkBox.value = actual_JSON.Data[key].Name;
checkBox.name = "cl[]";
//check the coins the user has already set
var str = String(settings.get('user.coins'));
var split_str = str.split(",");
if (split_str.indexOf(actual_JSON.Data[key].Name) !== -1) {
checkBox.checked = true;
}
myDiv.appendChild(li);
li.appendChild(checkBox);
li.appendChild(label);
label.appendChild(document.createTextNode(actual_JSON.Data[key].CoinName));
label.appendChild(document.createTextNode(' ('+actual_JSON.Data[key].Name+')'));
label.appendChild(div);
}); //forEach
}); //loadJSON
base = settings.get('user.currency'); // get the user's base currency
var currSel = document.getElementById('base'); //select the currency select box
currSel.value = settings.get('user.currency'); //select the option that corresponds to the user's currency
setBase = function() {
//selected base currency
var sel = document.getElementById('base');
var x = sel.selectedIndex;
var y = sel.options;
base = y[x].text;
settings.set('user.currency', base); //save the user's selection
updateData(); //immediately reflect the changed currency
};
})();
//Functions for creating/appending elements
function createNode(element) {
return document.createElement(element);
}
function append(parent, el) {
return parent.appendChild(el);
}
// Returns an array with values of the selected (checked) checkboxes in "frm"
function getSelectedChbox(frm) {
var selchbox = []; // array that will store the value of selected checkboxes
// gets all the input tags in frm, and their number
var inpfields = frm.getElementsByTagName('input');
var nr_inpfields = inpfields.length;
// traverse the inpfields elements, and adds the value of selected (checked) checkbox in selchbox
for(var i=0; i<nr_inpfields; i++) {
if(inpfields[i].type == 'checkbox' && inpfields[i].checked == true) selchbox.push(inpfields[i].value);
}
return selchbox;
}
/* Test this function */
//document.getElementById('firstname').innerHTML = settings.get('user.coins');
// Click on #saveCoins, save the coin selection to the user
document.getElementById('saveCoins').onclick = function(){
var coinForm = document.getElementById('coinlist');
var selchb = getSelectedChbox(coinForm); // gets the array returned by getSelectedChbox()
//alert(selchb);
settings.set('user', {
coins: selchb
});
var selectedCoins = settings.get('user.coins');
//document.getElementById('firstname').innerHTML = selectedCoins;
// just reloading the entire app because I have yet to figure out how to add/remove a coin from the primary list without a page reload
location.reload();
}
const ul = document.getElementById('prices'); // Get the list where we will place coins
const url = 'https://min-api.cryptocompare.com/data/pricemultifull?fsyms='+settings.get('user.coins') +'&tsyms='+base +'&extraParams=your_app_name';
function initData() {
fetch(url)
.then(
function(response) {
if (response.status !== 200) {
console.log('Looks like there was a problem. Status Code: ' +
response.status);
return;
}
// Examine the response
response.json().then(function(data) {
//console.log(data);
let prices = data.DISPLAY;
var i = 0;
for (let key of Object.keys(prices)) {
let coin = prices[key];
//console.log(coin);
let li = createNode('li'),
span = createNode('span');
sym = createNode('span');
li.setAttribute("class", "price");
li.setAttribute("id", "coin-"+[key]);
//alert("coin-"+[key])
//console.log(settings.get('coin.'+[key]+'.order'));
li.setAttribute("sortorder", settings.get(li.id+'.order'));
//alert(settings.get(li.id+'.order'));
append(li, span);
append(ul, li);
i++;
}
//console.log(data.RAW.BTC.USD.PRICE)
sortChildren(
document.getElementById('prices'),
function(li) { return +li.getAttribute('sortorder') }
);
//sort your coins
sortable('#prices', {
handle: 'span'
})[0].addEventListener('sortstop', function(e) {
// Declare variables
var ul, li, i;
ul = document.getElementById("prices");
li = ul.getElementsByTagName('li');
// Loop through all list items
for (i = 0; i < li.length; i++) {
li[i].setAttribute("sortorder", i);
var elementID = li[i].id;
//alert(elementID);
settings.set(elementID, { // coin-BTC
order: li[i].getAttribute('sortorder')
});
//alert(settings.get(elementID + '.order'));
}
//alert(settings.get('coin.'+e+'.order'));
/*
This event is triggered when the user stopped sorting and the DOM position has changed.
e.detail.item contains the current dragged element.
e.detail.index contains the new index of the dragged element (considering only list items)
e.detail.oldindex contains the old index of the dragged element (considering only list items)
e.detail.elementIndex contains the new index of the dragged element (considering all items within sortable)
e.detail.oldElementIndex contains the old index of the dragged element (considering all items within sortable)
e.detail.startparent contains the element that the dragged item comes from
e.detail.endparent contains the element that the dragged item was added to (new parent)
e.detail.newEndList contains all elements in the list the dragged item was dragged to
e.detail.newStartList contains all elements in the list the dragged item was dragged from
e.detail.oldStartList contains all elements in the list the dragged item was dragged from BEFORE it was dragged from it
*/
}); //sortable
}); //response.json
} //function(response)
) //.then
.catch(function(err) {
console.log('Fetch Error :-S', err);
});
updateData();
}
function updateData() {
const url = 'https://min-api.cryptocompare.com/data/pricemultifull?fsyms='+settings.get('user.coins') +'&tsyms='+base +'&extraParams=your_app_name';
fetch(url)
.then(
function(response) {
if (response.status !== 200) {
console.log('Looks like there was a problem. Status Code: ' +
response.status);
return;
}
// Examine the text in the response
response.json().then(function(data) {
let pricesDISPLAY = data.DISPLAY; // display for everything except coin symbol
let pricesRAW = data.RAW; // raw to get BTC instead of bitcoin symbol
for (let key of Object.keys(pricesRAW)) {
let coinDISPLAY = pricesDISPLAY[key];
let coinDISPLAYchange = coinDISPLAY[base].CHANGEPCT24HOUR;
let coinRAW = pricesRAW[key];
//console.log(coinDISPLAY);
let li = document.getElementById("coin-"+[key]),
span = document.querySelector("#coin-"+[key]+" span");
span.setAttribute("class", "draggable");
let coinSymbol = coinRAW[base].FROMSYMBOL;
let coinRate = coinDISPLAY[base].PRICE.replace(/ /g,''); //.replace(/ /g,'') removes space after $
//replace currencies that have no symbols with easier to read formats
if(coinRate.includes("AUD")) { coinRate = coinRate.replace("AUD", "A$"); }
if(coinRate.includes("CAD")) { coinRate = coinRate.replace("CAD", "C$"); }
if(coinRate.includes("HKD")) { coinRate = coinRate.replace("HKD", "HK$"); }
if(coinRate.includes("MXN")) { coinRate = coinRate.replace("MXN", "$"); }
if(coinRate.includes("NOK")) { coinRate = coinRate.replace("NOK", "kr"); }
if(coinRate.includes("NZD")) { coinRate = coinRate.replace("NZD", "NZ$"); }
if(coinRate.includes("SEK")) { coinRate = coinRate.replace("SEK", "kr"); }
if(coinRate.includes("SGD")) { coinRate = coinRate.replace("SGD", "S$"); }
if(coinRate.includes("TRY")) { coinRate = coinRate.replace("TRY", "₺"); }
if(coinRate.includes("ZAR")) { coinRate = coinRate.replace("ZAR", "R"); }
//console.log(span);
span.innerHTML = '<span class="sym">' + coinSymbol + '</span> ' + coinRate + '<span class="change">' + coinDISPLAYchange + '%</span>';
// % Change
let change = document.querySelector("#coin-"+[key]+" .change");
if(coinDISPLAYchange > 0) {
change.className += " positive";
change.classList.remove("negative");
}
else if(coinDISPLAYchange < 0) {
change.className += " negative";
change.classList.remove("postive");
}
else {
change.classList.remove("postive");
change.classList.remove("negative");
}
}
});
}
)
setTimeout(function(){updateData()}, 5000); // run this once every 5 seconds
}
// Let's do this thing!
initData();
/*******
* APP UI
********/
//Window controls
const remote = require('electron').remote;
document.getElementById("close-btn").addEventListener("click", function (e) {
var window = remote.getCurrentWindow();
window.close();
});
document.getElementById("min-btn").addEventListener("click", function (e) {
var window = remote.getCurrentWindow();
window.minimize();
});
//settings tab/icon
function toggleSettings() {
var divs = document.getElementsByClassName('panel'), i;
for (i = 0; i < divs.length; ++i) {
if(divs[i].classList.contains('inactive')) {
divs[i].classList.remove('inactive');
divs[i].classList.add('active');
}
else {
divs[i].classList.remove('active');
divs[i].classList.add('inactive');
}
}//for
}//toggleSettings
//Coin search filter
function myFunction() {
// Declare variables
var input, filter, ul, li, a, i;
input = document.getElementById('myInput');
filter = input.value.toUpperCase();
ul = document.getElementById("coinlist");
li = ul.getElementsByTagName('li');
// Loop through all list items, and hide those who don't match the search query
for (i = 0; i < li.length; i++) {
label = li[i].getElementsByTagName("label")[0];
checkbox = li[i].getElementsByTagName("input")[0].value;
if (label.innerHTML.toUpperCase().indexOf(filter) > -1) {
li[i].style.display = "";
} else {
li[i].style.display = "none";
}
} //for
} //myFunction
//sort by attribute
function sortChildren(wrap, f, isNum) {
var l = wrap.children.length,
arr = new Array(l);
for(var i=0; i<l; ++i)
arr[i] = [f(wrap.children[i]), wrap.children[i]];
arr.sort(isNum
? function(a,b){ return a[0]-b[0]; }
: function(a,b){ return a[0]<b[0] ? -1 : a[0]>b[0] ? 1 : 0; }
);
var par = wrap.parentNode,
ref = wrap.nextSibling;
par.removeChild(wrap);
for(var i=0; i<l; ++i) wrap.appendChild(arr[i][1]);
par.insertBefore(wrap, ref);
} //sortChildren
//require('./renderer.js')
</script>
<script src="js/app_common.js"></script>
<script src="js/html.sortable.min.js"></script>
</html>

10
js/Chart.min.js vendored Normal file

File diff suppressed because one or more lines are too long

549
js/app_common.js Normal file
View File

@ -0,0 +1,549 @@
/******************
* APP FUNCTIONALITY
******************/
//access electron from here
const remote = require("electron").remote;
//user settings
const settings = require("electron-settings");
//default coins
if (settings.has("user.coins")) {
//do nothing because coins already set
} else {
settings.set("user", {
coins: ["BTC", "ETH", "LTC"],
});
}
//default base currency
if (settings.has("user.currency")) {
//do nothing because currency already set
} else {
settings.set("user.currency", "USD");
}
/* Base Currency */
base = settings.get("user.currency"); // get the user's base currency
var currSel = document.getElementById("base"); //select the currency select box
currSel.value = settings.get("user.currency"); //select the option that corresponds to the user's currency
setBase = function () {
//selected base currency
var sel = document.getElementById("base");
var x = sel.selectedIndex;
var y = sel.options;
base = y[x].text;
settings.set("user.currency", base); //save the user's selection
updateData(); //immediately reflect the changed currency
};
//Functions for creating/appending elements
function createNode(element) {
return document.createElement(element);
}
function append(parent, el) {
return parent.appendChild(el);
}
const ul = document.getElementById("prices"); // Get the list where we will place coins
const portfolio_ul = document.getElementById("portfolio-list");
var url =
"https://min-api.cryptocompare.com/data/pricemultifull?fsyms=" +
settings.get("user.coins") +
"&tsyms=" +
base +
"&extraParams=crypto-price-widget";
var pinCheck = document.getElementById("pin-to-top");
function clearData() {
ul.innerHTML = "";
clearTimeout(appRefresh);
}
function initData() {
//need to redeclare the url variable here to grab the latest user coins, etc.
var url =
"https://min-api.cryptocompare.com/data/pricemultifull?fsyms=" +
settings.get("user.coins") +
"&tsyms=" +
base +
"&extraParams=crypto-price-widget";
fetch(url)
.then(
function (response) {
// Examine the response
response.json().then(function (data) {
//console.log(url);
let pricesDISPLAY = data.DISPLAY; // display for everything except coin symbol
let pricesRAW = data.RAW; // raw to get "BTC" instead of bitcoin symbol
var i = 0;
for (let key of Object.keys(pricesDISPLAY)) {
let coin = pricesDISPLAY[key];
//console.log(coin);
let li = createNode("li"),
span = createNode("span");
sym = createNode("span");
li.setAttribute("class", "price");
li.setAttribute("id", "coin-" + [key]);
span.setAttribute("class", "draggable");
//when adding a new coin, default sortorder to 999
if (settings.get(li.id + ".order") == null) {
settings.set(li.id + ".order", 999);
li.setAttribute("sortorder", 999);
} else {
li.setAttribute("sortorder", settings.get(li.id + ".order"));
}
append(li, span);
append(ul, li);
i++;
} //for
//sort your coins
sortable("#prices", {
handle: "span",
})[0].addEventListener("sortstop", function (e) {
// Declare variables
var ul, ulPortfolio, li, liPortfolio, i;
ul = document.getElementById("prices");
ulPortfolio = document.getElementById("portfolio-list");
li = ul.getElementsByTagName("li");
liPortfolio = ulPortfolio.getElementsByTagName("li");
// Loop through all list items
for (i = 0; i < li.length; i++) {
li[i].setAttribute("sortorder", i);
var elementID = li[i].id;
//alert(elementID);
settings.set(elementID, {
// coin-BTC
order: li[i].getAttribute("sortorder"),
});
//alert(settings.get(elementID + '.order'));
} //for
//alert(settings.get('coin.'+e+'.order'));
}); //sortable
//Pin to Top - settings check - immediately set checkbox and window to saved state
if (settings.get("user.pinToTop") == "yes") {
pinCheck.checked = true;
remote.getCurrentWindow().setAlwaysOnTop(true);
} else {
pinCheck.checked = false;
remote.getCurrentWindow().setAlwaysOnTop(false);
}
sortChildren(document.getElementById("prices"), function (li) {
return +li.getAttribute("sortorder");
});
sortChildren(document.getElementById("portfolio-list"), function (
li
) {
return +li.getAttribute("sortorder");
});
}); //response.json
updateData();
} //function(response)
) //.then
.catch(function (err) {
console.log("Unable to connect!");
var mainDiv = document.getElementById("main");
var errorDiv = document.createElement("div");
errorDiv.className = "error";
errorDiv.innerHTML =
'<h2>Uh-oh! Looks like you&#39;re offline.</h2>\
<img src="images/offline_doge.jpg" />\
<h4>Reconnect, then reload the app.</h4>\
<button type="button" class="refresh" onClick="location.reload(false);" >Reload</button>';
document.getElementById("main").appendChild(errorDiv);
}); //catch
} //initData
function updateData() {
//need to redeclare the url variable here to grab the latest user coins, etc.
var url =
"https://min-api.cryptocompare.com/data/pricemultifull?fsyms=" +
settings.get("user.coins") +
"&tsyms=" +
base +
"&extraParams=crypto-price-widget";
/*
** What data needs to be grabbed/changed?
** Base currency
** Coin price
** % change
** Portfolio - Coin price affects current value / total
*/
//console.log(settings.get('user.coins'));
fetch(url).then(
function (response) {
// Examine the text in the response
response.json().then(function (data) {
let pricesDISPLAY = data.DISPLAY; // display for everything except coin symbol
let pricesRAW = data.RAW; // raw to get "BTC" instead of bitcoin symbol
let portfolioSum = 0;
for (let key of Object.keys(pricesRAW)) {
let coinDISPLAY = pricesDISPLAY[key];
let coinDISPLAYchange = coinDISPLAY[base].CHANGEPCT24HOUR;
let coinRAW = pricesRAW[key];
//console.log(coinDISPLAY);
let li = document.getElementById("coin-" + [key]),
span = document.querySelector("#coin-" + [key] + " span");
let coinSymbol = coinRAW[base].FROMSYMBOL;
let coinRate = coinDISPLAY[base].PRICE.replace(/ /g, ""); //.replace(/ /g,'') removes space after $
//replace currencies that have no symbols with easier to read formats
if (coinRate.includes("AUD")) {
coinRate = coinRate.replace("AUD", "A$");
}
if (coinRate.includes("CAD")) {
coinRate = coinRate.replace("CAD", "C$");
}
if (coinRate.includes("HKD")) {
coinRate = coinRate.replace("HKD", "HK$");
}
if (coinRate.includes("MXN")) {
coinRate = coinRate.replace("MXN", "$");
}
if (coinRate.includes("NOK")) {
coinRate = coinRate.replace("NOK", "kr");
}
if (coinRate.includes("NZD")) {
coinRate = coinRate.replace("NZD", "NZ$");
}
if (coinRate.includes("SEK")) {
coinRate = coinRate.replace("SEK", "kr");
}
if (coinRate.includes("SGD")) {
coinRate = coinRate.replace("SGD", "S$");
}
if (coinRate.includes("TRY")) {
coinRate = coinRate.replace("TRY", "₺");
}
if (coinRate.includes("ZAR")) {
coinRate = coinRate.replace("ZAR", "R");
}
//console.log(span);
span.innerHTML =
'<span class="sym">' +
coinSymbol +
"</span> " +
coinRate +
'<span class="change">' +
coinDISPLAYchange +
"%</span>";
//Price Alert Test - PRO Feature
/*
* Choose crypto
* Choose price
* Choose equals, greater than, or less than price
* Alert set to "on"
* Alert when matches conditions
* When click on notification, alert set to "off"
* Use electron settings, localStorage, or sessionStorage?
* Should this be included in the updateData or separate?
*/
/*
var alerted = localStorage.getItem('alerted') || '';
if(coinSymbol.includes("BTC") && coinRAW[base].PRICE >= "5723" && alerted != 'yes') {
let notif = new window.Notification('Price Alert', {
body: "BTC has gone above 5790!"
});
notif.onclick = () => {
//so it doesn't keep notifying us every 3 seconds.
localStorage.setItem('alerted','yes');
}
}
*/
// % Change
let change = document.querySelector("#coin-" + [key] + " .change");
if (coinDISPLAYchange > 0) {
change.className += " positive";
change.classList.remove("negative");
} else if (coinDISPLAYchange < 0) {
change.className += " negative";
change.classList.remove("postive");
} else {
change.classList.remove("postive");
change.classList.remove("negative");
}
// Portfolio
let quantityValue = document.querySelector(
"#coin-" + [key] + " .quantity-value"
);
let quantityNumber = settings.get("quantity." + [key]);
let regp = /[^0-9.-]+/g;
if (quantityNumber != null) {
quantityTotal =
parseFloat(coinRate.replace(regp, "")) *
parseFloat(quantityNumber.replace(regp, ""));
}
// sum of all total coin values
portfolioSum += quantityTotal;
// put sum into the markup
let portfolioTotalValue = document.querySelector(
"#portfolio-total-value .value"
);
// total value for each coin
if (coinRate.includes("Ƀ")) {
//because BTC has 8 decimal places
quantityValue.innerHTML = quantityTotal.toFixed(8);
portfolioTotalValue.innerHTML = portfolioSum.toFixed(8);
} else if (quantityValue != null) {
//standard currency format
quantityValue.innerHTML = quantityTotal
.toFixed(2)
.replace(/(\d)(?=(\d{3})+\.)/g, "$1,");
portfolioTotalValue.innerHTML = portfolioSum
.toFixed(2)
.replace(/(\d)(?=(\d{3})+\.)/g, "$1,");
}
} //for
}); //response.json().then
} //function(response)
); //then
appRefresh = setTimeout(function () {
updateData();
}, 5000); // run this once every 5 seconds
} //updateData()
// Let's do this thing!
initData();
// Click on #saveCoins, save the coin selection to the user
document.getElementById("saveCoins").onclick = function () {
var coinForm = document.getElementById("coinlist");
var selchb = getSelectedChbox(coinForm); // gets the array returned by getSelectedChbox()
settings.set("user.coins", selchb);
//clear and reload
clearData();
initData();
};
/***********
* PORTFOLIO
***********/
var portfolio_list_container = document.querySelector("#portfolio-list");
var portfolio_list = settings.get("user.coins");
//generate html from list of coins
for (let key of Object.keys(portfolio_list)) {
let coin = portfolio_list[key];
//console.log(coin);
let li = createNode("li"),
span = createNode("span");
sym = createNode("span");
li.setAttribute("id", "coin-" + [coin]);
li.setAttribute("sortorder", settings.get(li.id + ".order"));
append(li, span);
append(portfolio_ul, li);
if (settings.has("quantity." + [coin])) {
inputValue = settings.get("quantity." + [coin]);
} else {
inputValue = "0";
settings.set("quantity." + [coin], "0");
}
span.innerHTML =
'<span class="sym">' +
coin +
'</span> <span class="block quantity"><label for="quantity.' +
coin +
'">Quantity</label> <input type="number" name="quantity.' +
coin +
'" min="0" value="' +
inputValue +
'" step=".01"></span> <span class="block value"><label>Current Value</label><span class="quantity-value"></span></span>';
i++;
} //for
// save quantities
document.getElementById("saveQuantities").onclick = function () {
var items = portfolio_ul.getElementsByTagName("input");
for (var i = 0; i < items.length; ++i) {
// do something with items[i], which is a <li> element
inputName = items[i].getAttribute("name");
inputValue = items[i].value;
//console.log(inputValue);
settings.set(inputName, inputValue);
}
// just reloading the entire app because I have yet to figure out how to add/remove a coin from the primary list without a page reload
//location.reload(false);
};
/***********
* SETTINGS
***********/
// Settings - list of coins
function loadJSON(callback) {
//Stored local version of https://www.cryptocompare.com/api/data/coinlist/ for performance
var file = "./coinlist.json";
var xobj = new XMLHttpRequest();
xobj.overrideMimeType("application/json");
xobj.open("GET", file, true);
xobj.onreadystatechange = function () {
if (xobj.readyState == 4 && xobj.status == "200") {
// Required use of an anonymous callback as .open will NOT return a value but simply returns undefined in asynchronous mode
callback(xobj.responseText);
}
};
xobj.send(null);
} //loadJSON
// Generate the list of all coins
loadJSON(function (response) {
// Parse JSON string into object
var myDiv = document.getElementById("coinlist");
var actual_JSON = JSON.parse(response);
//alert(settings.get('user.coins'));
//console.log(actual_JSON.Data);
//loop through data, get coin info, generate checkbox for each coin
Object.keys(actual_JSON.Data).forEach(function (key) {
//console.log(actual_JSON.Data[key].Name);
//console.log(actual_JSON.Data[key].CoinName);
var li = document.createElement("li");
var checkBox = document.createElement("input");
checkBox.className = "coinCode";
var label = document.createElement("label");
label.className = "coinName";
var div = document.createElement("div");
checkBox.type = "checkbox";
checkBox.value = actual_JSON.Data[key].Name;
checkBox.id = actual_JSON.Data[key].Name;
label.htmlFor = actual_JSON.Data[key].Name;
checkBox.name = "cl[]";
//check the coins the user has already set
var str = String(settings.get("user.coins"));
var split_str = str.split(",");
if (split_str.indexOf(actual_JSON.Data[key].Name) !== -1) {
checkBox.checked = true;
}
myDiv.appendChild(li);
li.appendChild(checkBox);
li.appendChild(label);
label.appendChild(document.createTextNode(actual_JSON.Data[key].CoinName));
label.appendChild(
document.createTextNode(" (" + actual_JSON.Data[key].Name + ")")
);
label.appendChild(div);
}); //forEach
}); //loadJSON
// Returns an array with values of the selected (checked) checkboxes in "frm"
function getSelectedChbox(frm) {
var selchbox = []; // array that will store the value of selected checkboxes
// gets all the input tags in frm, and their number
var inpfields = frm.getElementsByTagName("input");
var nr_inpfields = inpfields.length;
// traverse the inpfields elements, and adds the value of selected (checked) checkbox in selchbox
for (var i = 0; i < nr_inpfields; i++) {
if (inpfields[i].type == "checkbox" && inpfields[i].checked == true)
selchbox.push(inpfields[i].value);
}
return selchbox;
}
/***********
* PIN TO TOP
*************/
pinCheck.onclick = function (event) {
var window = remote.getCurrentWindow();
var checkbox = event.target;
if (checkbox.checked) {
//Checkbox has been checked
window.setAlwaysOnTop(true); //immediately make the change to the window
settings.set("user.pinToTop", "yes");
} else {
//Checkbox has been unchecked
window.setAlwaysOnTop(false);
settings.set("user.pinToTop", "no");
}
};
/*******
* APP UI
********/
//Window controls
document.getElementById("close-btn").addEventListener("click", function (e) {
var window = remote.getCurrentWindow();
window.close();
});
document.getElementById("min-btn").addEventListener("click", function (e) {
var window = remote.getCurrentWindow();
window.minimize();
});
//Panel tabs
var tabLinks = document.querySelectorAll(".tabs button");
for (var i = 0; i < tabLinks.length; i++) {
tabLinks[i].onclick = function () {
var target = this.getAttribute("href").replace("#", "");
var sections = document.querySelectorAll(".panel");
for (var j = 0; j < sections.length; j++) {
sections[j].style.display = "none";
}
document.getElementById(target).style.display = "block";
for (var k = 0; k < tabLinks.length; k++) {
tabLinks[k].removeAttribute("class");
}
this.setAttribute("class", "active");
return false;
};
}
//Coin search filter
function myFunction() {
// Declare variables
var input, filter, ul, li, a, i;
input = document.getElementById("myInput");
filter = input.value.toUpperCase();
ul = document.getElementById("coinlist");
li = ul.getElementsByTagName("li");
// Loop through all list items, and hide those who don't match the search query
for (i = 0; i < li.length; i++) {
label = li[i].getElementsByTagName("label")[0];
checkbox = li[i].getElementsByTagName("input")[0].value;
if (label.innerHTML.toUpperCase().indexOf(filter) > -1) {
li[i].style.display = "";
} else {
li[i].style.display = "none";
}
} //for
} //myFunction
//sort by attribute
function sortChildren(wrap, f, isNum) {
var l = wrap.children.length,
arr = new Array(l);
for (var i = 0; i < l; ++i) arr[i] = [f(wrap.children[i]), wrap.children[i]];
arr.sort(
isNum
? function (a, b) {
return a[0] - b[0];
}
: function (a, b) {
return a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0;
}
);
var par = wrap.parentNode,
ref = wrap.nextSibling;
par.removeChild(wrap);
for (var i = 0; i < l; ++i) wrap.appendChild(arr[i][1]);
par.insertBefore(wrap, ref);
} //sortChildren

2
js/offline.min.js vendored Normal file

File diff suppressed because one or more lines are too long

72
main.js
View File

@ -1,45 +1,47 @@
const electron = require('electron')
// Module to control application life.
const app = electron.app
// Module to create native browser window.
const BrowserWindow = electron.BrowserWindow
const electron = require("electron");
// app control, application life.BrowserWindow creates native browser window.
const { app, BrowserWindow } = require("electron");
//Store Window size and position
const windowStateKeeper = require('electron-window-state');
const windowStateKeeper = require("electron-window-state");
const path = require("path");
const url = require("url");
const path = require('path')
const url = require('url')
const settings = require('electron-settings');
const settings = require("electron-settings");
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let mainWindow
let mainWindow = null;
function createWindow () {
function createWindow() {
// Load the previous state with fallback to defaults
let mainWindowState = windowStateKeeper({
defaultWidth: 320,
defaultHeight: 240
defaultHeight: 240,
});
// Create the browser window.
mainWindow = new electron.BrowserWindow({
mainWindow = new BrowserWindow({
title: app.getName(),
alwaysOnTop: false,
//show: false,
x: mainWindowState.x,
y: mainWindowState.y,
width: mainWindowState.width,
height: mainWindowState.height,
maxWidth: 360,
minWidth: 240,
maxWidth: 960,
minWidth: 290,
minHeight: 100,
maximizable: false,
fullscreenable: false,
frame: false,
titleBarStyle: 'customButtonsOnHover',
titleBarStyle: "customButtonsOnHover",
autoHideMenuBar: true,
transparent: true,
icon: 'images/icon.png'
icon: path.join(__dirname, "images/icon.png"),
webPreferences: {
nodeIntegration: true,
},
});
// Let us register listeners on the window, so we can update the state
@ -48,45 +50,47 @@ function createWindow () {
mainWindowState.manage(mainWindow);
// and load the index.html of the app.
mainWindow.loadURL(url.format({
pathname: path.join(__dirname, 'index.html'),
protocol: 'file:',
slashes: true
}))
mainWindow.loadURL(
url.format({
pathname: path.join(__dirname, "index.html"),
protocol: "file:",
slashes: true,
})
);
// Open the DevTools.
// mainWindow.webContents.openDevTools()
// Emitted when the window is closed.
mainWindow.on('closed', function () {
mainWindow.on("closed", function () {
// Dereference the window object, usually you would store windows
// in an array if your app supports multi windows, this is the time
// when you should delete the corresponding element.
mainWindow = null
})
mainWindow = null;
});
}
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', createWindow)
app.on("ready", createWindow);
// Quit when all windows are closed.
app.on('window-all-closed', function () {
app.on("window-all-closed", function () {
// On OS X it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== 'darwin') {
app.quit()
if (process.platform !== "darwin") {
app.quit();
}
})
});
app.on('activate', function () {
app.on("activate", function () {
// On OS X it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (mainWindow === null) {
createWindow()
createWindow();
}
})
});
// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and require them here.

10637
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,14 +1,14 @@
{
"name": "crypto-price-widget",
"productName": "Crypto Price Widget",
"version": "1.0.0",
"version": "1.4.0",
"description": "A cross-platform app for tracking Crypto prices",
"main": "main.js",
"scripts": {
"start": "electron .",
"build": "node build.js",
"package-mac": "electron-packager . --overwrite --platform=darwin --arch=x64 --icon=images/icon.icns --prune=true --out=release-builds",
"package-win": "electron-packager . --overwrite --asar=true --platform=win32 --arch=ia32 --icon=images/icon_win.ico --prune=true --out=release-builds --version-string.CompanyName=CE --version-string.FileDescription=CE --version-string.ProductName=\"Crypto Price Widget\"",
"package-win": "electron-packager . --overwrite --platform=win32 --arch=x64 --icon=images/icon_win.ico --prune=true --out=release-builds --version-string.CompanyName=CE --version-string.FileDescription=CE --version-string.ProductName=\"Crypto Price Widget\"",
"package-linux": "electron-packager . --overwrite --platform=linux --arch=x64 --icon=images/icon.png --prune=true --out=release-builds"
},
"repository": "https://github.com/nathanp/crypto-price-widget",
@ -22,18 +22,19 @@
"author": "Nathan Parikh",
"license": "CC0-1.0",
"devDependencies": {
"electron": "~1.6.2",
"electron-packager": "^8.7.2"
"electron": "^9.0.4",
"electron-packager": "^14.2.1",
"electron-winstaller": "^4.0.0"
},
"dependencies": {
"ava": "^0.15.2",
"cryptocurrencies": "^1.0.0",
"electron-settings": "^3.1.1",
"electron-window-state": "^4.1.1",
"html5sortable": "^0.6.1",
"ava": "^3.8.2",
"cryptocurrencies": "^7.0.0",
"electron-settings": "^3.2.0",
"electron-window-state": "^5.0.3",
"html5sortable": "^0.9.17",
"isomorphic-fetch": "^2.2.1",
"lodash.sortby": "^4.7.0",
"sortablejs": "^1.6.0",
"xo": "^0.16.0"
"sortablejs": "^1.10.2",
"xo": "^0.32.0"
}
}

View File

@ -1,4 +1,9 @@
// This file is required by the index.html file and will
// be executed in the renderer process for that window.
// All of the Node.js APIs are available in this process.
// Do this from the renderer process
/*
var notif = new window.Notification('Download Complete', {
body: "yolo"
})
*/