Compare commits
562 Commits
v1.4.2
...
react-driv
Author | SHA1 | Date | |
---|---|---|---|
![]() |
868a35337c | ||
![]() |
1398ca2931 | ||
![]() |
96c865f14a | ||
![]() |
6dbd425e89 | ||
![]() |
5b2769d0e9 | ||
![]() |
5f38cca60c | ||
![]() |
78cebdb7a4 | ||
![]() |
a6aedab0a0 | ||
![]() |
4aeccbe963 | ||
![]() |
126b3fbb40 | ||
![]() |
9ea8a6134e | ||
![]() |
3706770322 | ||
![]() |
7be07bfe8c | ||
![]() |
791c047fa1 | ||
![]() |
35ad0340b9 | ||
![]() |
0a0be3a13d | ||
![]() |
86238af380 | ||
![]() |
d10073a052 | ||
![]() |
b99b0d4bf8 | ||
![]() |
27b5b1bf10 | ||
![]() |
bab9069dee | ||
![]() |
52a3258814 | ||
![]() |
da548f59d1 | ||
![]() |
ecc500907c | ||
![]() |
724dade1f6 | ||
![]() |
c5dc869c03 | ||
![]() |
273f7e4535 | ||
![]() |
a58e060138 | ||
![]() |
ef4d2fcc72 | ||
![]() |
330c06d926 | ||
![]() |
be9c36828a | ||
![]() |
17f83135c5 | ||
![]() |
543ba51d3c | ||
![]() |
33df23fc8c | ||
![]() |
3236d6b934 | ||
![]() |
e0e7775367 | ||
![]() |
198679583c | ||
![]() |
6dae2a604f | ||
![]() |
68905c6ae4 | ||
![]() |
26630c4d64 | ||
![]() |
d382f030f0 | ||
![]() |
08fca87b2f | ||
![]() |
33441a1c5c | ||
![]() |
6d8346b13a | ||
![]() |
d9b340ca45 | ||
![]() |
ebbc52ee1f | ||
![]() |
de5bee29ef | ||
![]() |
25f843ec0b | ||
![]() |
df600a9e14 | ||
![]() |
156c25cea1 | ||
![]() |
f7dd04e3de | ||
![]() |
3036d86cfa | ||
![]() |
3fccd52884 | ||
![]() |
00640274fc | ||
![]() |
a7e8fb98b3 | ||
![]() |
bed6643437 | ||
![]() |
f815e8511f | ||
![]() |
6360fd42e7 | ||
![]() |
62a9656888 | ||
![]() |
ffb89c7e5b | ||
![]() |
aa52735006 | ||
![]() |
b65526d8ee | ||
![]() |
ea8e2999ae | ||
![]() |
0b5017f992 | ||
![]() |
8bf1bdaa04 | ||
![]() |
01eb3b1c94 | ||
![]() |
3402c9f601 | ||
![]() |
13c3518c5e | ||
![]() |
821fad27dc | ||
![]() |
50a34e2f4c | ||
![]() |
2a19b2afbe | ||
![]() |
dc92d010fb | ||
![]() |
9cb27a616a | ||
![]() |
518a0ca45b | ||
![]() |
3526a0e3c5 | ||
![]() |
6386f85258 | ||
![]() |
e80106d8f8 | ||
![]() |
1145cbc75c | ||
![]() |
e669b81072 | ||
![]() |
9d78da941b | ||
![]() |
63d0f5e2c6 | ||
![]() |
dae047eff1 | ||
![]() |
8a2db8bced | ||
![]() |
792fab20e6 | ||
![]() |
f40c0f6bd3 | ||
![]() |
ccf11b9861 | ||
![]() |
1fcde5a17c | ||
![]() |
88f543dd25 | ||
![]() |
8b6f3f6022 | ||
![]() |
294ef8045a | ||
![]() |
1f7e4c886b | ||
![]() |
63c047009f | ||
![]() |
2c5f5004cc | ||
![]() |
2fa5426cf5 | ||
![]() |
428c777402 | ||
![]() |
7e2c62c520 | ||
![]() |
3d3b4f4a46 | ||
![]() |
a543dcf166 | ||
![]() |
5de54bb6bf | ||
![]() |
d95401e614 | ||
![]() |
2c835437e9 | ||
![]() |
498e70ed2b | ||
![]() |
fce5b500bf | ||
![]() |
11def54adb | ||
![]() |
9da9e73f7a | ||
![]() |
f90cd49a6d | ||
![]() |
6e72c07190 | ||
![]() |
1997e1faeb | ||
![]() |
b33b34bd71 | ||
![]() |
6a9b739541 | ||
![]() |
6cb0bdd1a4 | ||
![]() |
b73ebb6f92 | ||
![]() |
24a83260ca | ||
![]() |
fc1c1b402b | ||
![]() |
af462b3486 | ||
![]() |
3e236996c8 | ||
![]() |
c4e84483df | ||
![]() |
e9532f2158 | ||
![]() |
15fc8ab2e7 | ||
![]() |
b39dbeb25e | ||
![]() |
e181c21f85 | ||
![]() |
db771bc2cc | ||
![]() |
0695cfb3c0 | ||
![]() |
ff3982efa4 | ||
![]() |
40de7f5d54 | ||
![]() |
18a848696f | ||
![]() |
58de7375a2 | ||
![]() |
b61109a269 | ||
![]() |
164fd8f022 | ||
![]() |
cafaa9ff22 | ||
![]() |
34c98d1dcd | ||
![]() |
ec015da795 | ||
![]() |
a21a82c64e | ||
![]() |
1aca669c62 | ||
![]() |
39573ada54 | ||
![]() |
bceb7c77d1 | ||
![]() |
68fa771905 | ||
![]() |
8a85968ec4 | ||
![]() |
03b1a2dcff | ||
![]() |
5cb231e427 | ||
![]() |
e90f6764f5 | ||
![]() |
d078055e40 | ||
![]() |
98134eb612 | ||
![]() |
b0f2dfc4fb | ||
![]() |
195f07c09f | ||
![]() |
15f87edc96 | ||
![]() |
52caae8f05 | ||
![]() |
99f7fc99da | ||
![]() |
89fc98c6f6 | ||
![]() |
0c2eb1caab | ||
![]() |
303969bdbf | ||
![]() |
8b7266a435 | ||
![]() |
fc9282fff7 | ||
![]() |
33fb79e0de | ||
![]() |
b4418660df | ||
![]() |
3560a22a77 | ||
![]() |
818b466687 | ||
![]() |
91d29c1e2c | ||
![]() |
e06fe72131 | ||
![]() |
7d715fdca0 | ||
![]() |
3cfa6988ab | ||
![]() |
1c707b2d37 | ||
![]() |
2ed4c76abf | ||
![]() |
94e91723f4 | ||
![]() |
091bddbad8 | ||
![]() |
b3f68c2638 | ||
![]() |
00c94c8efd | ||
![]() |
66b19677bf | ||
![]() |
2d4170e0d7 | ||
![]() |
7f8f38ddf1 | ||
![]() |
2e1763f19a | ||
![]() |
a5f8b3c164 | ||
![]() |
a802087009 | ||
![]() |
3b16c06f70 | ||
![]() |
a979ae3ced | ||
![]() |
02e0f40702 | ||
![]() |
a4d7853bb2 | ||
![]() |
ac463e0f65 | ||
![]() |
c187aa46b7 | ||
![]() |
e65fa9dcae | ||
![]() |
fea230cfab | ||
![]() |
90838c99fc | ||
![]() |
8e96adeda9 | ||
![]() |
3cdb0f840e | ||
![]() |
b6ad6e0a85 | ||
![]() |
e73a577452 | ||
![]() |
16e8aa2447 | ||
![]() |
1d6958a67e | ||
![]() |
136ca282eb | ||
![]() |
388fc2f7d9 | ||
![]() |
72fdb7127a | ||
![]() |
f67832644f | ||
![]() |
2614f3261c | ||
![]() |
e84204b49a | ||
![]() |
86aaa725d5 | ||
![]() |
dd583a176f | ||
![]() |
5299d958f2 | ||
![]() |
f0374cf9d9 | ||
![]() |
6b6a0d7b4f | ||
![]() |
4317892421 | ||
![]() |
8052b2adfa | ||
![]() |
d44927447a | ||
![]() |
09e6c6422d | ||
![]() |
df85ffb254 | ||
![]() |
752ba4405c | ||
![]() |
1f3a02b83d | ||
![]() |
8e372f1e93 | ||
![]() |
caeb84f58b | ||
![]() |
759722bf7d | ||
![]() |
cefdc95c9b | ||
![]() |
3be7029078 | ||
![]() |
3f16858a93 | ||
![]() |
c61c6deaa8 | ||
![]() |
90c8483df8 | ||
![]() |
d70b189cec | ||
![]() |
41a7fc4de5 | ||
![]() |
db119d5230 | ||
![]() |
c88245954d | ||
![]() |
b1ab3834b6 | ||
![]() |
34b7c1be81 | ||
![]() |
082c77586f | ||
![]() |
3b6fe7b548 | ||
![]() |
da072e7621 | ||
![]() |
4568404a70 | ||
![]() |
43319853ef | ||
![]() |
ce9f142621 | ||
![]() |
8ef3add183 | ||
![]() |
ef45696015 | ||
![]() |
a8f8c2cd85 | ||
![]() |
6d79a8e23a | ||
![]() |
d65dc6ccac | ||
![]() |
ccc9076a80 | ||
![]() |
3c007cea34 | ||
![]() |
bf29312ecf | ||
![]() |
a53095b29e | ||
![]() |
d5c9e6b054 | ||
![]() |
d4f29bd2af | ||
![]() |
a6661ac759 | ||
![]() |
6aa83819d1 | ||
![]() |
8ff4c8a4a5 | ||
![]() |
1d77b8dae7 | ||
![]() |
3e0062621b | ||
![]() |
b20e220910 | ||
![]() |
0259572ded | ||
![]() |
a78866069b | ||
![]() |
b8fc83577c | ||
![]() |
c21969ab4e | ||
![]() |
e02cfc4529 | ||
![]() |
ac07c63631 | ||
![]() |
1054bc995b | ||
![]() |
3560ab6387 | ||
![]() |
e50274b962 | ||
![]() |
bb1773a1a1 | ||
![]() |
941dc3b1b4 | ||
![]() |
92d08d24ab | ||
![]() |
7f6ffe0f73 | ||
![]() |
66457ca0c7 | ||
![]() |
3344f1fd88 | ||
![]() |
c4b636f80a | ||
![]() |
0ea0975bd4 | ||
![]() |
534f3a7469 | ||
![]() |
f3110ba018 | ||
![]() |
e76cc81fe1 | ||
![]() |
adcc3343ec | ||
![]() |
645e114a1f | ||
![]() |
65d86460cb | ||
![]() |
aaccd10c2a | ||
![]() |
a237bfd930 | ||
![]() |
73f64d93b1 | ||
![]() |
871db09447 | ||
![]() |
8d79103392 | ||
![]() |
fd765443e4 | ||
![]() |
63967d1558 | ||
![]() |
6b270885bf | ||
![]() |
47937d6aaa | ||
![]() |
7d2ba45620 | ||
![]() |
b270d819a8 | ||
![]() |
c50553fbf6 | ||
![]() |
a541c863be | ||
![]() |
254b482651 | ||
![]() |
d3c2cd4215 | ||
![]() |
4f7cc7dd6b | ||
![]() |
21f1f4e503 | ||
![]() |
47f2336673 | ||
![]() |
5ae93bf6d0 | ||
![]() |
caf5f10326 | ||
![]() |
e68dbcf4ee | ||
![]() |
a42e81cf8c | ||
![]() |
98a8588c1b | ||
![]() |
8630af7646 | ||
![]() |
268c5302e8 | ||
![]() |
d07d535993 | ||
![]() |
a8a75f22b2 | ||
![]() |
6143023502 | ||
![]() |
ca6aa5d4aa | ||
![]() |
bc028ed41f | ||
![]() |
911d3a9188 | ||
![]() |
b88e715fc5 | ||
![]() |
a4dfa5f281 | ||
![]() |
05fc1711b9 | ||
![]() |
c16fbb5b47 | ||
![]() |
7ca3e2b519 | ||
![]() |
8c8a0bf8eb | ||
![]() |
e85251d2e3 | ||
![]() |
73e4827249 | ||
![]() |
c37270ea08 | ||
![]() |
8cc33b46bb | ||
![]() |
700341f9cc | ||
![]() |
ca85ad5995 | ||
![]() |
1c8c36a224 | ||
![]() |
25b814e796 | ||
![]() |
e89566e04a | ||
![]() |
e946f388c0 | ||
![]() |
7616e8dab7 | ||
![]() |
2dc4fef4d3 | ||
![]() |
f2ca997195 | ||
![]() |
7445004abf | ||
![]() |
9b76abe2ed | ||
![]() |
a97205c9fc | ||
![]() |
bf3d069aad | ||
![]() |
27ea74722c | ||
![]() |
2525456d8b | ||
![]() |
9fa32df3a6 | ||
![]() |
5bdd5da13b | ||
![]() |
b8756edd29 | ||
![]() |
c93910e858 | ||
![]() |
ad4226ace7 | ||
![]() |
d71b3fe1bc | ||
![]() |
07858eecac | ||
![]() |
64ec6d0e58 | ||
![]() |
cf722427ab | ||
![]() |
1b7ff07efc | ||
![]() |
87533f4417 | ||
![]() |
9077c95cdd | ||
![]() |
22acc5ae96 | ||
![]() |
cf596a88ab | ||
![]() |
7354fa3050 | ||
![]() |
978497b287 | ||
![]() |
a52d745250 | ||
![]() |
aae71f8105 | ||
![]() |
5419b4b732 | ||
![]() |
caf5a8917c | ||
![]() |
beec00dcb3 | ||
![]() |
7565e809b0 | ||
![]() |
5c9a646bc1 | ||
![]() |
dd8ef288f7 | ||
![]() |
db8d2953cb | ||
![]() |
b75ca26db2 | ||
![]() |
948a04122a | ||
![]() |
b7c4562b85 | ||
![]() |
6d0fea1983 | ||
![]() |
1a158a919a | ||
![]() |
9a83bd4267 | ||
![]() |
7e3f516b04 | ||
![]() |
afd888e14d | ||
![]() |
76af6e975e | ||
![]() |
2017df9ec6 | ||
![]() |
aa1e83dc24 | ||
![]() |
20996b153d | ||
![]() |
b298e53fc4 | ||
![]() |
7fb382bee0 | ||
![]() |
2158772e3b | ||
![]() |
333298e6c3 | ||
![]() |
6e9deeba5b | ||
![]() |
15951509a7 | ||
![]() |
dd8b7e42d6 | ||
![]() |
2655c86be3 | ||
![]() |
c4c4d347cf | ||
![]() |
a3f7239c1b | ||
![]() |
f90c2ad74e | ||
![]() |
2907cd173b | ||
![]() |
779ee8294f | ||
![]() |
a229c9e10e | ||
![]() |
3f8d2e4242 | ||
![]() |
c1a8b0c303 | ||
![]() |
8e92c5b844 | ||
![]() |
c366fbde22 | ||
![]() |
890e866ae2 | ||
![]() |
8eb11a8957 | ||
![]() |
9cc65a386b | ||
![]() |
e4d4af1587 | ||
![]() |
b3aab5116a | ||
![]() |
7227c76538 | ||
![]() |
c2c59f4a9e | ||
![]() |
eeede318fd | ||
![]() |
3855bb4d56 | ||
![]() |
89fa682721 | ||
![]() |
cb701a7bbc | ||
![]() |
407325b8ce | ||
![]() |
161debb35a | ||
![]() |
fd5385b127 | ||
![]() |
e85e1410aa | ||
![]() |
ac068f353a | ||
![]() |
fe7c6d0d57 | ||
![]() |
abf1e4a8ac | ||
![]() |
f9dfb2d0c7 | ||
![]() |
da23740f17 | ||
![]() |
47f81251d7 | ||
![]() |
7e01eca7f5 | ||
![]() |
941b5c9e45 | ||
![]() |
207c0d612d | ||
![]() |
4035176d88 | ||
![]() |
b9f9968f84 | ||
![]() |
7a2dac8d5b | ||
![]() |
2fb8ad146f | ||
![]() |
2f4a7352d9 | ||
![]() |
c3ff030542 | ||
![]() |
aa05cd1449 | ||
![]() |
31cd33f86c | ||
![]() |
9abca204e3 | ||
![]() |
e9760c2100 | ||
![]() |
1c5bab63a5 | ||
![]() |
0d80957639 | ||
![]() |
6c73ddcaca | ||
![]() |
1bb86fe4a8 | ||
![]() |
6b7be7a82d | ||
![]() |
37b25d8422 | ||
![]() |
49edd1a6dc | ||
![]() |
a338c6e60a | ||
![]() |
f9805f3bc7 | ||
![]() |
b24c4ea030 | ||
![]() |
0cabac1eed | ||
![]() |
7c08dbfbd2 | ||
![]() |
c0ec74bbb7 | ||
![]() |
ea834f6778 | ||
![]() |
2271f32140 | ||
![]() |
93906b9b17 | ||
![]() |
40d84b7a82 | ||
![]() |
d0ee569989 | ||
![]() |
92d969b075 | ||
![]() |
49b70d0efd | ||
![]() |
26779ef1fb | ||
![]() |
fd19af23a6 | ||
![]() |
f798fef212 | ||
![]() |
c74eab4fb5 | ||
![]() |
6a0198639f | ||
![]() |
8c870f2db8 | ||
![]() |
73d287e7ee | ||
![]() |
9eb3eea3f1 | ||
![]() |
df769396b1 | ||
![]() |
c2e47ca9dc | ||
![]() |
b348159e0e | ||
![]() |
45b62f0e77 | ||
![]() |
1fb420c2fc | ||
![]() |
b4f2bc1cb3 | ||
![]() |
d6cc182da2 | ||
![]() |
9d7baa86aa | ||
![]() |
872cd90dc6 | ||
![]() |
00ab816791 | ||
![]() |
2a9e9962e8 | ||
![]() |
ed25dd931e | ||
![]() |
66031f1bc2 | ||
![]() |
a902872880 | ||
![]() |
4c2d440871 | ||
![]() |
0da17de422 | ||
![]() |
07025ae76b | ||
![]() |
d99fe944f3 | ||
![]() |
408b2a473e | ||
![]() |
fc22e9e28a | ||
![]() |
d1c44ab7b1 | ||
![]() |
4ddac50d9b | ||
![]() |
7208ad67f1 | ||
![]() |
fffdeb1320 | ||
![]() |
e8fa7d8812 | ||
![]() |
dfdb92957e | ||
![]() |
441069f04b | ||
![]() |
201995eb90 | ||
![]() |
5863319c0b | ||
![]() |
2986d85b26 | ||
![]() |
f312457f35 | ||
![]() |
de501f5ba3 | ||
![]() |
c5e5141b21 | ||
![]() |
c08cf61d0c | ||
![]() |
6728382141 | ||
![]() |
7c3f104d1b | ||
![]() |
ad6be11bbc | ||
![]() |
f09faf6645 | ||
![]() |
117a7762e1 | ||
![]() |
3d47f494a8 | ||
![]() |
e0ebdc9045 | ||
![]() |
53f8e9328d | ||
![]() |
687e0b563b | ||
![]() |
6232cc7d49 | ||
![]() |
2a6670a404 | ||
![]() |
447efc7096 | ||
![]() |
c47878202d | ||
![]() |
349076bf34 | ||
![]() |
1748bf2e2a | ||
![]() |
d9a7730511 | ||
![]() |
4a239cc217 | ||
![]() |
ce2534c5b7 | ||
![]() |
3083f5fd55 | ||
![]() |
2045066b16 | ||
![]() |
995177498f | ||
![]() |
c00b7b62d6 | ||
![]() |
93b772f197 | ||
![]() |
7782f94daa | ||
![]() |
7c97dc8004 | ||
![]() |
34ce00e2d5 | ||
![]() |
702658cca5 | ||
![]() |
e472fe0276 | ||
![]() |
fb1c381ab7 | ||
![]() |
03c7998c11 | ||
![]() |
35729fc36b | ||
![]() |
f6ce603e45 | ||
![]() |
81a75ca955 | ||
![]() |
b57c9a51f8 | ||
![]() |
df396966b0 | ||
![]() |
b8897e0193 | ||
![]() |
150e8112ea | ||
![]() |
196f16b941 | ||
![]() |
21cb7a4847 | ||
![]() |
bb2dac7504 | ||
![]() |
f5fd2f2be3 | ||
![]() |
e6ea3879c3 | ||
![]() |
c07895d418 | ||
![]() |
58fb1cf4c3 | ||
![]() |
52cc8cb8fc | ||
![]() |
1b30dab8eb | ||
![]() |
40df4a94a7 | ||
![]() |
262d06f035 | ||
![]() |
408ab99774 | ||
![]() |
28cb21db13 | ||
![]() |
7f37f4ca41 | ||
![]() |
5f85258e84 | ||
![]() |
cde1776a2d | ||
![]() |
7f6303391a | ||
![]() |
66c7806cfa | ||
![]() |
2cdb6945ba | ||
![]() |
ca45855ed7 | ||
![]() |
07ed90ed11 | ||
![]() |
c1b97b1b44 | ||
![]() |
674019ea75 | ||
![]() |
8f762484f2 | ||
![]() |
4174991345 | ||
![]() |
71064cc760 | ||
![]() |
4c40c8ff30 | ||
![]() |
d7211b130b | ||
![]() |
e40d5a0a5d | ||
![]() |
553fbf1a77 | ||
![]() |
d3a4753b79 | ||
![]() |
12cc0de571 | ||
![]() |
cbd531e161 | ||
![]() |
a8bbe02e21 | ||
![]() |
6605d5ee63 | ||
![]() |
df8bacd82e | ||
![]() |
ee831da52d | ||
![]() |
8f969374c7 | ||
![]() |
5a788b04b5 | ||
![]() |
b88a45aa79 | ||
![]() |
3c20a056e6 | ||
![]() |
82a57d34b8 | ||
![]() |
c4d7076fe8 | ||
![]() |
00b11157b2 | ||
![]() |
b63bb1ac0c | ||
![]() |
882d0ecba8 | ||
![]() |
963f1a11eb | ||
![]() |
ba8acb40ec | ||
![]() |
1d4ea2164f | ||
![]() |
0199243ce9 | ||
![]() |
4c8b97afb3 | ||
![]() |
3bac0225e5 |
@@ -14,3 +14,6 @@ trim_trailing_whitespace = false
|
||||
|
||||
[Makefile]
|
||||
indent_style = tab
|
||||
|
||||
[*.ts]
|
||||
indent_style = tab
|
||||
|
@@ -8,9 +8,12 @@ plugins:
|
||||
- lodash
|
||||
- jsdoc
|
||||
- node
|
||||
- react
|
||||
extends: 'standard'
|
||||
parserOptions:
|
||||
sourceType: 'script'
|
||||
ecmaFeatures:
|
||||
jsx: true
|
||||
settings:
|
||||
jsdoc:
|
||||
additionalTagNames:
|
||||
@@ -286,7 +289,7 @@ rules:
|
||||
- error
|
||||
- anonymous: always
|
||||
named: always
|
||||
asyncArrow: never
|
||||
asyncArrow: always
|
||||
template-tag-spacing:
|
||||
- error
|
||||
- always
|
||||
@@ -295,9 +298,6 @@ rules:
|
||||
|
||||
# ECMAScript 6
|
||||
|
||||
arrow-body-style:
|
||||
- error
|
||||
- always
|
||||
arrow-parens:
|
||||
- error
|
||||
- always
|
||||
@@ -318,8 +318,6 @@ rules:
|
||||
- always
|
||||
prefer-const:
|
||||
- error
|
||||
prefer-reflect:
|
||||
- error
|
||||
prefer-spread:
|
||||
- error
|
||||
prefer-numeric-literals:
|
||||
@@ -444,3 +442,13 @@ rules:
|
||||
node/no-extraneous-import:
|
||||
- error
|
||||
|
||||
# React
|
||||
|
||||
react/jsx-uses-vars:
|
||||
- error
|
||||
|
||||
overrides:
|
||||
files: ['*.jsx']
|
||||
rules:
|
||||
require-jsdoc:
|
||||
- off
|
||||
|
12
.gitattributes
vendored
@@ -1,5 +1,7 @@
|
||||
# Javascript files must retain LF line-endings (to keep eslint happy)
|
||||
*.ts text eol=lf
|
||||
*.js text eol=lf
|
||||
*.jsx text eol=lf
|
||||
# CSS and SCSS files must retain LF line-endings (to keep ensure-staged-sass.sh happy)
|
||||
*.css text eol=lf
|
||||
*.scss text eol=lf
|
||||
@@ -11,7 +13,7 @@ Dockerfile* text
|
||||
etcher text
|
||||
.git* text
|
||||
*.html text
|
||||
*.json text
|
||||
*.json text eol=lf
|
||||
*.cpp text
|
||||
*.h text
|
||||
*.gyp text
|
||||
@@ -24,6 +26,7 @@ Makefile text
|
||||
*.yml text
|
||||
*.patch text
|
||||
*.txt text
|
||||
CODEOWNERS text
|
||||
|
||||
# Binary files (no line-ending conversions)
|
||||
*.bz2 binary diff=hex
|
||||
@@ -44,5 +47,12 @@ Makefile text
|
||||
*.bin binary diff=hex
|
||||
*.dmg binary diff=hex
|
||||
*.rpi-sdcard binary diff=hex
|
||||
*.wic binary diff=hex
|
||||
*.foo binary diff=hex
|
||||
*.eot binary diff=hex
|
||||
*.otf binary diff=hex
|
||||
*.woff binary diff=hex
|
||||
*.woff2 binary diff=hex
|
||||
*.ttf binary diff=hex
|
||||
xz-without-extension binary diff=hex
|
||||
wmic-output.txt binary diff=hex
|
||||
|
4
.gitignore
vendored
@@ -43,3 +43,7 @@ node_modules
|
||||
*.cer
|
||||
*.crt
|
||||
*.pem
|
||||
|
||||
# OSX files
|
||||
|
||||
.DS_Store
|
||||
|
4
.gitmodules
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
[submodule "scripts/resin"]
|
||||
path = scripts/resin
|
||||
url = https://github.com/balena-io/scripts.git
|
||||
branch = master
|
@@ -1,31 +1,82 @@
|
||||
{
|
||||
"node-cli": {
|
||||
"node": "6.1.0",
|
||||
"main": "lib/cli/etcher.js",
|
||||
"dependencies": {
|
||||
"linux": [
|
||||
"libudev-dev",
|
||||
"libusb-1.0-0-dev"
|
||||
]
|
||||
}
|
||||
},
|
||||
"electron": {
|
||||
"npm_version": "6.7.0",
|
||||
"dependencies": {
|
||||
"linux": [
|
||||
"libudev-dev",
|
||||
"libusb-1.0-0-dev",
|
||||
"libyaml-dev"
|
||||
"libyaml-dev",
|
||||
"libgtk-3-0",
|
||||
"libatk-bridge2.0-0",
|
||||
"libdbus-1-3",
|
||||
"libc6"
|
||||
]
|
||||
},
|
||||
"builder": {
|
||||
"appId": "io.resin.etcher",
|
||||
"copyright": "Copyright 2016-2018 Resinio Ltd",
|
||||
"productName": "Etcher",
|
||||
"appId": "io.balena.etcher",
|
||||
"copyright": "Copyright 2016-2019 Balena Ltd",
|
||||
"productName": "balenaEtcher",
|
||||
"nodeGypRebuild": true,
|
||||
"files": [
|
||||
"!node_modules/**/*.js.map",
|
||||
"!node_modules/**/*.h",
|
||||
"!node_modules/**/*.hpp",
|
||||
"!node_modules/**/*.cpp",
|
||||
"!node_modules/**/*.md",
|
||||
"!node_modules/**/*.ts",
|
||||
"!node_modules/**/*.coffee",
|
||||
"!node_modules/**/*.scss",
|
||||
"!node_modules/**/*.less",
|
||||
"!node_modules/**/*.hbs",
|
||||
"!node_modules/**/*.mkd",
|
||||
"!node_modules/**/LICENSE",
|
||||
"!node_modules/**/LICENCE",
|
||||
"!node_modules/**/license",
|
||||
"!node_modules/**/License",
|
||||
"!node_modules/**/LICENSE.txt",
|
||||
"!node_modules/**/Makefile",
|
||||
"!node_modules/**/.editorconfig",
|
||||
"!node_modules/**/.babelrc",
|
||||
"!node_modules/**/.prettierrc",
|
||||
"!node_modules/**/.prettierrc-*",
|
||||
"!node_modules/**/.eslintrc.yml",
|
||||
"!node_modules/**/.eslintignore",
|
||||
"!node_modules/**/.publishrc",
|
||||
"!lib/gui/app",
|
||||
"lib/gui/app/index.html",
|
||||
"generated"
|
||||
"generated",
|
||||
"!node_modules/chart.js/dist/docs",
|
||||
"!node_modules/ext2fs/config",
|
||||
"!node_modules/ext2fs/deps",
|
||||
"!node_modules/ext2fs/LICENSE",
|
||||
"!node_modules/ext2fs/src",
|
||||
"!node_modules/winusb-driver-generator/src",
|
||||
"!node_modules/winusb-driver-generator/deps",
|
||||
"!node_modules/winusb-driver-generator/ci",
|
||||
"!node_modules/rendition/__screenshots__",
|
||||
"!node_modules/polished/docs",
|
||||
"!node_modules/mermaid/src",
|
||||
"!node_modules/mermaid/dist",
|
||||
"node_modules/mermaid/dist/mermaid.core.js",
|
||||
"!node_modules/raven-js/src",
|
||||
"!node_modules/raven-js/dist",
|
||||
"node_modules/raven-js/dist/raven.js",
|
||||
"!node_modules/raven-js/plugins",
|
||||
"!node_modules/react-jsonschema-form/dist",
|
||||
"!node_modules/xxhash/deps",
|
||||
"!node_modules/xxhash/src",
|
||||
"!node_modules/unzip-stream/testData*",
|
||||
"!node_modules/usb",
|
||||
"node_modules/usb/usb.js",
|
||||
"node_modules/usb/package.json",
|
||||
"node_modules/usb/build",
|
||||
"node_modules/usb/src/binding",
|
||||
"!node_modules/roboto-fontface/fonts",
|
||||
"node_modules/roboto-fontface/fonts/roboto/Roboto-Thin.woff",
|
||||
"node_modules/roboto-fontface/fonts/roboto/Roboto-Light.woff",
|
||||
"node_modules/roboto-fontface/fonts/roboto/Roboto-Regular.woff",
|
||||
"node_modules/roboto-fontface/fonts/roboto/Roboto-Medium.woff",
|
||||
"node_modules/roboto-fontface/fonts/roboto/Roboto-Bold.woff"
|
||||
],
|
||||
"mac": {
|
||||
"category": "public.app-category.developer-tools"
|
||||
@@ -35,24 +86,24 @@
|
||||
"contents": [
|
||||
{
|
||||
"x": 140,
|
||||
"y": 225
|
||||
"y": 245
|
||||
},
|
||||
{
|
||||
"x": 415,
|
||||
"y": 225,
|
||||
"y": 245,
|
||||
"type": "link",
|
||||
"path": "/Applications"
|
||||
}
|
||||
],
|
||||
"window": {
|
||||
"width": 540,
|
||||
"height": 405
|
||||
"width": 544,
|
||||
"height": 407
|
||||
}
|
||||
},
|
||||
"linux": {
|
||||
"category": "Utility",
|
||||
"packageCategory": "utils",
|
||||
"synopsis": "Etcher is a powerful OS image flasher built with web technologies to ensure flashing an SDCard or USB drive is a pleasant and safe experience. It protects you from accidentally writing to your hard-drives, ensures every byte of data was written correctly and much more."
|
||||
"synopsis": "balenaEtcher is a powerful OS image flasher built with web technologies to ensure flashing an SDCard or USB drive is a pleasant and safe experience. It protects you from accidentally writing to your hard-drives, ensures every byte of data was written correctly and much more."
|
||||
},
|
||||
"deb": {
|
||||
"priority": "optional",
|
||||
|
69
.travis.yml
@@ -1,69 +0,0 @@
|
||||
language: node_js
|
||||
sudo: false
|
||||
node_js:
|
||||
- "6.10.3"
|
||||
|
||||
# Remove wine from cache
|
||||
before_cache:
|
||||
- rm -rf $HOME/.cache/electron-builder/wine
|
||||
|
||||
cache:
|
||||
ccache: true
|
||||
directories:
|
||||
- $HOME/.cache/electron
|
||||
- $HOME/.cache/electron-builder
|
||||
- $HOME/.npm/_prebuilds
|
||||
- $HOME/Library/Caches/electron
|
||||
- $HOME/Library/Caches/electron-builder
|
||||
- $HOME/.pkg-cache
|
||||
- node_modules
|
||||
|
||||
services:
|
||||
- docker
|
||||
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
packages:
|
||||
- libstdc++-6-dev
|
||||
|
||||
env:
|
||||
global:
|
||||
- CCACHE_TEMPDIR=/tmp/.ccache-temp
|
||||
- CCACHE_COMPRESS=1
|
||||
- CC="clang"
|
||||
- CXX="clang++"
|
||||
- HOMEBREW_NO_AUTO_UPDATE=1
|
||||
matrix:
|
||||
- TARGET_ARCH=x64
|
||||
- TARGET_ARCH=x86
|
||||
|
||||
matrix:
|
||||
fast_finish: true
|
||||
exclude:
|
||||
- os: osx
|
||||
env: TARGET_ARCH=x86
|
||||
|
||||
os:
|
||||
- linux
|
||||
|
||||
before_install:
|
||||
- export HOST_OS="$TRAVIS_OS_NAME";
|
||||
|
||||
install:
|
||||
- ./scripts/ci/install.sh -o $HOST_OS -r $TARGET_ARCH
|
||||
|
||||
script:
|
||||
- ./scripts/ci/test.sh -o $HOST_OS -r $TARGET_ARCH
|
||||
- ./scripts/ci/build-installers.sh -o $HOST_OS -r $TARGET_ARCH
|
||||
|
||||
deploy:
|
||||
provider: script
|
||||
skip_cleanup: true
|
||||
script: scripts/ci/deploy.sh -o $HOST_OS -r $TARGET_ARCH
|
||||
on:
|
||||
branch: master
|
||||
|
||||
notifications:
|
||||
email: false
|
405
CHANGELOG.md
@@ -3,6 +3,411 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
This project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
# v1.5.51
|
||||
## (2019-06-28)
|
||||
|
||||
* Update sudo-prompt to ^9.0.0 [Alexis Svinartchouk]
|
||||
|
||||
# v1.5.50
|
||||
## (2019-06-13)
|
||||
|
||||
* Option for trimming ext partitions on raw images [Alexis Svinartchouk]
|
||||
|
||||
# v1.5.49
|
||||
## (2019-06-13)
|
||||
|
||||
* Make window size configurable [Alexis Svinartchouk]
|
||||
|
||||
# v1.5.48
|
||||
## (2019-06-13)
|
||||
|
||||
* Don't use sudo-prompt when already elevated [Alexis Svinartchouk]
|
||||
|
||||
# v1.5.47
|
||||
## (2019-06-10)
|
||||
|
||||
* Rework drive-selector with react + rendition [Lorenzo Alberto Maria Ambrosi]
|
||||
* Use rendition theme property for step buttons [Lorenzo Alberto Maria Ambrosi]
|
||||
* Upgrade styled-system to v4.1.0 [Lorenzo Alberto Maria Ambrosi]
|
||||
* Upgrade rendition to v8.7.2 [Lorenzo Alberto Maria Ambrosi]
|
||||
|
||||
# v1.5.46
|
||||
## (2019-06-09)
|
||||
|
||||
* Update ext2fs to 1.0.29 [Alexis Svinartchouk]
|
||||
|
||||
# v1.5.45
|
||||
## (2019-06-04)
|
||||
|
||||
* Empty commit to trigger build [Alexis Svinartchouk]
|
||||
|
||||
# v1.5.44
|
||||
## (2019-06-03)
|
||||
|
||||
* Fix elevation on windows when the path contains "&" or "'" [Alexis Svinartchouk]
|
||||
|
||||
# v1.5.43
|
||||
## (2019-05-28)
|
||||
|
||||
* Revert "Include sass in webpack configs" [Lorenzo Alberto Maria Ambrosi]
|
||||
|
||||
# v1.5.42
|
||||
## (2019-05-28)
|
||||
|
||||
* Include sass in webpack configs [Lorenzo Alberto Maria Ambrosi]
|
||||
|
||||
# v1.5.41
|
||||
## (2019-05-27)
|
||||
|
||||
* waffle.io removal and adding a link to the license [Mateusz Hajder]
|
||||
|
||||
# v1.5.40
|
||||
## (2019-05-24)
|
||||
|
||||
* windows installer and portable version support both ia32 and x64 [Alexis Svinartchouk]
|
||||
|
||||
# v1.5.39
|
||||
## (2019-05-14)
|
||||
|
||||
* Add clean-shrinkwrap script to postshrinkwrap step [Lorenzo Alberto Maria Ambrosi]
|
||||
|
||||
# v1.5.38
|
||||
## (2019-05-13)
|
||||
|
||||
* Add mention to usbboot compatibility [Carlo Maria Curinga]
|
||||
|
||||
# v1.5.37
|
||||
## (2019-05-13)
|
||||
|
||||
* Bump react dependency to v16.8.5 [Lorenzo Alberto Maria Ambrosi]
|
||||
|
||||
# v1.5.36
|
||||
## (2019-05-13)
|
||||
|
||||
* Update etcher-sdk to ^2.0.9 [Alexis Svinartchouk]
|
||||
|
||||
# v1.5.35
|
||||
## (2019-05-10)
|
||||
|
||||
* Downgrade electron 4.1.5 -> 3.1.9 [Alexis Svinartchouk]
|
||||
|
||||
# v1.5.34
|
||||
## (2019-05-09)
|
||||
|
||||
* Use https url for fetching config, avoid redirection [Alexis Svinartchouk]
|
||||
* win32: fix running diskpart when the tmp file path contains spaces [Alexis Svinartchouk]
|
||||
|
||||
# v1.5.33
|
||||
## (2019-04-30)
|
||||
|
||||
* Fix gzipped files verification percentage and dmg verification. [Alexis Svinartchouk]
|
||||
|
||||
# v1.5.32
|
||||
## (2019-04-30)
|
||||
|
||||
* Export NPM_VERSION variable in Makefile [Lorenzo Alberto Maria Ambrosi]
|
||||
|
||||
# v1.5.31
|
||||
## (2019-04-29)
|
||||
|
||||
* Update etcher-sdk to ^2.0.3 [Alexis Svinartchouk]
|
||||
* Update electron to 4.1.5 [Alexis Svinartchouk]
|
||||
|
||||
# v1.5.30
|
||||
## (2019-04-24)
|
||||
|
||||
* Don't show a dialog when the write fails. [Alexis Svinartchouk]
|
||||
|
||||
# v1.5.29
|
||||
## (2019-04-19)
|
||||
|
||||
* Add support for auto-updating feature [Giovanni Garufi]
|
||||
|
||||
# v1.5.28
|
||||
## (2019-04-18)
|
||||
|
||||
* Update electron-builder to ^20.40.2 [Alexis Svinartchouk]
|
||||
* Update etcher-sdk to ^2.0.1 [Alexis Svinartchouk]
|
||||
|
||||
# v1.5.27
|
||||
## (2019-04-16)
|
||||
|
||||
* (Windows): Fix reading images from network drives when the tmp dir has spaces [Alexis Svinartchouk]
|
||||
|
||||
# v1.5.26
|
||||
## (2019-04-12)
|
||||
|
||||
* (Windows): Fix reading images from network drives containing non ascii characters [Alexis Svinartchouk]
|
||||
|
||||
# v1.5.25
|
||||
## (2019-04-09)
|
||||
|
||||
* New parameter in webview for opt-out analytics [Lorenzo Alberto Maria Ambrosi]
|
||||
|
||||
# v1.5.24
|
||||
## (2019-04-05)
|
||||
|
||||
* Update resin-corvus to ^2.0.3 [Alexis Svinartchouk]
|
||||
|
||||
# v1.5.23
|
||||
## (2019-04-03)
|
||||
|
||||
* Configure versionbot to publish repo metadata to github pages [Giovanni Garufi]
|
||||
|
||||
# v1.5.22
|
||||
## (2019-04-02)
|
||||
|
||||
* (Windows): Use full path to wmic as some systems don't have it in their PATH [Alexis Svinartchouk]
|
||||
|
||||
# v1.5.21
|
||||
## (2019-04-02)
|
||||
|
||||
* Fix error when config.analytics was undefined [Alexis Svinartchouk]
|
||||
|
||||
# v1.5.20
|
||||
## (2019-04-01)
|
||||
|
||||
* Don't try to flash when no device is selected [Alexis Svinartchouk]
|
||||
* Reformat changelog [Giovanni Garufi]
|
||||
* Avoid "Error: There is already a flash in progress" errors [Alexis Svinartchouk]
|
||||
|
||||
# v1.5.19
|
||||
## (2019-03-28)
|
||||
|
||||
* Update resin-corvus to ^2.0.2 [Alexis Svinartchouk]
|
||||
* Better reporting of unhandled rejections to sentry [Alexis Svinartchouk]
|
||||
|
||||
# v1.5.18
|
||||
## (2019-03-26)
|
||||
|
||||
* Update build scripts [Giovanni Garufi]
|
||||
|
||||
## v1.5.17 - 2019-03-25
|
||||
|
||||
### Misc
|
||||
|
||||
- Automatically publish github release from CI
|
||||
|
||||
## v1.5.16 - 2019-03-25
|
||||
|
||||
### Misc
|
||||
|
||||
- Add repo.yml
|
||||
|
||||
## v1.5.15 - 2019-03-20
|
||||
|
||||
### Misc
|
||||
|
||||
- Show the correct logo on usbboot devices on Ubuntu
|
||||
|
||||
## v1.5.14 - 2019-03-20
|
||||
|
||||
### Misc
|
||||
|
||||
- Update etcher-sdk to ^1.3.10
|
||||
|
||||
## v1.5.13 - 2019-03-18
|
||||
|
||||
### Misc
|
||||
|
||||
- Update build scripts
|
||||
|
||||
## v1.5.12 - 2019-03-15
|
||||
|
||||
### Misc
|
||||
|
||||
- Update build scripts
|
||||
|
||||
## v1.5.11 - 2019-03-12
|
||||
|
||||
### Misc
|
||||
|
||||
- Fixed broken Hombrew cask link for etcher
|
||||
- Remove no longer used travis and appveyor configs
|
||||
|
||||
## v1.5.10 - 2019-03-12
|
||||
|
||||
### Misc
|
||||
|
||||
- Update resin-scripts
|
||||
|
||||
## v1.5.9 - 2019-03-05
|
||||
|
||||
### Misc
|
||||
|
||||
- Update etcher-sdk to 1.3.0
|
||||
|
||||
## v1.5.8 - 2019-03-01
|
||||
|
||||
### Misc
|
||||
|
||||
- Update ext2fs to 1.0.27
|
||||
|
||||
## v1.5.7 - 2019-03-01
|
||||
|
||||
### Fixes
|
||||
|
||||
- Update docs
|
||||
- Fix disappearing modal window
|
||||
|
||||
### Misc
|
||||
|
||||
- Fix blurred background image
|
||||
|
||||
## v1.5.6 - 2019-02-28
|
||||
|
||||
### Misc
|
||||
|
||||
- Target electron 3 runtime in babel options
|
||||
|
||||
## v1.5.5 - 2019-02-28
|
||||
|
||||
### Misc
|
||||
|
||||
- Don't pass undefined sockets to ipc.server.emit()
|
||||
- Fix error when event.dataTransfer.files is empty
|
||||
- Fix error message not showing when an unsupported image is selected
|
||||
- Avoid `Invalid percentage` exceptions
|
||||
- Update etcher-sdk to 1.1.0
|
||||
|
||||
## v1.5.4 - 2019-02-27
|
||||
|
||||
### Misc
|
||||
|
||||
- Add missing step for submodule cloning in README
|
||||
|
||||
## v1.5.3 - 2019-02-27
|
||||
|
||||
### Misc
|
||||
|
||||
- Throw error if no commit is annotated with a changelog entry
|
||||
|
||||
## v1.5.2 - 2019-02-26
|
||||
|
||||
- Enable versionist editVersion
|
||||
|
||||
## v1.5.1 - 2019-02-22
|
||||
|
||||
### Misc
|
||||
|
||||
- Removed lodash dependency in versionist.conf.js
|
||||
|
||||
## v1.5.0 - 2019-02-16
|
||||
|
||||
### Misc
|
||||
|
||||
- Reworked flashing logic with etcher-sdk
|
||||
- Add support for flashing Raspberry Pi CM3+
|
||||
- Upgrade to Electron v3.
|
||||
- Upgrade to NPM 6.7.0
|
||||
- Fix incorrect drives list on Linux
|
||||
- Changed “Drive Contains Image” to “Drive Mountpoint Contains Image”
|
||||
- Removed etcher-cli
|
||||
|
||||
## v1.4.9 - 2018-12-19
|
||||
|
||||
### Fixes
|
||||
|
||||
- Fix update notifier error popping up on v1.4.1->1.4.8
|
||||
|
||||
### Misc
|
||||
|
||||
- Added React component for the Flash Results button
|
||||
- Added React component for the Flash Another button
|
||||
- Restyle success screen and enlarge UI elements
|
||||
- Use https for fetching sub modules
|
||||
- Add `.wic` image extension as supported format
|
||||
|
||||
## v1.4.8 - 2018-11-23
|
||||
|
||||
### Features
|
||||
|
||||
- Added featured-project while flashing
|
||||
|
||||
### Fixes
|
||||
|
||||
- Moved back the write cancel button
|
||||
- Reject drives with null size (fixes pretty-bytes error)
|
||||
|
||||
## v1.4.7 - 2018-11-12
|
||||
|
||||
### Fixes
|
||||
|
||||
- Fix typo in contributing guidelines
|
||||
- Modify versionist.conf.js to match new internal commit guidelines
|
||||
|
||||
### Misc
|
||||
|
||||
- Rename etcher to balena-etcher
|
||||
- Convert Select Image button to Rendition
|
||||
|
||||
## v1.4.6 - 2018-10-28
|
||||
|
||||
### Fixes
|
||||
|
||||
- Provide a Buffer to xxhash.Stream
|
||||
- Fix 64 bit detection on arm
|
||||
- Fix incorrect file constraint path
|
||||
- Fix flash cancel button interaction
|
||||
|
||||
### Misc
|
||||
|
||||
- Add new balena.io logos
|
||||
- Use Resin CI scripts to build Etcher
|
||||
- Enable React lint rules
|
||||
- Convert Progress Button to Rendition
|
||||
|
||||
## v1.4.5 - 2018-10-11
|
||||
|
||||
### Features
|
||||
|
||||
- Center content independent to window resolution.
|
||||
- Add electron-native file-picker component.
|
||||
- Hide unsafe mode option toggle with an env var.
|
||||
- Use new design background color and drive step size ordering.
|
||||
- Add a convenience Storage class on top of localStorage.
|
||||
- Introduce env var to toggle autoselection of all drives.
|
||||
- Add font-awesome.
|
||||
- Add support for configuration files
|
||||
- Use GTK-3 darkTheme mode.
|
||||
- Add environment variable to toggle fullscreen.
|
||||
- Allow blacklisting of drives through and environment variable ETCHER_BLACKLISTED_DRIVES.
|
||||
- Show selected drives below drive selection step.
|
||||
- Add a button to cancel the flash process.
|
||||
- Download usbboot drivers installer when clicking a driverless usbboot device on Windows.
|
||||
- Allow disabling links and hiding help link with an env var.
|
||||
|
||||
### Fixes
|
||||
|
||||
- Add "make webpack" to travis-ci build script
|
||||
- Makefile: Don't use tilde in rpm versions
|
||||
- Change Spectron port so not to overlap with other builds
|
||||
- Fix multi-writes analytics by reusing existing logic in multi-write events.
|
||||
- Load usbboot adapter on start on GNU/Linux if running as root.
|
||||
|
||||
### Misc
|
||||
|
||||
- Update drivelist to v6.4.2
|
||||
- Add instructions for installing and uninstalling on Solus.
|
||||
|
||||
## v1.4.4 - 2018-04-24
|
||||
|
||||
### Fixes
|
||||
|
||||
- Don't display status dots with a quantity of zero on success screen
|
||||
- Correct wording of flash status to use "successful" instead of "succeeded"
|
||||
- Keep single drive-image pairs with warnings selected
|
||||
|
||||
### Misc
|
||||
|
||||
- Improve notification messages
|
||||
|
||||
## v1.4.3 - 2018-04-19
|
||||
|
||||
### Fixes
|
||||
|
||||
- Fix blob handling for usbboot
|
||||
|
||||
## v1.4.2 - 2018-04-18
|
||||
|
||||
### Features
|
||||
|
2
CODEOWNERS
Normal file
@@ -0,0 +1,2 @@
|
||||
* @thundron @zvin @jviotti
|
||||
/scripts @nazrhom
|
483
Makefile
@@ -2,9 +2,9 @@
|
||||
# Build configuration
|
||||
# ---------------------------------------------------------------------
|
||||
|
||||
# A non-existing target to force rules to rebuild
|
||||
# See https://stackoverflow.com/a/816416
|
||||
.FORCE:
|
||||
RESIN_SCRIPTS ?= ./scripts/resin
|
||||
export NPM_VERSION ?= 6.7.0
|
||||
S3_BUCKET = artifacts.ci.balena-cloud.com
|
||||
|
||||
# This directory will be completely deleted by the `clean` rule
|
||||
BUILD_DIRECTORY ?= dist
|
||||
@@ -17,8 +17,11 @@ endif
|
||||
|
||||
BUILD_TEMPORARY_DIRECTORY = $(BUILD_DIRECTORY)/.tmp
|
||||
|
||||
# See https://github.com/electron/spectron/issues/127
|
||||
ETCHER_SPECTRON_ENTRYPOINT ?= $(shell node -e 'console.log(require("electron"))')
|
||||
$(BUILD_DIRECTORY):
|
||||
mkdir $@
|
||||
|
||||
$(BUILD_TEMPORARY_DIRECTORY): | $(BUILD_DIRECTORY)
|
||||
mkdir $@
|
||||
|
||||
# See https://stackoverflow.com/a/13468229/1641422
|
||||
SHELL := /bin/bash
|
||||
@@ -75,10 +78,10 @@ else
|
||||
endif
|
||||
|
||||
ifndef PLATFORM
|
||||
$(error We couldn't detect your host platform)
|
||||
$(error We could not detect your host platform)
|
||||
endif
|
||||
ifndef HOST_ARCH
|
||||
$(error We couldn't detect your host architecture)
|
||||
$(error We could not detect your host architecture)
|
||||
endif
|
||||
|
||||
# Default to host architecture. You can override by doing:
|
||||
@@ -87,289 +90,32 @@ endif
|
||||
#
|
||||
TARGET_ARCH ?= $(HOST_ARCH)
|
||||
|
||||
# Support x86 builds from x64 in GNU/Linux
|
||||
# See https://github.com/addaleax/lzma-native/issues/27
|
||||
ifeq ($(PLATFORM),linux)
|
||||
ifneq ($(HOST_ARCH),$(TARGET_ARCH))
|
||||
ifeq ($(TARGET_ARCH),x86)
|
||||
export CFLAGS += -m32
|
||||
else
|
||||
$(error Can't build $(TARGET_ARCH) binaries on a $(HOST_ARCH) host)
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# Application configuration
|
||||
# Electron
|
||||
# ---------------------------------------------------------------------
|
||||
electron-develop: | $(BUILD_TEMPORARY_DIRECTORY)
|
||||
$(RESIN_SCRIPTS)/electron/install.sh \
|
||||
-b $(shell pwd) \
|
||||
-r $(TARGET_ARCH) \
|
||||
-s $(PLATFORM) \
|
||||
-m $(NPM_VERSION)
|
||||
|
||||
ELECTRON_VERSION = $(shell jq -r '.devDependencies["electron"]' package.json)
|
||||
NODE_VERSION = 6.1.0
|
||||
COMPANY_NAME = Resinio Ltd
|
||||
APPLICATION_NAME = $(shell jq -r '.displayName' package.json)
|
||||
APPLICATION_DESCRIPTION = $(shell jq -r '.description' package.json)
|
||||
APPLICATION_COPYRIGHT = $(shell cat electron-builder.yml | shyaml get-value copyright)
|
||||
|
||||
BINTRAY_ORGANIZATION = resin-io
|
||||
BINTRAY_REPOSITORY_DEBIAN = debian
|
||||
BINTRAY_REPOSITORY_REDHAT = redhat
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# Extra variables
|
||||
# ---------------------------------------------------------------------
|
||||
|
||||
TARGET_ARCH_DEBIAN = $(shell ./scripts/build/architecture-convert.sh -r $(TARGET_ARCH) -t debian)
|
||||
TARGET_ARCH_REDHAT = $(shell ./scripts/build/architecture-convert.sh -r $(TARGET_ARCH) -t redhat)
|
||||
TARGET_ARCH_APPIMAGE = $(shell ./scripts/build/architecture-convert.sh -r $(TARGET_ARCH) -t appimage)
|
||||
TARGET_ARCH_ELECTRON_BUILDER = $(shell ./scripts/build/architecture-convert.sh -r $(TARGET_ARCH) -t electron-builder)
|
||||
PLATFORM_PKG = $(shell ./scripts/build/platform-convert.sh -r $(PLATFORM) -t pkg)
|
||||
ENTRY_POINT_CLI = lib/cli/etcher.js
|
||||
ETCHER_CLI_BINARY = $(APPLICATION_NAME_LOWERCASE)
|
||||
ifeq ($(PLATFORM),win32)
|
||||
ETCHER_CLI_BINARY = $(APPLICATION_NAME_LOWERCASE).exe
|
||||
endif
|
||||
|
||||
APPLICATION_NAME_LOWERCASE = $(shell echo $(APPLICATION_NAME) | tr A-Z a-z)
|
||||
APPLICATION_VERSION_DEBIAN = $(shell echo $(APPLICATION_VERSION) | tr "-" "~")
|
||||
APPLICATION_VERSION_REDHAT = $(shell echo $(APPLICATION_VERSION) | tr "-" "~")
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# Release type
|
||||
# ---------------------------------------------------------------------
|
||||
|
||||
# Add the current commit to the version if release type is "snapshot"
|
||||
RELEASE_TYPE ?= snapshot
|
||||
PACKAGE_JSON_VERSION = $(shell jq -r '.version' package.json)
|
||||
ifeq ($(RELEASE_TYPE),production)
|
||||
APPLICATION_VERSION = $(PACKAGE_JSON_VERSION)
|
||||
S3_BUCKET = resin-production-downloads
|
||||
BINTRAY_COMPONENT = $(APPLICATION_NAME_LOWERCASE)
|
||||
endif
|
||||
ifeq ($(RELEASE_TYPE),snapshot)
|
||||
CURRENT_COMMIT_HASH = $(shell git log -1 --format="%h")
|
||||
APPLICATION_VERSION = $(PACKAGE_JSON_VERSION)+$(CURRENT_COMMIT_HASH)
|
||||
S3_BUCKET = resin-nightly-downloads
|
||||
BINTRAY_COMPONENT = $(APPLICATION_NAME_LOWERCASE)-devel
|
||||
endif
|
||||
ifndef APPLICATION_VERSION
|
||||
$(error Invalid release type: $(RELEASE_TYPE))
|
||||
endif
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# Code signing
|
||||
# ---------------------------------------------------------------------
|
||||
|
||||
ifeq ($(PLATFORM),darwin)
|
||||
ifndef CSC_NAME
|
||||
$(warning No code-sign identity found (CSC_NAME is not set))
|
||||
endif
|
||||
endif
|
||||
|
||||
ifeq ($(PLATFORM),win32)
|
||||
ifndef CSC_LINK
|
||||
$(warning No code-sign certificate found (CSC_LINK is not set))
|
||||
ifndef CSC_KEY_PASSWORD
|
||||
$(warning No code-sign certificate password found (CSC_KEY_PASSWORD is not set))
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# Electron Builder
|
||||
# ---------------------------------------------------------------------
|
||||
|
||||
ELECTRON_BUILDER_OPTIONS = --$(TARGET_ARCH_ELECTRON_BUILDER)
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# Analytics
|
||||
# ---------------------------------------------------------------------
|
||||
|
||||
ifndef ANALYTICS_SENTRY_TOKEN
|
||||
$(warning No Sentry token found (ANALYTICS_SENTRY_TOKEN is not set))
|
||||
else
|
||||
ELECTRON_BUILDER_OPTIONS += --extraMetadata.analytics.sentry.token=$(ANALYTICS_SENTRY_TOKEN)
|
||||
endif
|
||||
|
||||
ifndef ANALYTICS_MIXPANEL_TOKEN
|
||||
$(warning No Mixpanel token found (ANALYTICS_MIXPANEL_TOKEN is not set))
|
||||
else
|
||||
ELECTRON_BUILDER_OPTIONS += --extraMetadata.analytics.mixpanel.token=$(ANALYTICS_MIXPANEL_TOKEN)
|
||||
endif
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# Rules
|
||||
# ---------------------------------------------------------------------
|
||||
|
||||
# See http://stackoverflow.com/a/12528721
|
||||
# Note that the blank line before 'endef' is actually important - don't delete it
|
||||
define execute-command
|
||||
$(1)
|
||||
|
||||
endef
|
||||
|
||||
$(BUILD_DIRECTORY):
|
||||
mkdir $@
|
||||
|
||||
$(BUILD_TEMPORARY_DIRECTORY): | $(BUILD_DIRECTORY)
|
||||
mkdir $@
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# CLI
|
||||
# ---------------------------------------------------------------------
|
||||
|
||||
$(BUILD_DIRECTORY)/$(APPLICATION_NAME)-cli-$(APPLICATION_VERSION)-$(PLATFORM)-$(TARGET_ARCH)-app: \
|
||||
package.json npm-shrinkwrap.json \
|
||||
| $(BUILD_DIRECTORY)
|
||||
mkdir -p $@
|
||||
./scripts/build/dependencies-npm.sh -p \
|
||||
-r "$(TARGET_ARCH)" \
|
||||
-v "$(NODE_VERSION)" \
|
||||
-x $@ \
|
||||
-t node \
|
||||
-s "$(PLATFORM)"
|
||||
patch --directory=$@ --force --strip=1 --ignore-whitespace < patches/lzma-native-index-static-addon-require.patch
|
||||
cp -r lib $@
|
||||
cp package.json $@
|
||||
|
||||
$(BUILD_DIRECTORY)/$(APPLICATION_NAME)-cli-$(APPLICATION_VERSION)-$(PLATFORM)-$(TARGET_ARCH): \
|
||||
$(BUILD_DIRECTORY)/$(APPLICATION_NAME)-cli-$(APPLICATION_VERSION)-$(PLATFORM)-$(TARGET_ARCH)-app \
|
||||
| $(BUILD_DIRECTORY)
|
||||
mkdir $@
|
||||
cd $< && pkg --output ../../$@/$(ETCHER_CLI_BINARY) -t node6-$(PLATFORM_PKG)-$(TARGET_ARCH) $(ENTRY_POINT_CLI)
|
||||
./scripts/build/dependencies-npm-extract-addons.sh \
|
||||
-d $</node_modules \
|
||||
-o $@/node_modules
|
||||
# pkg currently has a bug where darwin executables
|
||||
# can't be code-signed
|
||||
# See https://github.com/zeit/pkg/issues/128
|
||||
# ifeq ($(PLATFORM),darwin)
|
||||
# ifdef CSC_NAME
|
||||
# ./scripts/build/electron-sign-file-darwin.sh -f $@/$(ETCHER_CLI_BINARY) -i "$(CSC_NAME)"
|
||||
# endif
|
||||
# endif
|
||||
|
||||
# pkg currently has a bug where Windows executables
|
||||
# can't be branded
|
||||
# See https://github.com/zeit/pkg/issues/149
|
||||
# ifeq ($(PLATFORM),win32)
|
||||
# ./scripts/build/electron-brand-exe.sh \
|
||||
# -f $@/$(ETCHER_CLI_BINARY) \
|
||||
# -n $(APPLICATION_NAME) \
|
||||
# -d "$(APPLICATION_DESCRIPTION)" \
|
||||
# -v "$(APPLICATION_VERSION)" \
|
||||
# -c "$(APPLICATION_COPYRIGHT)" \
|
||||
# -m "$(COMPANY_NAME)" \
|
||||
# -i assets/icon.ico \
|
||||
# -w $(BUILD_TEMPORARY_DIRECTORY)
|
||||
# endif
|
||||
|
||||
ifeq ($(PLATFORM),win32)
|
||||
ifdef CSC_LINK
|
||||
ifdef CSC_KEY_PASSWORD
|
||||
./scripts/build/electron-sign-exe-win32.sh -f $@/$(ETCHER_CLI_BINARY) \
|
||||
-d "$(APPLICATION_NAME) - $(APPLICATION_VERSION)" \
|
||||
-c $(CSC_LINK) \
|
||||
-p $(CSC_KEY_PASSWORD)
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
$(BUILD_DIRECTORY)/$(APPLICATION_NAME)-cli-$(APPLICATION_VERSION)-$(PLATFORM)-$(TARGET_ARCH).zip: \
|
||||
$(BUILD_DIRECTORY)/$(APPLICATION_NAME)-cli-$(APPLICATION_VERSION)-$(PLATFORM)-$(TARGET_ARCH)
|
||||
./scripts/build/zip-file.sh -f $< -s $(PLATFORM) -o $@
|
||||
|
||||
$(BUILD_DIRECTORY)/$(APPLICATION_NAME)-cli-$(APPLICATION_VERSION)-$(PLATFORM)-$(TARGET_ARCH).tar.gz: \
|
||||
$(BUILD_DIRECTORY)/$(APPLICATION_NAME)-cli-$(APPLICATION_VERSION)-$(PLATFORM)-$(TARGET_ARCH)
|
||||
./scripts/build/tar-gz-file.sh -f $< -o $@
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# GUI
|
||||
# ---------------------------------------------------------------------
|
||||
electron-test:
|
||||
$(RESIN_SCRIPTS)/electron/test.sh \
|
||||
-b $(shell pwd) \
|
||||
-s $(PLATFORM)
|
||||
|
||||
assets/dmg/background.tiff: assets/dmg/background.png assets/dmg/background@2x.png
|
||||
tiffutil -cathidpicheck $^ -out $@
|
||||
|
||||
build/js/gui.js: .FORCE
|
||||
webpack
|
||||
|
||||
$(BUILD_DIRECTORY)/$(APPLICATION_NAME)-$(APPLICATION_VERSION).dmg: assets/dmg/background.tiff build/js/gui.js \
|
||||
| $(BUILD_DIRECTORY)
|
||||
TARGET_ARCH=$(TARGET_ARCH) build --mac dmg $(ELECTRON_BUILDER_OPTIONS) \
|
||||
--extraMetadata.version=$(APPLICATION_VERSION) \
|
||||
--extraMetadata.packageType=dmg
|
||||
|
||||
$(BUILD_DIRECTORY)/$(APPLICATION_NAME)-$(APPLICATION_VERSION)-mac.zip: assets/dmg/background.tiff build/js/gui.js \
|
||||
| $(BUILD_DIRECTORY)
|
||||
TARGET_ARCH=$(TARGET_ARCH) build --mac zip $(ELECTRON_BUILDER_OPTIONS) \
|
||||
--extraMetadata.version=$(APPLICATION_VERSION) \
|
||||
--extraMetadata.packageType=zip
|
||||
|
||||
APPLICATION_NAME_ELECTRON = $(APPLICATION_NAME_LOWERCASE)-electron
|
||||
|
||||
$(BUILD_DIRECTORY)/$(APPLICATION_NAME_ELECTRON)-$(APPLICATION_VERSION_REDHAT).$(TARGET_ARCH_REDHAT).rpm: build/js/gui.js \
|
||||
| $(BUILD_DIRECTORY)
|
||||
build --linux rpm $(ELECTRON_BUILDER_OPTIONS) \
|
||||
--extraMetadata.name=$(APPLICATION_NAME_ELECTRON) \
|
||||
--extraMetadata.version=$(APPLICATION_VERSION_REDHAT) \
|
||||
--extraMetadata.packageType=rpm
|
||||
|
||||
$(BUILD_DIRECTORY)/$(APPLICATION_NAME_ELECTRON)_$(APPLICATION_VERSION_DEBIAN)_$(TARGET_ARCH_DEBIAN).deb: build/js/gui.js \
|
||||
| $(BUILD_DIRECTORY)
|
||||
build --linux deb $(ELECTRON_BUILDER_OPTIONS) \
|
||||
--extraMetadata.name=$(APPLICATION_NAME_ELECTRON) \
|
||||
--extraMetadata.version=$(APPLICATION_VERSION_DEBIAN) \
|
||||
--extraMetadata.packageType=deb
|
||||
|
||||
ifeq ($(TARGET_ARCH),x64)
|
||||
ELECTRON_BUILDER_LINUX_UNPACKED_DIRECTORY = linux-unpacked
|
||||
else
|
||||
ELECTRON_BUILDER_LINUX_UNPACKED_DIRECTORY = linux-$(TARGET_ARCH_ELECTRON_BUILDER)-unpacked
|
||||
endif
|
||||
|
||||
$(BUILD_DIRECTORY)/$(ELECTRON_BUILDER_LINUX_UNPACKED_DIRECTORY)/$(APPLICATION_NAME_ELECTRON): build/js/gui.js | $(BUILD_DIRECTORY)
|
||||
build --dir --linux $(ELECTRON_BUILDER_OPTIONS) \
|
||||
--extraMetadata.name=$(APPLICATION_NAME_ELECTRON) \
|
||||
--extraMetadata.version=$(APPLICATION_VERSION) \
|
||||
--extraMetadata.packageType=AppImage
|
||||
touch $@
|
||||
|
||||
$(BUILD_DIRECTORY)/$(APPLICATION_NAME_LOWERCASE)-$(APPLICATION_VERSION)-$(PLATFORM).AppDir: \
|
||||
$(BUILD_DIRECTORY)/$(ELECTRON_BUILDER_LINUX_UNPACKED_DIRECTORY)/$(APPLICATION_NAME_ELECTRON) \
|
||||
| $(BUILD_DIRECTORY)
|
||||
./scripts/build/electron-create-appdir.sh \
|
||||
-n $(APPLICATION_NAME) \
|
||||
-d "$(APPLICATION_DESCRIPTION)" \
|
||||
-p $(dir $<) \
|
||||
electron-build: assets/dmg/background.tiff | $(BUILD_TEMPORARY_DIRECTORY)
|
||||
$(RESIN_SCRIPTS)/electron/build.sh \
|
||||
-b $(shell pwd) \
|
||||
-r $(TARGET_ARCH) \
|
||||
-b $(APPLICATION_NAME_ELECTRON) \
|
||||
-i assets/icon.png \
|
||||
-o $@
|
||||
|
||||
$(BUILD_DIRECTORY)/$(APPLICATION_NAME_LOWERCASE)-$(APPLICATION_VERSION)-$(TARGET_ARCH_APPIMAGE).AppImage: \
|
||||
$(BUILD_DIRECTORY)/$(APPLICATION_NAME_LOWERCASE)-$(APPLICATION_VERSION)-$(PLATFORM).AppDir \
|
||||
| $(BUILD_DIRECTORY) $(BUILD_TEMPORARY_DIRECTORY)
|
||||
./scripts/build/electron-create-appimage-linux.sh \
|
||||
-d $< \
|
||||
-r $(TARGET_ARCH) \
|
||||
-w $(BUILD_TEMPORARY_DIRECTORY) \
|
||||
-o $@
|
||||
|
||||
$(BUILD_DIRECTORY)/$(APPLICATION_NAME_LOWERCASE)-$(APPLICATION_VERSION)-$(PLATFORM)-$(TARGET_ARCH_APPIMAGE).zip: \
|
||||
$(BUILD_DIRECTORY)/$(APPLICATION_NAME_LOWERCASE)-$(APPLICATION_VERSION)-$(TARGET_ARCH_APPIMAGE).AppImage \
|
||||
| $(BUILD_DIRECTORY)
|
||||
./scripts/build/zip-file.sh -f $< -s $(PLATFORM) -o $@
|
||||
|
||||
$(BUILD_DIRECTORY)/$(APPLICATION_NAME)-Portable-$(APPLICATION_VERSION)-$(TARGET_ARCH).exe: build/js/gui.js \
|
||||
| $(BUILD_DIRECTORY)
|
||||
TARGET_ARCH=$(TARGET_ARCH) build --win portable $(ELECTRON_BUILDER_OPTIONS) \
|
||||
--extraMetadata.version=$(APPLICATION_VERSION) \
|
||||
--extraMetadata.packageType=portable
|
||||
|
||||
$(BUILD_DIRECTORY)/$(APPLICATION_NAME)-Setup-$(APPLICATION_VERSION)-$(TARGET_ARCH).exe: build/js/gui.js \
|
||||
| $(BUILD_DIRECTORY)
|
||||
TARGET_ARCH=$(TARGET_ARCH) build --win nsis $(ELECTRON_BUILDER_OPTIONS) \
|
||||
--extraMetadata.version=$(APPLICATION_VERSION) \
|
||||
--extraMetadata.packageType=nsis
|
||||
-s $(PLATFORM) \
|
||||
-v production \
|
||||
-n $(BUILD_TEMPORARY_DIRECTORY)/npm \
|
||||
-w $(BUILD_TEMPORARY_DIRECTORY)
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# Phony targets
|
||||
@@ -386,159 +132,29 @@ TARGETS = \
|
||||
lint-spell \
|
||||
test-spectron \
|
||||
test-gui \
|
||||
test-sdk \
|
||||
test-cli \
|
||||
test \
|
||||
sanity-checks \
|
||||
clean \
|
||||
distclean \
|
||||
changelog \
|
||||
webpack \
|
||||
package-electron \
|
||||
package-cli \
|
||||
cli-develop \
|
||||
installers-all \
|
||||
publish-all \
|
||||
electron-develop
|
||||
electron-develop \
|
||||
electron-test \
|
||||
electron-build
|
||||
|
||||
changelog:
|
||||
versionist
|
||||
|
||||
webpack: build/js/gui.js
|
||||
|
||||
package-electron:
|
||||
TARGET_ARCH=$(TARGET_ARCH) build --dir $(ELECTRON_BUILDER_OPTIONS)
|
||||
|
||||
package-cli: $(BUILD_DIRECTORY)/$(APPLICATION_NAME)-cli-$(APPLICATION_VERSION)-$(PLATFORM)-$(TARGET_ARCH)
|
||||
|
||||
ifeq ($(PLATFORM),darwin)
|
||||
electron-installer-app-zip: $(BUILD_DIRECTORY)/$(APPLICATION_NAME)-$(APPLICATION_VERSION)-mac.zip
|
||||
electron-installer-dmg: $(BUILD_DIRECTORY)/$(APPLICATION_NAME)-$(APPLICATION_VERSION).dmg
|
||||
cli-installer-tar-gz: $(BUILD_DIRECTORY)/$(APPLICATION_NAME)-cli-$(APPLICATION_VERSION)-$(PLATFORM)-$(TARGET_ARCH).tar.gz
|
||||
TARGETS += \
|
||||
electron-installer-dmg \
|
||||
electron-installer-app-zip \
|
||||
cli-installer-tar-gz
|
||||
PUBLISH_AWS_S3 += \
|
||||
$(BUILD_DIRECTORY)/$(APPLICATION_NAME)-$(APPLICATION_VERSION)-mac.zip \
|
||||
$(BUILD_DIRECTORY)/$(APPLICATION_NAME)-$(APPLICATION_VERSION).dmg \
|
||||
$(BUILD_DIRECTORY)/$(APPLICATION_NAME)-cli-$(APPLICATION_VERSION)-$(PLATFORM)-$(TARGET_ARCH).tar.gz
|
||||
endif
|
||||
|
||||
ifeq ($(PLATFORM),linux)
|
||||
electron-installer-appimage: $(BUILD_DIRECTORY)/$(APPLICATION_NAME_LOWERCASE)-$(APPLICATION_VERSION)-$(PLATFORM)-$(TARGET_ARCH_APPIMAGE).zip
|
||||
electron-installer-debian: $(BUILD_DIRECTORY)/$(APPLICATION_NAME_ELECTRON)_$(APPLICATION_VERSION_DEBIAN)_$(TARGET_ARCH_DEBIAN).deb
|
||||
electron-installer-redhat: $(BUILD_DIRECTORY)/$(APPLICATION_NAME_ELECTRON)-$(APPLICATION_VERSION_REDHAT).$(TARGET_ARCH_REDHAT).rpm
|
||||
cli-installer-tar-gz: $(BUILD_DIRECTORY)/$(APPLICATION_NAME)-cli-$(APPLICATION_VERSION)-$(PLATFORM)-$(TARGET_ARCH).tar.gz
|
||||
TARGETS += \
|
||||
electron-installer-appimage \
|
||||
electron-installer-debian \
|
||||
electron-installer-redhat \
|
||||
cli-installer-tar-gz
|
||||
PUBLISH_AWS_S3 += \
|
||||
$(BUILD_DIRECTORY)/$(APPLICATION_NAME_LOWERCASE)-$(APPLICATION_VERSION)-$(PLATFORM)-$(TARGET_ARCH_APPIMAGE).zip \
|
||||
$(BUILD_DIRECTORY)/$(APPLICATION_NAME)-cli-$(APPLICATION_VERSION)-$(PLATFORM)-$(TARGET_ARCH).tar.gz
|
||||
PUBLISH_BINTRAY_DEBIAN += \
|
||||
$(BUILD_DIRECTORY)/$(APPLICATION_NAME_ELECTRON)_$(APPLICATION_VERSION_DEBIAN)_$(TARGET_ARCH_DEBIAN).deb
|
||||
PUBLISH_BINTRAY_REDHAT += \
|
||||
$(BUILD_DIRECTORY)/$(APPLICATION_NAME_ELECTRON)-$(APPLICATION_VERSION_REDHAT).$(TARGET_ARCH_REDHAT).rpm
|
||||
endif
|
||||
|
||||
ifeq ($(PLATFORM),win32)
|
||||
electron-installer-portable: $(BUILD_DIRECTORY)/$(APPLICATION_NAME)-Portable-$(APPLICATION_VERSION)-$(TARGET_ARCH).exe
|
||||
electron-installer-nsis: $(BUILD_DIRECTORY)/$(APPLICATION_NAME)-Setup-$(APPLICATION_VERSION)-$(TARGET_ARCH).exe
|
||||
cli-installer-zip: $(BUILD_DIRECTORY)/$(APPLICATION_NAME)-cli-$(APPLICATION_VERSION)-$(PLATFORM)-$(TARGET_ARCH).zip
|
||||
TARGETS += \
|
||||
electron-installer-portable \
|
||||
electron-installer-nsis \
|
||||
cli-installer-zip
|
||||
PUBLISH_AWS_S3 += \
|
||||
$(BUILD_DIRECTORY)/$(APPLICATION_NAME)-Portable-$(APPLICATION_VERSION)-$(TARGET_ARCH).exe \
|
||||
$(BUILD_DIRECTORY)/$(APPLICATION_NAME)-Setup-$(APPLICATION_VERSION)-$(TARGET_ARCH).exe \
|
||||
$(BUILD_DIRECTORY)/$(APPLICATION_NAME)-cli-$(APPLICATION_VERSION)-$(PLATFORM)-$(TARGET_ARCH).zip
|
||||
endif
|
||||
|
||||
installers-all: $(PUBLISH_AWS_S3) $(PUBLISH_BINTRAY_DEBIAN) $(PUBLISH_BINTRAY_REDHAT)
|
||||
|
||||
ifdef PUBLISH_AWS_S3
|
||||
publish-aws-s3: $(PUBLISH_AWS_S3)
|
||||
ifeq ($(RELEASE_TYPE),production)
|
||||
$(foreach publishable,$^,$(call execute-command,./scripts/publish/aws-s3.sh \
|
||||
-f $(publishable) \
|
||||
-b $(S3_BUCKET) \
|
||||
-v $(APPLICATION_VERSION) \
|
||||
-p $(APPLICATION_NAME_LOWERCASE)))
|
||||
endif
|
||||
ifeq ($(RELEASE_TYPE),snapshot)
|
||||
$(foreach publishable,$^,$(call execute-command,./scripts/publish/aws-s3.sh \
|
||||
-f $(publishable) \
|
||||
-b $(S3_BUCKET) \
|
||||
-v $(APPLICATION_VERSION) \
|
||||
-p $(APPLICATION_NAME_LOWERCASE) \
|
||||
-k $(shell date +"%Y-%m-%d")))
|
||||
endif
|
||||
|
||||
PUBLISHABLES += publish-aws-s3
|
||||
TARGETS += publish-aws-s3
|
||||
endif
|
||||
|
||||
ifeq ($(RELEASE_TYPE),production)
|
||||
ifdef PUBLISH_BINTRAY_DEBIAN
|
||||
publish-bintray-debian: $(PUBLISH_BINTRAY_DEBIAN)
|
||||
$(foreach publishable,$^,$(call execute-command,./scripts/publish/bintray.sh \
|
||||
-f $(publishable) \
|
||||
-v $(APPLICATION_VERSION_DEBIAN) \
|
||||
-r $(TARGET_ARCH) \
|
||||
-t $(RELEASE_TYPE) \
|
||||
-o $(BINTRAY_ORGANIZATION) \
|
||||
-p $(BINTRAY_REPOSITORY_DEBIAN) \
|
||||
-c $(BINTRAY_COMPONENT) \
|
||||
-y debian))
|
||||
|
||||
PUBLISHABLES += publish-bintray-debian
|
||||
TARGETS += publish-bintray-debian
|
||||
endif
|
||||
|
||||
ifdef PUBLISH_BINTRAY_REDHAT
|
||||
publish-bintray-redhat: $(PUBLISH_BINTRAY_REDHAT)
|
||||
$(foreach publishable,$^,$(call execute-command,./scripts/publish/bintray.sh \
|
||||
-f $(publishable) \
|
||||
-v $(APPLICATION_VERSION_REDHAT) \
|
||||
-r $(TARGET_ARCH) \
|
||||
-t $(RELEASE_TYPE) \
|
||||
-o $(BINTRAY_ORGANIZATION) \
|
||||
-p $(BINTRAY_REPOSITORY_REDHAT) \
|
||||
-c $(BINTRAY_COMPONENT) \
|
||||
-y redhat))
|
||||
|
||||
PUBLISHABLES += publish-bintray-redhat
|
||||
TARGETS += publish-bintray-redhat
|
||||
endif
|
||||
endif
|
||||
|
||||
publish-all: $(PUBLISHABLES)
|
||||
webpack:
|
||||
./node_modules/.bin/webpack
|
||||
|
||||
.PHONY: $(TARGETS)
|
||||
|
||||
cli-develop:
|
||||
./scripts/build/dependencies-npm.sh \
|
||||
-r "$(TARGET_ARCH)" \
|
||||
-v "$(NODE_VERSION)" \
|
||||
-t node \
|
||||
-s "$(PLATFORM)"
|
||||
|
||||
electron-develop:
|
||||
./scripts/build/dependencies-npm.sh \
|
||||
-r "$(TARGET_ARCH)" \
|
||||
-v "$(ELECTRON_VERSION)" \
|
||||
-t electron \
|
||||
-s "$(PLATFORM)"
|
||||
|
||||
sass:
|
||||
npm rebuild node-sass
|
||||
node-sass lib/gui/app/scss/main.scss > lib/gui/css/main.css
|
||||
|
||||
lint-ts:
|
||||
resin-lint --typescript lib
|
||||
|
||||
lint-js:
|
||||
eslint lib tests scripts bin versionist.conf.js webpack.config.js
|
||||
eslint --ignore-pattern scripts/resin/**/*.js lib tests scripts bin webpack.config.js
|
||||
|
||||
lint-sass:
|
||||
sass-lint lib/gui/scss
|
||||
@@ -553,13 +169,15 @@ lint-spell:
|
||||
codespell \
|
||||
--dictionary - \
|
||||
--dictionary dictionary.txt \
|
||||
--skip *.gz,*.bz2,*.xz,*.zip,*.img,*.dmg,*.iso,*.rpi-sdcard,.DS_Store,*.dtb,*.dtbo,*.dat,*.elf,*.bin,*.foo,xz-without-extension \
|
||||
--skip *.svg *.gz,*.bz2,*.xz,*.zip,*.img,*.dmg,*.iso,*.rpi-sdcard,*.wic,.DS_Store,*.dtb,*.dtbo,*.dat,*.elf,*.bin,*.foo,xz-without-extension \
|
||||
lib tests docs scripts Makefile *.md LICENSE
|
||||
|
||||
lint: lint-js lint-sass lint-cpp lint-html lint-spell
|
||||
lint: lint-ts lint-js lint-sass lint-cpp lint-html lint-spell
|
||||
|
||||
MOCHA_OPTIONS=--recursive --reporter spec
|
||||
MOCHA_OPTIONS=--recursive --reporter spec --require ts-node/register
|
||||
|
||||
# See https://github.com/electron/spectron/issues/127
|
||||
ETCHER_SPECTRON_ENTRYPOINT ?= $(shell node -e 'console.log(require("electron"))')
|
||||
test-spectron:
|
||||
ETCHER_SPECTRON_ENTRYPOINT="$(ETCHER_SPECTRON_ENTRYPOINT)" mocha $(MOCHA_OPTIONS) tests/spectron
|
||||
|
||||
@@ -568,13 +186,7 @@ test-gui:
|
||||
|
||||
test-sdk:
|
||||
electron-mocha $(MOCHA_OPTIONS) \
|
||||
tests/shared \
|
||||
tests/image-stream
|
||||
|
||||
test-cli:
|
||||
mocha $(MOCHA_OPTIONS) \
|
||||
tests/shared \
|
||||
tests/image-stream
|
||||
tests/shared
|
||||
|
||||
test: test-gui test-sdk test-spectron
|
||||
|
||||
@@ -582,18 +194,13 @@ help:
|
||||
@echo "Available targets: $(TARGETS)"
|
||||
|
||||
info:
|
||||
@echo "Application version : $(APPLICATION_VERSION)"
|
||||
@echo "Release type : $(RELEASE_TYPE)"
|
||||
@echo "Platform : $(PLATFORM)"
|
||||
@echo "Host arch : $(HOST_ARCH)"
|
||||
@echo "Target arch : $(TARGET_ARCH)"
|
||||
|
||||
sanity-checks:
|
||||
./scripts/ci/ensure-staged-sass.sh
|
||||
./scripts/ci/ensure-staged-shrinkwrap.sh
|
||||
./scripts/ci/ensure-npm-dependencies-compatibility.sh
|
||||
./scripts/ci/ensure-npm-valid-dependencies.sh
|
||||
./scripts/ci/ensure-npm-shrinkwrap-versions.sh
|
||||
./scripts/ci/ensure-all-file-extensions-in-gitattributes.sh
|
||||
|
||||
clean:
|
||||
@@ -602,6 +209,8 @@ clean:
|
||||
distclean: clean
|
||||
rm -rf node_modules
|
||||
rm -rf build
|
||||
rm -rf dist
|
||||
rm -rf generated
|
||||
rm -rf $(BUILD_TEMPORARY_DIRECTORY)
|
||||
|
||||
.DEFAULT_GOAL = help
|
||||
|
109
README.md
@@ -1,40 +1,32 @@
|
||||
Etcher
|
||||
======
|
||||
# Etcher
|
||||
|
||||
> Flash OS images to SD cards & USB drives, safely and easily.
|
||||
|
||||
Etcher is a powerful OS image flasher built with web technologies to ensure
|
||||
flashing an SDCard or USB drive is a pleasant and safe experience. It protects
|
||||
you from accidentally writing to your hard-drives, ensures every byte of data
|
||||
was written correctly and much more.
|
||||
was written correctly and much more. It can also flash directly Raspberry Pi devices that support the usbboot protocol
|
||||
|
||||
[](https://etcher.io)
|
||||

|
||||
[](https://travis-ci.org/resin-io/etcher/branches)
|
||||
[](https://ci.appveyor.com/project/resin-io/etcher/branch/master)
|
||||
[](https://david-dm.org/resin-io/etcher)
|
||||
[](https://forums.resin.io/c/etcher)
|
||||
[](https://waffle.io/resin-io/etcher)
|
||||
[](https://balena.io/etcher)
|
||||
[](https://github.com/balena-io/etcher/blob/master/LICENSE)
|
||||
[](https://david-dm.org/balena-io/etcher)
|
||||
[](https://forums.balena.io/c/etcher)
|
||||
|
||||
***
|
||||
|
||||
[**Download**][etcher] | [**Support**][SUPPORT] | [**Documentation**][USER-DOCUMENTATION] | [**Contributing**][CONTRIBUTING] | [**Roadmap**][milestones] | [**CLI**][CLI]
|
||||
[**Download**][etcher] | [**Support**][SUPPORT] | [**Documentation**][USER-DOCUMENTATION] | [**Contributing**][CONTRIBUTING] | [**Roadmap**][milestones]
|
||||
|
||||

|
||||
|
||||
Supported Operating Systems
|
||||
---------------------------
|
||||
## Supported Operating Systems
|
||||
|
||||
- Linux (most distros)
|
||||
- macOS 10.9 and later
|
||||
- macOS 10.10 (Yosemite) and later
|
||||
- Microsoft Windows 7 and later
|
||||
|
||||
Note that Etcher will run on any platform officially supported by
|
||||
[Electron][electron]. Read more in their
|
||||
[documentation][electron-supported-platforms].
|
||||
|
||||
Installers
|
||||
----------
|
||||
## Installers
|
||||
|
||||
Refer to the [downloads page][etcher] for the latest pre-made
|
||||
installers for all supported operating systems.
|
||||
@@ -43,28 +35,28 @@ installers for all supported operating systems.
|
||||
|
||||
1. Add Etcher debian repository:
|
||||
|
||||
```
|
||||
echo "deb https://dl.bintray.com/resin-io/debian stable etcher" | sudo tee /etc/apt/sources.list.d/etcher.list
|
||||
```sh
|
||||
echo "deb https://deb.etcher.io stable etcher" | sudo tee /etc/apt/sources.list.d/balena-etcher.list
|
||||
```
|
||||
|
||||
2. Trust Bintray.com's GPG key:
|
||||
|
||||
```sh
|
||||
sudo apt-key adv --keyserver hkp://pgp.mit.edu:80 --recv-keys 379CE192D401AB61
|
||||
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 379CE192D401AB61
|
||||
```
|
||||
|
||||
3. Update and install:
|
||||
|
||||
```sh
|
||||
sudo apt-get update
|
||||
sudo apt-get install etcher-electron
|
||||
sudo apt-get install balena-etcher-electron
|
||||
```
|
||||
|
||||
##### Uninstall
|
||||
|
||||
```sh
|
||||
sudo apt-get remove etcher-electron
|
||||
sudo rm /etc/apt/sources.list.d/etcher.list
|
||||
sudo apt-get remove balena-etcher-electron
|
||||
sudo rm /etc/apt/sources.list.d/balena-etcher.list
|
||||
sudo apt-get update
|
||||
```
|
||||
#### Redhat (RHEL) and Fedora based Package Repository (GNU/Linux x86/x64)
|
||||
@@ -72,35 +64,47 @@ sudo apt-get update
|
||||
1. Add Etcher rpm repository:
|
||||
|
||||
```sh
|
||||
sudo wget https://bintray.com/resin-io/redhat/rpm -O /etc/yum.repos.d/bintray-resin-io-redhat.repo
|
||||
sudo wget https://balena.io/etcher/static/etcher-rpm.repo -O /etc/yum.repos.d/etcher-rpm.repo
|
||||
```
|
||||
|
||||
2. Update and install:
|
||||
|
||||
```sh
|
||||
sudo yum install -y etcher-electron
|
||||
sudo yum install -y balena-etcher-electron
|
||||
```
|
||||
or
|
||||
```sh
|
||||
sudo dnf install -y etcher-electron
|
||||
sudo dnf install -y balena-etcher-electron
|
||||
```
|
||||
|
||||
##### Uninstall
|
||||
|
||||
```
|
||||
sudo yum remove -y etcher-electron
|
||||
sudo rm /etc/yum.repos.d/bintray-resin-io-redhat.repo
|
||||
```sh
|
||||
sudo yum remove -y balena-etcher-electron
|
||||
sudo rm /etc/yum.repos.d/etcher-rpm.repo
|
||||
sudo yum clean all
|
||||
sudo yum makecache fast
|
||||
```
|
||||
or
|
||||
```
|
||||
sudo dnf remove -y etcher-electron
|
||||
sudo rm /etc/yum.repos.d/bintray-resin-io-redhat.repo
|
||||
```sh
|
||||
sudo dnf remove -y balena-etcher-electron
|
||||
sudo rm /etc/yum.repos.d/etcher-rpm.repo
|
||||
sudo dnf clean all
|
||||
sudo dnf makecache
|
||||
```
|
||||
|
||||
#### Solus (GNU/Linux x64)
|
||||
|
||||
```sh
|
||||
sudo eopkg it etcher
|
||||
```
|
||||
|
||||
##### Uninstall
|
||||
|
||||
```sh
|
||||
sudo eopkg rm etcher
|
||||
```
|
||||
|
||||
#### Brew Cask (macOS)
|
||||
|
||||
Note that the Etcher Cask has to be updated manually to point to new versions,
|
||||
@@ -108,16 +112,16 @@ so it might not refer to the latest version immediately after an Etcher
|
||||
release.
|
||||
|
||||
```sh
|
||||
brew cask install etcher
|
||||
brew cask install balenaetcher
|
||||
```
|
||||
|
||||
##### Uninstall
|
||||
|
||||
```sh
|
||||
brew cask uninstall etcher
|
||||
brew cask uninstall balenaetcher
|
||||
```
|
||||
|
||||
### Chocolatey (Windows)
|
||||
#### Chocolatey (Windows)
|
||||
|
||||
This package is maintained by [@majkinetor](https://github.com/majkinetor), and
|
||||
is kept up to date automatically.
|
||||
@@ -126,25 +130,28 @@ is kept up to date automatically.
|
||||
choco install etcher
|
||||
```
|
||||
|
||||
Support
|
||||
-------
|
||||
##### Uninstall
|
||||
|
||||
```sh
|
||||
choco uninstall etcher
|
||||
```
|
||||
|
||||
## Support
|
||||
|
||||
If you're having any problem, please [raise an issue][newissue] on GitHub and
|
||||
the resin.io team will be happy to help.
|
||||
the balena.io team will be happy to help.
|
||||
|
||||
License
|
||||
-------
|
||||
## License
|
||||
|
||||
Etcher is free software, and may be redistributed under the terms specified in
|
||||
the [license].
|
||||
|
||||
[etcher]: https://etcher.io
|
||||
[electron]: http://electron.atom.io
|
||||
[electron-supported-platforms]: http://electron.atom.io/docs/tutorial/supported-platforms/
|
||||
[SUPPORT]: https://github.com/resin-io/etcher/blob/master/SUPPORT.md
|
||||
[CONTRIBUTING]: https://github.com/resin-io/etcher/blob/master/docs/CONTRIBUTING.md
|
||||
[CLI]: https://github.com/resin-io/etcher/blob/master/docs/CLI.md
|
||||
[USER-DOCUMENTATION]: https://github.com/resin-io/etcher/blob/master/docs/USER-DOCUMENTATION.md
|
||||
[milestones]: https://github.com/resin-io/etcher/milestones
|
||||
[newissue]: https://github.com/resin-io/etcher/issues/new
|
||||
[license]: https://github.com/resin-io/etcher/blob/master/LICENSE
|
||||
[etcher]: https://balena.io/etcher
|
||||
[electron]: https://electronjs.org/
|
||||
[electron-supported-platforms]: https://electronjs.org/docs/tutorial/support#supported-platforms
|
||||
[SUPPORT]: https://github.com/balena-io/etcher/blob/master/SUPPORT.md
|
||||
[CONTRIBUTING]: https://github.com/balena-io/etcher/blob/master/docs/CONTRIBUTING.md
|
||||
[USER-DOCUMENTATION]: https://github.com/balena-io/etcher/blob/master/docs/USER-DOCUMENTATION.md
|
||||
[milestones]: https://github.com/balena-io/etcher/milestones
|
||||
[newissue]: https://github.com/balena-io/etcher/issues/new
|
||||
[license]: https://github.com/balena-io/etcher/blob/master/LICENSE
|
||||
|
10
SUPPORT.md
@@ -8,7 +8,7 @@ Forums
|
||||
------
|
||||
|
||||
We have a [Discourse forum][discourse] which is open to everyone, so please
|
||||
come join us :). Drop us a line there and the resin.io staff and community
|
||||
come join us :). Drop us a line there and the balena.io staff and community
|
||||
users will be happy to assist. Your question might already be answered, so take
|
||||
a look at the existing threads before opening a new one!
|
||||
|
||||
@@ -20,7 +20,7 @@ support:
|
||||
- The operating system you're running Etcher in.
|
||||
|
||||
- Relevant logging output, if any, from DevTools, which you can open by
|
||||
pressing `Ctrl+Alt+I` or `Cmd+Alt+I` depending on your platform.
|
||||
pressing `Ctrl+Shift+I` or `Cmd+Alt+I` depending on your platform.
|
||||
|
||||
GitHub
|
||||
------
|
||||
@@ -29,6 +29,6 @@ If you encounter an issue or have a suggestion, head on over to Etcher's [issue
|
||||
tracker][issues] and if there isn't a ticket covering it, [create
|
||||
one][new-issue].
|
||||
|
||||
[discourse]: https://forums.resin.io/c/etcher
|
||||
[issues]: https://github.com/resin-io/etcher/issues
|
||||
[new-issue]: https://github.com/resin-io/etcher/issues/new
|
||||
[discourse]: https://forums.balena.io/c/etcher
|
||||
[issues]: https://github.com/balena-io/etcher/issues
|
||||
[new-issue]: https://github.com/balena-io/etcher/issues/new
|
||||
|
50
appveyor.yml
@@ -1,50 +0,0 @@
|
||||
# appveyor file
|
||||
# http://www.appveyor.com/docs/appveyor-yml
|
||||
|
||||
image: Visual Studio 2015
|
||||
|
||||
# See https://github.com/electron/spectron#on-appveyor
|
||||
os: unstable
|
||||
|
||||
cache:
|
||||
- C:\Users\appveyor\.node-gyp
|
||||
- '%LOCALAPPDATA%\electron\Cache'
|
||||
- '%LOCALAPPDATA%\electron-builder\cache'
|
||||
- '%AppData%\npm-cache'
|
||||
- '%USERPROFILE%\.pkg-cache'
|
||||
- node_modules -> npm-shrinkwrap.json
|
||||
- C:\ProgramData\chocolatey\bin -> appveyor.yml
|
||||
- C:\ProgramData\chocolatey\lib -> appveyor.yml
|
||||
- C:\Users\appveyor\AppData\Local\Temp\chocolatey -> appveyor.yml
|
||||
|
||||
# what combinations to test
|
||||
environment:
|
||||
global:
|
||||
ELECTRON_NO_ATTACH_CONSOLE: true
|
||||
nodejs_version: "6.10.3"
|
||||
|
||||
platform:
|
||||
- x86
|
||||
- x64
|
||||
|
||||
matrix:
|
||||
fast_finish: true
|
||||
|
||||
install:
|
||||
- ps: Update-NodeJsInstallation $env:nodejs_version $env:Platform
|
||||
- set PATH=C:\Program Files (x86)\Windows Kits\8.1\bin\x86;%PATH%
|
||||
- set PATH=C:\Program Files (x86)\NSIS;%PATH%
|
||||
- set PATH=C:\MinGW\bin;%PATH%
|
||||
- set PATH=C:\MinGW\msys\1.0\bin;%PATH%
|
||||
- bash .\scripts\ci\install.sh -o win32 -r %Platform%
|
||||
|
||||
build: off
|
||||
|
||||
test_script:
|
||||
- node --version
|
||||
- npm --version
|
||||
- bash .\scripts\ci\test.sh -o win32 -r %Platform%
|
||||
- bash .\scripts\ci\build-installers.sh -o win32 -r %Platform%
|
||||
|
||||
deploy_script:
|
||||
- if %APPVEYOR_REPO_BRANCH%==master (bash .\scripts\ci\deploy.sh -o win32 -r %Platform%)
|
BIN
assets/dmg/background.png
Normal file → Executable file
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 38 KiB |
BIN
assets/dmg/background.tiff
Normal file → Executable file
BIN
assets/dmg/background@2x.png
Normal file → Executable file
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 49 KiB |
BIN
assets/icon.icns
Normal file → Executable file
BIN
assets/icon.ico
Normal file → Executable file
Before Width: | Height: | Size: 361 KiB After Width: | Height: | Size: 361 KiB |
BIN
assets/icon.png
Normal file → Executable file
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 5.4 KiB |
BIN
assets/iconset/128x128.png
Normal file → Executable file
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 3.0 KiB |
BIN
assets/iconset/16x16.png
Normal file → Executable file
Before Width: | Height: | Size: 881 B After Width: | Height: | Size: 479 B |
BIN
assets/iconset/256x256.png
Normal file → Executable file
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 5.4 KiB |
BIN
assets/iconset/32x32.png
Normal file → Executable file
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 802 B |
BIN
assets/iconset/48x48.png
Normal file → Executable file
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 1.2 KiB |
BIN
assets/iconset/512x512.png
Normal file → Executable file
Before Width: | Height: | Size: 146 KiB After Width: | Height: | Size: 11 KiB |
@@ -1,2 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
require('../lib/cli/etcher');
|
12
binding.gyp
@@ -17,8 +17,18 @@
|
||||
"libraries": [
|
||||
"-lShell32.lib",
|
||||
],
|
||||
} ]
|
||||
} ],
|
||||
|
||||
[ 'OS=="mac"', {
|
||||
"xcode_settings": {
|
||||
"OTHER_CPLUSPLUSFLAGS": [
|
||||
"-stdlib=libc++"
|
||||
],
|
||||
"OTHER_LDFLAGS": [
|
||||
"-stdlib=libc++"
|
||||
]
|
||||
}
|
||||
} ]
|
||||
],
|
||||
}
|
||||
],
|
||||
|
4
dev-app-update.yml
Normal file
@@ -0,0 +1,4 @@
|
||||
owner: balena-io
|
||||
repo: etcher
|
||||
provider: github
|
||||
updaterCacheDirName: balena-etcher-updater
|
@@ -40,62 +40,18 @@ to submit their work or bug reports.
|
||||
|
||||
These are the main Etcher components, in a nutshell:
|
||||
|
||||
- [Etcher Image Write][etcher-image-write]
|
||||
|
||||
This is the repository that implements the actual procedures to write an image
|
||||
to a raw device and the place where image validation resides. Its main purpose
|
||||
is to abstract the messy details of interacting with raw devices in all major
|
||||
operating systems.
|
||||
|
||||
- [Etcher Image Stream](../lib/image-stream)
|
||||
|
||||
> (Moved from a separate repository into the main Etcher codebase)
|
||||
|
||||
This module converts any kind of input into a readable stream
|
||||
representing the image so it can be plugged to [etcher-image-write]. Inputs
|
||||
that this module might handle could be, for example: a simple image file, a URL
|
||||
to an image, a compressed image, an image inside a ZIP archive, etc. Together
|
||||
with [etcher-image-write], these modules are the building blocks needed to take
|
||||
an image representation to the user's device, the "Etcher's backend".
|
||||
|
||||
- [Drivelist](https://github.com/resin-io-modules/drivelist)
|
||||
- [Drivelist](https://github.com/balena-io-modules/drivelist)
|
||||
|
||||
As the name implies, this module's duty is to detect the connected drives
|
||||
uniformly in all major operating systems, along with valuable metadata, like if
|
||||
a drive is removable or not, to prevent users from trying to write an image to
|
||||
a system drive.
|
||||
|
||||
- [Etcher](https://github.com/resin-io/etcher)
|
||||
- [Etcher](https://github.com/balena-io/etcher)
|
||||
|
||||
This is the *"main repository"*, from which you're reading this from, which is
|
||||
basically the front-end and glue for all previously listed projects.
|
||||
|
||||
Front-ends
|
||||
----------
|
||||
|
||||
The main repository consists of the implementation of the Etcher CLI and the
|
||||
Etcher GUI (the desktop application), located at [`lib/cli/`][cli-dir] and
|
||||
[`lib/gui/`][gui-dir], respectively.
|
||||
|
||||
In fact, the only front-end that interacts directly with Etcher's backend is
|
||||
the CLI. The GUI merely forks the CLI and communicates with its child process
|
||||
to get state information.
|
||||
|
||||
In this sense, you can consider the GUI as being the front-end to the CLI,
|
||||
which is in turn the front-end to the actual image writing functionality.
|
||||
|
||||
As a way to simplify how the GUI forks the CLI in a packaged and distributed
|
||||
context, both the CLI and GUI share the same application entry point. This
|
||||
means that the same Etcher binary can behave as CLI or GUI as needed.
|
||||
|
||||
## Process communication
|
||||
|
||||
As mentioned before, the Etcher GUI forks the CLI and retrieves information
|
||||
from it to update its state. In order to accomplish this, the Etcher CLI
|
||||
contains certain features to ease communication:
|
||||
|
||||
- [Well-documented exit codes.][exit-codes]
|
||||
|
||||
Summary
|
||||
-------
|
||||
|
||||
@@ -106,10 +62,8 @@ since fresh eyes could help unveil things that we take for granted, but should
|
||||
be documented instead!
|
||||
|
||||
[lego-blocks]: https://github.com/sindresorhus/ama/issues/10#issuecomment-117766328
|
||||
[etcher-image-write]: https://github.com/resin-io-modules/etcher-image-write
|
||||
[exit-codes]: https://github.com/resin-io/etcher/blob/master/lib/shared/exit-codes.js
|
||||
[cli-dir]: https://github.com/resin-io/etcher/tree/master/lib/cli
|
||||
[gui-dir]: https://github.com/resin-io/etcher/tree/master/lib/gui
|
||||
[exit-codes]: https://github.com/balena-io/etcher/blob/master/lib/gui/app/modules/exit-codes.js
|
||||
[gui-dir]: https://github.com/balena-io/etcher/tree/master/lib/gui
|
||||
[electron]: http://electron.atom.io
|
||||
[nodejs]: https://nodejs.org
|
||||
[angularjs]: https://angularjs.org
|
||||
|
@@ -1,61 +0,0 @@
|
||||
### macOS and GNU/Linux
|
||||
|
||||
- Extract the `.tar.gz` package by running:
|
||||
|
||||
```sh
|
||||
tar fvx path/to/cli.tar.gz
|
||||
```
|
||||
|
||||
- Move the resulting directory to `/opt/etcher-cli`
|
||||
|
||||
- Add `/opt/etcher-cli` to the `PATH`. For example, add the following to
|
||||
`.bashrc` or `.zshrc`:
|
||||
|
||||
```sh
|
||||
export PATH="$PATH:/opt/etcher-cli"
|
||||
```
|
||||
|
||||
### Windows
|
||||
|
||||
- Unzip the `.zip` package by right-clicking on it and selecting "Extract All"
|
||||
|
||||
- Move the resulting directory to `C:\etcher-cli`
|
||||
|
||||
- Add `C:\etcher-cli` to the `%PATH%`
|
||||
|
||||
- On Windows 10 and Windows 8
|
||||
- Open *Control Panel*
|
||||
- Open *System*
|
||||
- Click the *Advanced system settings* link
|
||||
- Click *Environment Variables*
|
||||
- Find the `PATH` environment variable, and click *Edit*
|
||||
- Append `;C:\etcher-cli` to the environment variable value
|
||||
- Click *OK*
|
||||
|
||||
- On Windows 7
|
||||
- Right-click the *My Computer* icon
|
||||
- Open the *Properties* menu
|
||||
- Open the *Advanced* tab
|
||||
- Click *Environment Variables*
|
||||
- Find the `PATH` environment variable, and click *Edit*
|
||||
- Append `;C:\etcher-cli` to the environment variable value
|
||||
- Click *OK*
|
||||
|
||||
- Re-open `cmd.exe`, or PowerShell
|
||||
|
||||
### Running
|
||||
|
||||
```sh
|
||||
etcher -v
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
```
|
||||
--help, -h show help
|
||||
--version, -v show version number
|
||||
--drive, -d drive
|
||||
--check, -c validate write
|
||||
--yes, -y confirm non-interactively
|
||||
--unmount, -u unmount on success
|
||||
```
|
42
docs/CLI.md
@@ -1,42 +0,0 @@
|
||||
Etcher CLI
|
||||
==========
|
||||
|
||||
The Etcher CLI is a command-line tool that aims to provide all the benefits of
|
||||
the Etcher desktop application in a way that can be run from a terminal, or
|
||||
even used from a script.
|
||||
|
||||
In fact, the Etcher desktop application is simply a wrapper around the CLI,
|
||||
which is the place where the actual writing logic takes place.
|
||||
|
||||
Installing
|
||||
----------
|
||||
|
||||
Head over to [etcher.io/cli][etcher-cli], download the package that corresponds to
|
||||
your operating system, and then follow the installation instructions there.
|
||||
|
||||
Running
|
||||
-------
|
||||
|
||||
```sh
|
||||
etcher -v
|
||||
```
|
||||
|
||||
Options
|
||||
-------
|
||||
|
||||
```
|
||||
--help, -h show help
|
||||
--version, -v show version number
|
||||
--drive, -d drive
|
||||
--check, -c validate write
|
||||
--yes, -y confirm non-interactively
|
||||
--unmount, -u unmount on success
|
||||
```
|
||||
|
||||
Debug mode
|
||||
----------
|
||||
|
||||
You can set the `ETCHER_CLI_DEBUG` environment variable to make the Etcher CLI
|
||||
print error stack traces.
|
||||
|
||||
[etcher-cli]: https://etcher.io/cli
|
@@ -56,11 +56,6 @@ The scope is required for types that make sense, such as `feat`, `fix`,
|
||||
`test`, etc. Certain commit types, such as `chore` might not have a clearly
|
||||
defined scope, in which case its better to omit it.
|
||||
|
||||
When it applies, the scope must be either `GUI` or `CLI`.
|
||||
|
||||
A commit that takes part in both the GUI and CLI scopes, and makes more logical
|
||||
sense that way, might entirely omit the scope.
|
||||
|
||||
Subject
|
||||
-------
|
||||
|
||||
@@ -122,8 +117,8 @@ A commit can include multiple instances of this tag.
|
||||
Examples:
|
||||
|
||||
```
|
||||
Closes: https://github.com/resin-io/etcher/issues/XXX
|
||||
Fixes: https://github.com/resin-io/etcher/issues/XXX
|
||||
Closes: https://github.com/balena-io/etcher/issues/XXX
|
||||
Fixes: https://github.com/balena-io/etcher/issues/XXX
|
||||
```
|
||||
|
||||
### `Change-Type: <type>`
|
||||
@@ -198,7 +193,7 @@ first non compressed extension.
|
||||
|
||||
Change-Type: patch
|
||||
Changelog-Entry: Don't interpret image file name information between dots as image extensions.
|
||||
Fixes: https://github.com/resin-io/etcher/issues/492
|
||||
Fixes: https://github.com/balena-io/etcher/issues/492
|
||||
```
|
||||
|
||||
***
|
||||
@@ -212,8 +207,8 @@ the operating system still thinks the drive has a file system.
|
||||
|
||||
Change-Type: patch
|
||||
Changelog-Entry: Upgrade `etcher-image-write` to v5.0.2.
|
||||
Link: https://github.com/resin-io-modules/etcher-image-write/blob/master/CHANGELOG.md#502---2016-06-27
|
||||
Fixes: https://github.com/resin-io/etcher/issues/531
|
||||
Link: https://github.com/balena-io-modules/etcher-image-write/blob/master/CHANGELOG.md#502---2016-06-27
|
||||
Fixes: https://github.com/balena-io/etcher/issues/531
|
||||
```
|
||||
|
||||
***
|
||||
@@ -243,7 +238,7 @@ re-used by other services.
|
||||
|
||||
Change-Type: minor
|
||||
Changelog-Entry: Check for updates and show a modal prompting the user to download the latest version.
|
||||
Closes: https://github.com/resin-io/etcher/issues/396
|
||||
Closes: https://github.com/balena-io/etcher/issues/396
|
||||
```
|
||||
|
||||
[angular-commit-guidelines]: https://github.com/angular/angular.js/blob/master/CONTRIBUTING.md#commit
|
||||
|
@@ -17,10 +17,11 @@ Developing
|
||||
|
||||
#### Common
|
||||
|
||||
- [NodeJS](https://nodejs.org) (at least v6)
|
||||
- [NodeJS](https://nodejs.org) (at least v6.11)
|
||||
- [Python 2.7](https://www.python.org)
|
||||
- [jq](https://stedolan.github.io/jq/)
|
||||
- [curl](https://curl.haxx.se/)
|
||||
- [npm](https://www.npmjs.com/) (version 6.7)
|
||||
|
||||
```sh
|
||||
pip install -r requirements.txt
|
||||
@@ -51,10 +52,12 @@ The following MinGW packages are required:
|
||||
- `msys-bash`
|
||||
- `msys-coreutils`
|
||||
|
||||
#### OS X
|
||||
#### macOS
|
||||
|
||||
- [XCode](https://developer.apple.com/xcode/) or [XCode Command Line Tools],
|
||||
which can be installed by running `xcode-select --install`.
|
||||
- [Xcode](https://developer.apple.com/xcode/)
|
||||
|
||||
It's not enough to have [Xcode Command Line Tools] installed. Xcode must be installed
|
||||
as well.
|
||||
|
||||
#### Linux
|
||||
|
||||
@@ -63,7 +66,7 @@ which can be installed by running `xcode-select --install`.
|
||||
### Cloning the project
|
||||
|
||||
```sh
|
||||
git clone https://github.com/resin-io/etcher
|
||||
git clone --recursive https://github.com/balena-io/etcher
|
||||
cd etcher
|
||||
```
|
||||
|
||||
@@ -93,12 +96,6 @@ make webpack
|
||||
npm start
|
||||
```
|
||||
|
||||
#### CLI
|
||||
|
||||
```sh
|
||||
node bin/etcher
|
||||
```
|
||||
|
||||
Testing
|
||||
-------
|
||||
|
||||
@@ -204,7 +201,7 @@ Before your pull request can be merged, the following conditions must hold:
|
||||
|
||||
- The linter doesn't throw any warning.
|
||||
|
||||
- All the tests passes.
|
||||
- All the tests pass.
|
||||
|
||||
- The coding style aligns with the project's convention.
|
||||
|
||||
@@ -213,9 +210,9 @@ systems we support.
|
||||
|
||||
Don't hesitate to get in touch if you have any questions or need any help!
|
||||
|
||||
[ARCHITECTURE]: https://github.com/resin-io/etcher/blob/master/docs/ARCHITECTURE.md
|
||||
[COMMIT-GUIDELINES]: https://github.com/resin-io/etcher/blob/master/docs/COMMIT-GUIDELINES.md
|
||||
[ARCHITECTURE]: https://github.com/balena-io/etcher/blob/master/docs/ARCHITECTURE.md
|
||||
[COMMIT-GUIDELINES]: https://github.com/balena-io/etcher/blob/master/docs/COMMIT-GUIDELINES.md
|
||||
[EditorConfig]: http://editorconfig.org
|
||||
[shrinkwrap]: https://docs.npmjs.com/cli/shrinkwrap
|
||||
[hxd]: https://github.com/jhermsmeier/hxd
|
||||
[XCode Command Line Tools]: https://developer.apple.com/library/content/technotes/tn2339/_index.html
|
||||
[Xcode Command Line Tools]: https://developer.apple.com/library/content/technotes/tn2339/_index.html
|
||||
|
@@ -17,7 +17,7 @@ Releasing
|
||||
|
||||
- [Prepare the new version](#preparing-a-new-version)
|
||||
- [Generate build artifacts](#generating-binaries) (binaries, archives, etc.)
|
||||
- [Draft a release on GitHub](https://github.com/resin-io/etcher/releases)
|
||||
- [Draft a release on GitHub](https://github.com/balena-io/etcher/releases)
|
||||
- Upload build artifacts to GitHub release draft
|
||||
|
||||
#### Testing
|
||||
@@ -27,9 +27,10 @@ Releasing
|
||||
|
||||
#### Publishing
|
||||
|
||||
- [Publish release draft on GitHub](https://github.com/resin-io/etcher/releases)
|
||||
- [Post release note to forums](https://forums.resin.io/c/etcher)
|
||||
- [Update the website](https://github.com/resin-io/etcher-homepage)
|
||||
- [Publish release draft on GitHub](https://github.com/balena-io/etcher/releases)
|
||||
- [Post release note to forums](https://forums.balena.io/c/etcher)
|
||||
- [Submit Windows binaries to Symantec for whitelisting](#submitting-binaries-to-symantec)
|
||||
- [Update the website](https://github.com/balena-io/etcher-homepage)
|
||||
- Wait 2-3 hours for analytics (Sentry, Mixpanel) to trickle in and check for elevated error rates, or regressions
|
||||
- If regressions arise; pull the release, and release a patched version, else:
|
||||
- [Upload deb & rpm packages to Bintray](#uploading-packages-to-bintray)
|
||||
@@ -39,39 +40,6 @@ Releasing
|
||||
- Write a blog post about it, and / or
|
||||
- Write about it to the Etcher mailing list
|
||||
|
||||
### Preparing a New Version
|
||||
|
||||
- Create & hop onto a new release branch, i.e. `release-1.0.0`
|
||||
- Bump the version number in the `package.json`'s `version` property.
|
||||
- Bump the version number in the `npm-shrinkwrap.json`'s `version` property
|
||||
- Add a new entry to `CHANGELOG.md` by running `make changelog`
|
||||
- Manually revise the `CHANGELOG.md` versionist output
|
||||
- Update `screenshot.png` so it displays the latest version in the bottom
|
||||
right corner
|
||||
- Revise the `updates.semverRange` version in `package.json`
|
||||
- Commit the changes with the version number as the commit title, including the `v` prefix, to `master`. For example:
|
||||
|
||||
**NOTE:** The version **MUST** be prefixed with a "v"
|
||||
|
||||
```bash
|
||||
git commit -m "v1.0.0" # not 1.0.0
|
||||
```
|
||||
|
||||
- Create an annotated tag for the new version. The commit title should equal the annotated tag name. For example:
|
||||
|
||||
```bash
|
||||
git tag -a v1.0.0 -m "v1.0.0"
|
||||
```
|
||||
|
||||
- Push the commit and the annotated tag.
|
||||
|
||||
```bash
|
||||
git push
|
||||
git push --tags
|
||||
```
|
||||
|
||||
- Open a pull request against `master` titled "Release v1.0.0"
|
||||
|
||||
### Generating binaries
|
||||
|
||||
**Environment**
|
||||
@@ -104,8 +72,6 @@ export ANALYTICS_MIXPANEL_TOKEN="xxxxxx"
|
||||
./scripts/build/docker/run-command.sh -r x64 -s . -c "make electron-develop && make RELEASE_TYPE=production electron-installer-redhat"
|
||||
# Build AppImages
|
||||
./scripts/build/docker/run-command.sh -r x64 -s . -c "make electron-develop && make RELEASE_TYPE=production electron-installer-appimage"
|
||||
# Build CLI
|
||||
./scripts/build/docker/run-command.sh -r x64 -s . -c "make electron-develop && make RELEASE_TYPE=production cli-installer-tar-gz"
|
||||
|
||||
# x86
|
||||
|
||||
@@ -115,8 +81,6 @@ export ANALYTICS_MIXPANEL_TOKEN="xxxxxx"
|
||||
./scripts/build/docker/run-command.sh -r x86 -s . -c "make electron-develop && make RELEASE_TYPE=production electron-installer-redhat"
|
||||
# Build AppImages
|
||||
./scripts/build/docker/run-command.sh -r x86 -s . -c "make electron-develop && make RELEASE_TYPE=production electron-installer-appimage"
|
||||
# Build CLI
|
||||
./scripts/build/docker/run-command.sh -r x86 -s . -c "make electron-develop && make RELEASE_TYPE=production cli-installer-tar-gz"
|
||||
```
|
||||
|
||||
#### Mac OS
|
||||
@@ -124,13 +88,9 @@ export ANALYTICS_MIXPANEL_TOKEN="xxxxxx"
|
||||
**ATTENTION:** For production releases you'll need the code-signing key,
|
||||
and set `CSC_NAME` to generate signed binaries on Mac OS.
|
||||
|
||||
**NOTE:** The CLI is not code-signed for either at this time.
|
||||
|
||||
```bash
|
||||
make electron-develop
|
||||
|
||||
# Build the CLI
|
||||
make RELEASE_TYPE=production cli-installer-tar-gz
|
||||
# Build the zip
|
||||
make RELEASE_TYPE=production electron-installer-app-zip
|
||||
# Build the dmg
|
||||
@@ -143,14 +103,11 @@ make RELEASE_TYPE=production electron-installer-dmg
|
||||
and set `CSC_LINK`, and `CSC_KEY_PASSWORD` to generate signed binaries on Windows.
|
||||
|
||||
**NOTE:**
|
||||
- The CLI is not code-signed for either at this time.
|
||||
- Keep in mind to also generate artifacts for x86, with `TARGET_ARCH=x86`.
|
||||
|
||||
```bash
|
||||
make electron-develop
|
||||
|
||||
# Build the CLI
|
||||
make RELEASE_TYPE=production cli-installer-zip
|
||||
# Build the Portable version
|
||||
make RELEASE_TYPE=production electron-installer-portable
|
||||
# Build the Installer
|
||||
@@ -165,10 +122,10 @@ export BINTRAY_API_KEY="youruserapikey"
|
||||
```
|
||||
|
||||
```bash
|
||||
./scripts/publish/bintray.sh -c "etcher" -t "production" -v "1.2.1" -o "resin-io" -p "debian" -y "debian" -r "x64" -f "dist/etcher-electron_1.2.1_amd64.deb"
|
||||
./scripts/publish/bintray.sh -c "etcher" -t "production" -v "1.2.1" -o "resin-io" -p "debian" -y "debian" -r "x86" -f "dist/etcher-electron_1.2.1_i386.deb"
|
||||
./scripts/publish/bintray.sh -c "etcher" -t "production" -v "1.2.1" -o "resin-io" -p "redhat" -y "redhat" -r "x64" -f "dist/etcher-electron-1.2.1.x86_64.rpm"
|
||||
./scripts/publish/bintray.sh -c "etcher" -t "production" -v "1.2.1" -o "resin-io" -p "redhat" -y "redhat" -r "x86" -f "dist/etcher-electron-1.2.1.i686.rpm"
|
||||
./scripts/publish/bintray.sh -c "etcher" -t "production" -v "1.2.1" -o "etcher" -p "debian" -y "debian" -r "x64" -f "dist/etcher-electron_1.2.1_amd64.deb"
|
||||
./scripts/publish/bintray.sh -c "etcher" -t "production" -v "1.2.1" -o "etcher" -p "debian" -y "debian" -r "x86" -f "dist/etcher-electron_1.2.1_i386.deb"
|
||||
./scripts/publish/bintray.sh -c "etcher" -t "production" -v "1.2.1" -o "etcher" -p "redhat" -y "redhat" -r "x64" -f "dist/etcher-electron-1.2.1.x86_64.rpm"
|
||||
./scripts/publish/bintray.sh -c "etcher" -t "production" -v "1.2.1" -o "etcher" -p "redhat" -y "redhat" -r "x86" -f "dist/etcher-electron-1.2.1.i686.rpm"
|
||||
```
|
||||
|
||||
### Uploading binaries to Amazon S3
|
||||
@@ -178,7 +135,7 @@ export S3_KEY="..."
|
||||
```
|
||||
|
||||
```bash
|
||||
./scripts/publish/aws-s3.sh -b "resin-production-downloads" -v "1.2.1" -p "etcher" -f "dist/<filename>"
|
||||
./scripts/publish/aws-s3.sh -b "balena-production-downloads" -v "1.2.1" -p "etcher" -f "dist/<filename>"
|
||||
```
|
||||
|
||||
### Dealing with a Problematic Release
|
||||
@@ -189,7 +146,7 @@ revert the problematic release as soon as possible, until the bugs are fixed.
|
||||
|
||||
You can revert a version by deleting its builds from the S3 bucket and Bintray.
|
||||
Refer to the `Makefile` for the up to date information about the S3 bucket
|
||||
where we push builds to, and get in touch with the resin.io operations team to
|
||||
where we push builds to, and get in touch with the balena.io operations team to
|
||||
get write access to it.
|
||||
|
||||
The Etcher update notifier dialog and the website only show the a certain
|
||||
@@ -203,3 +160,15 @@ aws s3api delete-object --bucket <bucket name> --key <file name>
|
||||
```
|
||||
|
||||
The Bintray dashboard provides an easy way to delete a version's files.
|
||||
|
||||
|
||||
### Submitting binaries to Symantec
|
||||
|
||||
- [Report a Suspected Erroneous Detection](https://submit.symantec.com/false_positive/standard/)
|
||||
- Fill out form:
|
||||
- **Select Submission Type:** "Provide a direct download URL"
|
||||
- **Name of the software being detected:** Etcher
|
||||
- **Name of detection given by Symantec product:** WS.Reputation.1
|
||||
- **Contact name:** Balena.io Ltd
|
||||
- **E-mail address:** hello@etcher.io
|
||||
- **Are you the creator or distributor of the software in question?** Yes
|
||||
|
@@ -52,7 +52,7 @@ Signing
|
||||
### OS X
|
||||
|
||||
1. Get our Apple Developer ID certificate for signing applications distributed
|
||||
outside the Mac App Store from the resin.io Apple account.
|
||||
outside the Mac App Store from the balena.io Apple account.
|
||||
|
||||
2. Install the Developer ID certificate to your Mac's Keychain by double
|
||||
clicking on the certificate file.
|
||||
@@ -62,7 +62,7 @@ packaging for OS X.
|
||||
|
||||
### Windows
|
||||
|
||||
1. Get access to our code signing certificate and decryption key as a resin.io
|
||||
1. Get access to our code signing certificate and decryption key as a balena.io
|
||||
employee by asking for it from the relevant people.
|
||||
|
||||
2. Place the certificate in the root of the Etcher repository naming it
|
||||
@@ -118,7 +118,7 @@ Publishing to S3
|
||||
- [AWS CLI][aws-cli]
|
||||
|
||||
Make sure you have the [AWS CLI tool][aws-cli] installed and configured to
|
||||
access resin.io's production or snapshot S3 bucket.
|
||||
access balena.io's production or snapshot S3 bucket.
|
||||
|
||||
Run the following command to publish all files for the current combination of
|
||||
_platform_ and _arch_ (building them if necessary):
|
||||
@@ -128,7 +128,7 @@ make publish-aws-s3
|
||||
```
|
||||
|
||||
Also add links to each AWS S3 file in [GitHub Releases][github-releases]. See
|
||||
[`v1.0.0-beta.17`](https://github.com/resin-io/etcher/releases/tag/v1.0.0-beta.17)
|
||||
[`v1.0.0-beta.17`](https://github.com/balena-io/etcher/releases/tag/v1.0.0-beta.17)
|
||||
as an example.
|
||||
|
||||
Publishing to Homebrew Cask
|
||||
@@ -143,12 +143,12 @@ Publishing to Homebrew Cask
|
||||
Announcing
|
||||
----------
|
||||
|
||||
Post messages to the [Etcher forum][resin-forum-etcher] announcing the new version
|
||||
Post messages to the [Etcher forum][balena-forum-etcher] announcing the new version
|
||||
of Etcher, and including the relevant section of the Changelog.
|
||||
|
||||
[aws-cli]: https://aws.amazon.com/cli
|
||||
[bintray]: https://bintray.com
|
||||
[etcher-cask-file]: https://github.com/caskroom/homebrew-cask/blob/master/Casks/etcher.rb
|
||||
[etcher-cask-file]: https://github.com/caskroom/homebrew-cask/blob/master/Casks/balenaetcher.rb
|
||||
[homebrew-cask]: https://github.com/caskroom/homebrew-cask
|
||||
[resin-forum-etcher]: https://forums.resin.io/c/etcher
|
||||
[github-releases]: https://github.com/resin-io/etcher/releases
|
||||
[balena-forum-etcher]: https://forums.balena.io/c/etcher
|
||||
[github-releases]: https://github.com/balena-io/etcher/releases
|
||||
|
@@ -30,7 +30,7 @@ if you require this functionality, we advise to fallback to
|
||||
Deactivate desktop shortcut prompt on GNU/Linux
|
||||
-----------------------------------------------
|
||||
|
||||
This is a feature provided by [AppImages](appimage), where the applications
|
||||
This is a feature provided by [AppImages][appimage], where the applications
|
||||
prompts the user to automatically register a desktop shortcut to easily access
|
||||
the application.
|
||||
|
||||
@@ -206,20 +206,16 @@ Running in older macOS versions
|
||||
-------------------------------
|
||||
|
||||
Etcher GUI is based on the [Electron][electron] framework, [which only supports
|
||||
macOS 10.9 and newer versions][electron-supported-platforms].
|
||||
macOS 10.10 (Yosemite) and newer versions][electron-supported-platforms].
|
||||
|
||||
You can however, run the [Etcher CLI][etcher-cli], which should work in older
|
||||
platforms.
|
||||
|
||||
[resin.io]: https://resin.io
|
||||
[balena.io]: https://balena.io
|
||||
[appimage]: http://appimage.org
|
||||
[xwayland]: https://wayland.freedesktop.org/xserver.html
|
||||
[weston.ini]: http://manpages.ubuntu.com/manpages/wily/man5/weston.ini.5.html
|
||||
[diskpart]: https://technet.microsoft.com/en-us/library/cc770877(v=ws.11).aspx
|
||||
[electron]: http://electron.atom.io
|
||||
[electron-supported-platforms]: https://github.com/electron/electron/blob/master/docs/tutorial/supported-platforms.md
|
||||
[etcher-cli]: https://github.com/resin-io/etcher/blob/master/docs/CLI.md
|
||||
[publishing]: https://github.com/resin-io/etcher/blob/master/docs/PUBLISHING.md
|
||||
[electron]: https://electronjs.org/
|
||||
[electron-supported-platforms]: https://electronjs.org/docs/tutorial/support#supported-platforms
|
||||
[publishing]: https://github.com/balena-io/etcher/blob/master/docs/PUBLISHING.md
|
||||
[windows-usb-tool]: https://www.microsoft.com/en-us/download/windows-usb-dvd-download-tool
|
||||
[rufus]: https://rufus.akeo.ie
|
||||
[unetbootin]: https://unetbootin.github.io
|
||||
|
@@ -1,12 +1,11 @@
|
||||
appId: io.resin.etcher
|
||||
copyright: Copyright 2016-2018 Resinio Ltd
|
||||
productName: Etcher
|
||||
npmRebuild: false
|
||||
nodeGypRebuild: false
|
||||
appId: io.balena.etcher
|
||||
copyright: Copyright 2016-2019 Balena Ltd
|
||||
productName: balenaEtcher
|
||||
npmRebuild: true
|
||||
nodeGypRebuild: true
|
||||
publish: null
|
||||
files:
|
||||
- lib
|
||||
- "!lib/gui/app"
|
||||
- lib/gui/app/index.html
|
||||
- generated
|
||||
- build/**/*.node
|
||||
@@ -38,15 +37,15 @@ nsis:
|
||||
uninstallerIcon: assets/icon.ico
|
||||
deleteAppDataOnUninstall: true
|
||||
license: LICENSE
|
||||
artifactName: "${productName}-Setup-${version}-${env.TARGET_ARCH}.${ext}"
|
||||
artifactName: "${productName}-Setup-${version}.${ext}"
|
||||
portable:
|
||||
artifactName: "${productName}-Portable-${version}-${env.TARGET_ARCH}.${ext}"
|
||||
artifactName: "${productName}-Portable-${version}.${ext}"
|
||||
requestExecutionLevel: user
|
||||
linux:
|
||||
category: Utility
|
||||
packageCategory: utils
|
||||
executableName: etcher-electron
|
||||
synopsis: Etcher is a powerful OS image flasher built with web technologies to ensure flashing an SDCard or USB drive is a pleasant and safe experience. It protects you from accidentally writing to your hard-drives, ensures every byte of data was written correctly and much more.
|
||||
executableName: balena-etcher-electron
|
||||
synopsis: balenaEtcher is a powerful OS image flasher built with web technologies to ensure flashing an SDCard or USB drive is a pleasant and safe experience. It protects you from accidentally writing to your hard-drives, ensures every byte of data was written correctly and much more.
|
||||
icon: assets/iconset
|
||||
deb:
|
||||
priority: optional
|
||||
@@ -67,7 +66,7 @@ deb:
|
||||
- libgconf-2-4
|
||||
- libgdk-pixbuf2.0-0
|
||||
- libglib2.0-0
|
||||
- libgtk2.0-0
|
||||
- libgtk-3-0
|
||||
- liblzma5
|
||||
- libnotify4
|
||||
- libnspr4
|
||||
|
@@ -1,339 +0,0 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Lesser General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, the commands you use may
|
||||
be called something other than `show w' and `show c'; they could even be
|
||||
mouse-clicks or menu items--whatever suits your program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1989
|
||||
Ty Coon, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License.
|
@@ -1,4 +0,0 @@
|
||||
gpu_mem=16
|
||||
dtoverlay=dwc2,dr_mode=peripheral
|
||||
dtparam=act_led_trigger=none
|
||||
dtparam=act_led_activelow=off
|
@@ -1,30 +0,0 @@
|
||||
Copyright (c) 2006, Broadcom Corporation.
|
||||
Copyright (c) 2015, Raspberry Pi (Trading) Ltd
|
||||
All rights reserved.
|
||||
|
||||
Redistribution. Redistribution and use in binary form, without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* This software may only be used for the purposes of developing for,
|
||||
running or using a Raspberry Pi device.
|
||||
* Redistributions must reproduce the above copyright notice and the
|
||||
following disclaimer in the documentation and/or other materials
|
||||
provided with the distribution.
|
||||
* Neither the name of Broadcom Corporation nor the names of its suppliers
|
||||
may be used to endorse or promote products derived from this software
|
||||
without specific prior written permission.
|
||||
|
||||
DISCLAIMER. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
|
||||
CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
|
||||
BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
|
||||
OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
|
||||
TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
|
||||
USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
|
||||
DAMAGE.
|
||||
|
@@ -1,21 +0,0 @@
|
||||
Etcher CLI
|
||||
==========
|
||||
|
||||
The Etcher CLI is a command line interface to the Etcher writer backend, and
|
||||
currently the only module in the "Etcher" umbrella that makes use of this
|
||||
backend directly.
|
||||
|
||||
This module also has the task of unmounting the drives before and after
|
||||
flashing.
|
||||
|
||||
Notice the Etcher CLI is not worried about elevation, and assumes it has enough
|
||||
permissions to continue, throwing an error otherwise.
|
||||
|
||||
Exit codes
|
||||
----------
|
||||
|
||||
The Etcher CLI uses certain exit codes to signal the result of the operation.
|
||||
These are documented in [`lib/shared/exit-codes.js`][exit-codes] and are also
|
||||
printed on the Etcher CLI help page.
|
||||
|
||||
[exit-codes]: https://github.com/resin-io/etcher/blob/master/lib/shared/exit-codes.js
|
@@ -1,124 +0,0 @@
|
||||
/*
|
||||
* Copyright 2017 resin.io
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
const os = require('os')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const crypto = require('crypto')
|
||||
const childProcess = require('child_process')
|
||||
const debug = require('debug')('etcher:cli:diskpart')
|
||||
const Promise = require('bluebird')
|
||||
const retry = require('bluebird-retry')
|
||||
|
||||
const TMP_RANDOM_BYTES = 6
|
||||
const DISKPART_DELAY = 2000
|
||||
const DISKPART_RETRIES = 5
|
||||
|
||||
/**
|
||||
* @summary Generate a tmp filename with full path of OS' tmp dir
|
||||
* @function
|
||||
* @private
|
||||
*
|
||||
* @param {String} extension - temporary file extension
|
||||
* @returns {String} filename
|
||||
*
|
||||
* @example
|
||||
* const filename = tmpFilename('.sh');
|
||||
*/
|
||||
const tmpFilename = (extension) => {
|
||||
const random = crypto.randomBytes(TMP_RANDOM_BYTES).toString('hex')
|
||||
const filename = `etcher-diskpart-${random}${extension}`
|
||||
return path.join(os.tmpdir(), filename)
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Run a diskpart script
|
||||
* @param {Array<String>} commands - list of commands to run
|
||||
* @param {Function} callback - callback(error)
|
||||
* @example
|
||||
* runDiskpart(['rescan'], (error) => {
|
||||
* ...
|
||||
* })
|
||||
*/
|
||||
const runDiskpart = (commands, callback) => {
|
||||
if (os.platform() !== 'win32') {
|
||||
callback()
|
||||
return
|
||||
}
|
||||
|
||||
const filename = tmpFilename('')
|
||||
const script = commands.join('\r\n')
|
||||
|
||||
fs.writeFile(filename, script, {
|
||||
mode: 0o755
|
||||
}, (writeError) => {
|
||||
debug('write %s:', filename, writeError || 'OK')
|
||||
|
||||
childProcess.exec(`diskpart /s ${filename}`, (execError, stdout, stderr) => {
|
||||
debug('stdout:', stdout)
|
||||
debug('stderr:', stderr)
|
||||
|
||||
fs.unlink(filename, (unlinkError) => {
|
||||
debug('unlink %s:', filename, unlinkError || 'OK')
|
||||
callback(execError)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
||||
/**
|
||||
* @summary Clean a device's partition tables
|
||||
* @param {String} device - device path
|
||||
* @example
|
||||
* diskpart.clean('\\\\.\\PhysicalDrive2')
|
||||
* .then(...)
|
||||
* .catch(...)
|
||||
* @returns {Promise}
|
||||
*/
|
||||
clean (device) {
|
||||
if (os.platform() !== 'win32') {
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
debug('clean', device)
|
||||
|
||||
const pattern = /PHYSICALDRIVE(\d+)/i
|
||||
|
||||
if (pattern.test(device)) {
|
||||
const deviceId = device.match(pattern).pop()
|
||||
return retry(() => {
|
||||
return new Promise((resolve, reject) => {
|
||||
runDiskpart([ `select disk ${deviceId}`, 'clean', 'rescan' ], (error) => {
|
||||
return error ? reject(error) : resolve()
|
||||
})
|
||||
}).delay(DISKPART_DELAY)
|
||||
}, {
|
||||
/* eslint-disable camelcase */
|
||||
max_tries: DISKPART_RETRIES
|
||||
/* eslint-enable camelcase */
|
||||
}).catch((error) => {
|
||||
throw new Error(`Couldn't clean the drive, ${error.failure.message} (code ${error.failure.code})`)
|
||||
})
|
||||
}
|
||||
|
||||
return Promise.reject(new Error(`Invalid device: "${device}"`))
|
||||
}
|
||||
|
||||
}
|
@@ -1,153 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 resin.io
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
const _ = require('lodash')
|
||||
const Bluebird = require('bluebird')
|
||||
const visuals = require('resin-cli-visuals')
|
||||
const form = require('resin-cli-form')
|
||||
const bytes = require('pretty-bytes')
|
||||
const ImageWriter = require('../sdk/writer')
|
||||
const utils = require('./utils')
|
||||
const options = require('./options')
|
||||
const messages = require('../shared/messages')
|
||||
const EXIT_CODES = require('../shared/exit-codes')
|
||||
const errors = require('../shared/errors')
|
||||
const permissions = require('../shared/permissions')
|
||||
|
||||
/* eslint-disable no-magic-numbers */
|
||||
|
||||
const ARGV_IMAGE_PATH_INDEX = 0
|
||||
const imagePath = options._[ARGV_IMAGE_PATH_INDEX]
|
||||
|
||||
permissions.isElevated().then((elevated) => {
|
||||
if (!elevated) {
|
||||
throw errors.createUserError({
|
||||
title: messages.error.elevationRequired(),
|
||||
description: 'This tool requires special permissions to write to external drives'
|
||||
})
|
||||
}
|
||||
|
||||
return form.run([
|
||||
{
|
||||
message: 'Select drive',
|
||||
type: 'drive',
|
||||
name: 'drive'
|
||||
},
|
||||
{
|
||||
message: 'This will erase the selected drive. Are you sure?',
|
||||
type: 'confirm',
|
||||
name: 'yes',
|
||||
default: false
|
||||
}
|
||||
], {
|
||||
override: {
|
||||
drive: options.drive,
|
||||
|
||||
// If `options.yes` is `false`, pass `null`,
|
||||
// otherwise the question will not be asked because
|
||||
// `false` is a defined value.
|
||||
yes: options.yes || null
|
||||
}
|
||||
})
|
||||
}).then((answers) => {
|
||||
if (!answers.yes) {
|
||||
throw errors.createUserError({
|
||||
title: 'Aborted',
|
||||
description: 'We can\'t proceed without confirmation'
|
||||
})
|
||||
}
|
||||
|
||||
const progressBars = {
|
||||
write: new visuals.Progress('Flashing'),
|
||||
check: new visuals.Progress('Validating')
|
||||
}
|
||||
|
||||
return new Bluebird((resolve, reject) => {
|
||||
/**
|
||||
* @summary Progress update handler
|
||||
* @param {Object} state - progress state
|
||||
* @private
|
||||
* @example
|
||||
* writer.on('progress', onProgress)
|
||||
*/
|
||||
const onProgress = (state) => {
|
||||
state.message = state.active > 1
|
||||
? `${bytes(state.totalSpeed)}/s total, ${bytes(state.speed)}/s x ${state.active}`
|
||||
: `${bytes(state.totalSpeed)}/s`
|
||||
|
||||
state.message = `${state.type === 'write' ? 'Flashing' : 'Validating'}: ${state.message}`
|
||||
|
||||
// Update progress bar
|
||||
progressBars[state.type].update(state)
|
||||
}
|
||||
|
||||
const writer = new ImageWriter({
|
||||
verify: options.check,
|
||||
unmountOnSuccess: options.unmount,
|
||||
checksumAlgorithms: options.check ? [ 'sha512' ] : []
|
||||
})
|
||||
|
||||
/**
|
||||
* @summary Finish handler
|
||||
* @private
|
||||
* @example
|
||||
* writer.on('finish', onFinish)
|
||||
*/
|
||||
const onFinish = function () {
|
||||
resolve(Array.from(writer.destinations.values()))
|
||||
}
|
||||
|
||||
writer.on('progress', onProgress)
|
||||
writer.on('error', reject)
|
||||
writer.on('finish', onFinish)
|
||||
|
||||
// NOTE: Drive can be (String|Array)
|
||||
const destinations = [].concat(answers.drive)
|
||||
|
||||
writer.write(imagePath, destinations)
|
||||
})
|
||||
}).then((results) => {
|
||||
let exitCode = EXIT_CODES.SUCCESS
|
||||
|
||||
if (options.check) {
|
||||
console.log('')
|
||||
console.log('Checksums:')
|
||||
|
||||
_.forEach(results, (result) => {
|
||||
if (result.error) {
|
||||
exitCode = EXIT_CODES.GENERAL_ERROR
|
||||
console.log(` - ${result.device.device}: ${result.error.message}`)
|
||||
} else {
|
||||
console.log(` - ${result.device.device}: ${result.checksum.sha512}`)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
process.exit(exitCode)
|
||||
}).catch((error) => {
|
||||
return Bluebird.try(() => {
|
||||
utils.printError(error)
|
||||
return Bluebird.resolve()
|
||||
}).then(() => {
|
||||
if (error.code === 'EVALIDATION') {
|
||||
process.exit(EXIT_CODES.VALIDATION_ERROR)
|
||||
}
|
||||
|
||||
process.exit(EXIT_CODES.GENERAL_ERROR)
|
||||
})
|
||||
})
|
@@ -1,166 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 resin.io
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
const _ = require('lodash')
|
||||
const fs = require('fs')
|
||||
const yargs = require('yargs')
|
||||
const utils = require('./utils')
|
||||
const EXIT_CODES = require('../shared/exit-codes')
|
||||
const errors = require('../shared/errors')
|
||||
const packageJSON = require('../../package.json')
|
||||
|
||||
/**
|
||||
* @summary The minimum required number of CLI arguments
|
||||
* @constant
|
||||
* @private
|
||||
* @type {Number}
|
||||
*/
|
||||
const MINIMUM_NUMBER_OF_ARGUMENTS = 1
|
||||
|
||||
/**
|
||||
* @summary The index of the image argument
|
||||
* @constant
|
||||
* @private
|
||||
* @type {Number}
|
||||
*/
|
||||
const IMAGE_PATH_ARGV_INDEX = 0
|
||||
|
||||
/**
|
||||
* @summary The first index that represents an actual option argument
|
||||
* @constant
|
||||
* @private
|
||||
* @type {Number}
|
||||
*
|
||||
* @description
|
||||
* The first arguments are usually the program executable itself, etc.
|
||||
*/
|
||||
const OPTIONS_INDEX_START = 2
|
||||
|
||||
/**
|
||||
* @summary Parsed CLI options and arguments
|
||||
* @type {Object}
|
||||
* @public
|
||||
*/
|
||||
module.exports = yargs
|
||||
|
||||
// Don't wrap at all
|
||||
.wrap(null)
|
||||
|
||||
.demand(MINIMUM_NUMBER_OF_ARGUMENTS, 'Missing image')
|
||||
|
||||
// Usage help
|
||||
.usage('Usage: $0 [options] <image>')
|
||||
.epilogue([
|
||||
'Exit codes:',
|
||||
_.map(EXIT_CODES, (value, key) => {
|
||||
const reason = _.map(_.split(key, '_'), _.capitalize).join(' ')
|
||||
return ` ${value} - ${reason}`
|
||||
}).join('\n'),
|
||||
'',
|
||||
'If you need help, don\'t hesitate in contacting us at:',
|
||||
'',
|
||||
' GitHub: https://github.com/resin-io/etcher/issues/new',
|
||||
' Forums: https://forums.resin.io/c/etcher'
|
||||
].join('\n'))
|
||||
|
||||
// Examples
|
||||
.example('$0 raspberry-pi.img')
|
||||
.example('$0 --no-check raspberry-pi.img')
|
||||
.example('$0 -d /dev/disk2 ubuntu.iso')
|
||||
.example('$0 -d /dev/disk2 -y rpi.img')
|
||||
|
||||
// Help option
|
||||
.help()
|
||||
|
||||
// Version option
|
||||
.version(packageJSON.version)
|
||||
|
||||
// Error reporting
|
||||
.fail((message, error) => {
|
||||
const errorObject = error || errors.createUserError({
|
||||
title: message
|
||||
})
|
||||
|
||||
yargs.showHelp()
|
||||
utils.printError(errorObject)
|
||||
process.exit(EXIT_CODES.GENERAL_ERROR)
|
||||
})
|
||||
|
||||
// Assert that image exists
|
||||
.check((argv) => {
|
||||
const imagePath = argv._[IMAGE_PATH_ARGV_INDEX]
|
||||
|
||||
try {
|
||||
fs.accessSync(imagePath)
|
||||
} catch (error) {
|
||||
throw errors.createUserError({
|
||||
title: 'Unable to access file',
|
||||
description: `The image ${imagePath} is not accessible`
|
||||
})
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
// Assert that if the `yes` flag is provided, the `drive` flag is also provided.
|
||||
.check((argv) => {
|
||||
if (argv.yes && !argv.drive) {
|
||||
throw errors.createUserError({
|
||||
title: 'Missing drive',
|
||||
description: 'You need to explicitly pass a drive when disabling interactively'
|
||||
})
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
.options({
|
||||
help: {
|
||||
describe: 'show help',
|
||||
boolean: true,
|
||||
alias: 'h'
|
||||
},
|
||||
version: {
|
||||
describe: 'show version number',
|
||||
boolean: true,
|
||||
alias: 'v'
|
||||
},
|
||||
drive: {
|
||||
describe: 'drive',
|
||||
string: true,
|
||||
alias: 'd'
|
||||
},
|
||||
check: {
|
||||
describe: 'validate write',
|
||||
boolean: true,
|
||||
alias: 'c',
|
||||
default: true
|
||||
},
|
||||
yes: {
|
||||
describe: 'confirm non-interactively',
|
||||
boolean: true,
|
||||
alias: 'y'
|
||||
},
|
||||
unmount: {
|
||||
describe: 'unmount on success',
|
||||
boolean: true,
|
||||
alias: 'u',
|
||||
default: true
|
||||
}
|
||||
})
|
||||
.parse(process.argv.slice(OPTIONS_INDEX_START))
|
@@ -27,25 +27,29 @@ var angular = require('angular')
|
||||
/* eslint-enable no-var */
|
||||
|
||||
const electron = require('electron')
|
||||
const Bluebird = require('bluebird')
|
||||
const semver = require('semver')
|
||||
const EXIT_CODES = require('../../shared/exit-codes')
|
||||
const messages = require('../../shared/messages')
|
||||
const s3Packages = require('../../shared/s3-packages')
|
||||
const release = require('../../shared/release')
|
||||
const store = require('../../shared/store')
|
||||
const errors = require('../../shared/errors')
|
||||
const sdk = require('etcher-sdk')
|
||||
const _ = require('lodash')
|
||||
const uuidV4 = require('uuid/v4')
|
||||
|
||||
const EXIT_CODES = require('../../gui/app/modules/exit-codes')
|
||||
const messages = require('../../gui/app/modules/messages')
|
||||
const store = require('./models/store')
|
||||
const packageJSON = require('../../../package.json')
|
||||
const flashState = require('../../shared/models/flash-state')
|
||||
const flashState = require('./models/flash-state')
|
||||
// eslint-disable-next-line node/no-missing-require
|
||||
const settings = require('./models/settings')
|
||||
const windowProgress = require('./os/window-progress')
|
||||
const analytics = require('./modules/analytics')
|
||||
const updateNotifier = require('./components/update-notifier')
|
||||
const availableDrives = require('../../shared/models/available-drives')
|
||||
const selectionState = require('../../shared/models/selection-state')
|
||||
const availableDrives = require('./models/available-drives')
|
||||
const selectionState = require('./models/selection-state')
|
||||
const driveScanner = require('./modules/drive-scanner')
|
||||
const osDialog = require('./os/dialog')
|
||||
const exceptionReporter = require('./modules/exception-reporter')
|
||||
const updateLock = require('./modules/update-lock')
|
||||
// eslint-disable-next-line node/no-missing-require
|
||||
const screensaver = require('./modules/screensaver')
|
||||
|
||||
/* eslint-disable lodash/prefer-lodash-method,lodash/prefer-get */
|
||||
|
||||
// Enable debug information from all modules that use `debug`
|
||||
// See https://github.com/visionmedia/debug#browser-support
|
||||
@@ -55,6 +59,30 @@ const exceptionReporter = require('./modules/exception-reporter')
|
||||
process.env.DRIVELIST_DEBUG = /drivelist|^\*$/i.test(process.env.DEBUG) ? '1' : ''
|
||||
window.localStorage.debug = process.env.DEBUG
|
||||
|
||||
window.addEventListener('unhandledrejection', (event) => {
|
||||
// Promise: event.reason
|
||||
// Bluebird: event.detail.reason
|
||||
// Anything else: event
|
||||
const error = event.reason || (event.detail && event.detail.reason) || event
|
||||
analytics.logException(error)
|
||||
event.preventDefault()
|
||||
})
|
||||
|
||||
// Set application session UUID
|
||||
store.dispatch({
|
||||
type: store.Actions.SET_APPLICATION_SESSION_UUID,
|
||||
data: uuidV4()
|
||||
})
|
||||
|
||||
// Set first flashing workflow UUID
|
||||
store.dispatch({
|
||||
type: store.Actions.SET_FLASHING_WORKFLOW_UUID,
|
||||
data: uuidV4()
|
||||
})
|
||||
|
||||
const applicationSessionUuid = store.getState().toJS().applicationSessionUuid
|
||||
const flashingWorkflowUuid = store.getState().toJS().flashingWorkflowUuid
|
||||
|
||||
const app = angular.module('Etcher', [
|
||||
require('angular-ui-router'),
|
||||
require('angular-ui-bootstrap'),
|
||||
@@ -64,6 +92,7 @@ const app = angular.module('Etcher', [
|
||||
require('./components/svg-icon'),
|
||||
require('./components/warning-modal/warning-modal'),
|
||||
require('./components/safe-webview'),
|
||||
require('./components/file-selector'),
|
||||
|
||||
// Pages
|
||||
require('./pages/main/main'),
|
||||
@@ -88,7 +117,7 @@ app.run(() => {
|
||||
'\\____/ \\__\\___|_| |_|\\___|_|',
|
||||
'',
|
||||
'Interested in joining the Etcher team?',
|
||||
'Drop us a line at join+etcher@resin.io',
|
||||
'Drop us a line at join+etcher@balena.io',
|
||||
'',
|
||||
`Version = ${packageJSON.version}, Type = ${packageJSON.packageType}`
|
||||
].join('\n'))
|
||||
@@ -99,84 +128,13 @@ app.run(() => {
|
||||
|
||||
analytics.logEvent('Application start', {
|
||||
packageType: packageJSON.packageType,
|
||||
version: currentVersion
|
||||
version: currentVersion,
|
||||
applicationSessionUuid
|
||||
})
|
||||
|
||||
const shouldCheckForUpdates = updateNotifier.shouldCheckForUpdates({
|
||||
currentVersion,
|
||||
lastSleptUpdateNotifier: settings.get('lastSleptUpdateNotifier'),
|
||||
lastSleptUpdateNotifierVersion: settings.get('lastSleptUpdateNotifierVersion')
|
||||
})
|
||||
|
||||
const isStableRelease = release.isStableRelease(currentVersion)
|
||||
const updatesEnabled = settings.get('updatesEnabled')
|
||||
|
||||
if (!shouldCheckForUpdates || !updatesEnabled) {
|
||||
analytics.logEvent('Not checking for updates', {
|
||||
shouldCheckForUpdates,
|
||||
updatesEnabled,
|
||||
stable: isStableRelease
|
||||
})
|
||||
|
||||
return Bluebird.resolve()
|
||||
}
|
||||
|
||||
const updateSemverRange = packageJSON.updates.semverRange
|
||||
const includeUnstableChannel = settings.get('includeUnstableUpdateChannel')
|
||||
|
||||
analytics.logEvent('Checking for updates', {
|
||||
currentVersion,
|
||||
stable: isStableRelease,
|
||||
updateSemverRange,
|
||||
includeUnstableChannel
|
||||
})
|
||||
|
||||
return s3Packages.getLatestVersion(release.getReleaseType(currentVersion), {
|
||||
range: updateSemverRange,
|
||||
includeUnstableChannel
|
||||
}).then((latestVersion) => {
|
||||
if (semver.gte(currentVersion, latestVersion || '0.0.0')) {
|
||||
analytics.logEvent('Update notification skipped', {
|
||||
reason: 'Latest version'
|
||||
})
|
||||
return Bluebird.resolve()
|
||||
}
|
||||
|
||||
// In case the internet connection is not good and checking the
|
||||
// latest published version takes too long, only show notify
|
||||
// the user about the new version if he didn't start the flash
|
||||
// process (e.g: selected an image), otherwise such interruption
|
||||
// might be annoying.
|
||||
if (selectionState.hasImage()) {
|
||||
analytics.logEvent('Update notification skipped', {
|
||||
reason: 'Image selected'
|
||||
})
|
||||
return Bluebird.resolve()
|
||||
}
|
||||
|
||||
analytics.logEvent('Notifying update', {
|
||||
latestVersion
|
||||
})
|
||||
|
||||
return updateNotifier.notify(latestVersion, {
|
||||
allowSleepUpdateCheck: isStableRelease
|
||||
})
|
||||
|
||||
// If the error is an update user error, then we don't want
|
||||
// to bother users each time they open the app.
|
||||
// See: https://github.com/resin-io/etcher/issues/1525
|
||||
}).catch((error) => {
|
||||
return errors.isUserError(error) && error.code === 'UPDATE_USER_ERROR'
|
||||
}, (error) => {
|
||||
analytics.logEvent('Update check user error', {
|
||||
title: errors.getTitle(error),
|
||||
description: errors.getDescription(error)
|
||||
})
|
||||
}).catch(exceptionReporter.report)
|
||||
})
|
||||
|
||||
app.run(() => {
|
||||
store.subscribe(() => {
|
||||
store.observe(() => {
|
||||
if (!flashState.isFlashing()) {
|
||||
return
|
||||
}
|
||||
@@ -202,16 +160,160 @@ app.run(() => {
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
* @summary The radix used by USB ID numbers
|
||||
* @type {Number}
|
||||
* @constant
|
||||
*/
|
||||
const USB_ID_RADIX = 16
|
||||
|
||||
/**
|
||||
* @summary The expected length of a USB ID number
|
||||
* @type {Number}
|
||||
* @constant
|
||||
*/
|
||||
const USB_ID_LENGTH = 4
|
||||
|
||||
/**
|
||||
* @summary Convert a USB id (e.g. product/vendor) to a string
|
||||
* @function
|
||||
* @private
|
||||
*
|
||||
* @param {Number} id - USB id
|
||||
* @returns {String} string id
|
||||
*
|
||||
* @example
|
||||
* console.log(usbIdToString(2652))
|
||||
* > '0x0a5c'
|
||||
*/
|
||||
const usbIdToString = (id) => {
|
||||
return `0x${_.padStart(id.toString(USB_ID_RADIX), USB_ID_LENGTH, '0')}`
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Product ID of BCM2708
|
||||
* @type {Number}
|
||||
* @constant
|
||||
*/
|
||||
const USB_PRODUCT_ID_BCM2708_BOOT = 0x2763
|
||||
|
||||
/**
|
||||
* @summary Product ID of BCM2710
|
||||
* @type {Number}
|
||||
* @constant
|
||||
*/
|
||||
const USB_PRODUCT_ID_BCM2710_BOOT = 0x2764
|
||||
|
||||
/**
|
||||
* @summary Compute module descriptions
|
||||
* @type {Object}
|
||||
* @constant
|
||||
*/
|
||||
const COMPUTE_MODULE_DESCRIPTIONS = {
|
||||
[USB_PRODUCT_ID_BCM2708_BOOT]: 'Compute Module 1',
|
||||
[USB_PRODUCT_ID_BCM2710_BOOT]: 'Compute Module 3'
|
||||
}
|
||||
|
||||
app.run(($timeout) => {
|
||||
driveScanner.on('devices', (drives) => {
|
||||
const BLACKLISTED_DRIVES = settings.has('driveBlacklist')
|
||||
? settings.get('driveBlacklist').split(',')
|
||||
: []
|
||||
|
||||
// eslint-disable-next-line require-jsdoc
|
||||
const driveIsAllowed = (drive) => {
|
||||
return !(
|
||||
BLACKLISTED_DRIVES.includes(drive.devicePath) ||
|
||||
BLACKLISTED_DRIVES.includes(drive.device) ||
|
||||
BLACKLISTED_DRIVES.includes(drive.raw)
|
||||
)
|
||||
}
|
||||
|
||||
// eslint-disable-next-line require-jsdoc,consistent-return
|
||||
const prepareDrive = (drive) => {
|
||||
if (drive instanceof sdk.sourceDestination.BlockDevice) {
|
||||
return drive.drive
|
||||
} else if (drive instanceof sdk.sourceDestination.UsbbootDrive) {
|
||||
// This is a workaround etcher expecting a device string and a size
|
||||
drive.device = drive.usbDevice.portId
|
||||
drive.size = null
|
||||
drive.progress = 0
|
||||
drive.disabled = true
|
||||
drive.on('progress', (progress) => {
|
||||
updateDriveProgress(drive, progress)
|
||||
})
|
||||
return drive
|
||||
} else if (drive instanceof sdk.sourceDestination.DriverlessDevice) {
|
||||
const description = COMPUTE_MODULE_DESCRIPTIONS[drive.deviceDescriptor.idProduct] || 'Compute Module'
|
||||
return {
|
||||
device: `${usbIdToString(drive.deviceDescriptor.idVendor)}:${usbIdToString(drive.deviceDescriptor.idProduct)}`,
|
||||
displayName: 'Missing drivers',
|
||||
description,
|
||||
mountpoints: [],
|
||||
isReadOnly: false,
|
||||
isSystem: false,
|
||||
disabled: true,
|
||||
icon: 'warning',
|
||||
size: null,
|
||||
link: 'https://www.raspberrypi.org/documentation/hardware/computemodule/cm-emmc-flashing.md',
|
||||
linkCTA: 'Install',
|
||||
linkTitle: 'Install missing drivers',
|
||||
linkMessage: [
|
||||
'Would you like to download the necessary drivers from the Raspberry Pi Foundation?',
|
||||
'This will open your browser.\n\n',
|
||||
'Once opened, download and run the installer from the "Windows Installer" section to install the drivers.'
|
||||
].join(' ')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line require-jsdoc
|
||||
const setDrives = (drives) => {
|
||||
availableDrives.setDrives(_.values(drives))
|
||||
|
||||
// Safely trigger a digest cycle.
|
||||
// In some cases, AngularJS doesn't acknowledge that the
|
||||
// available drives list has changed, and incorrectly
|
||||
// keeps asking the user to "Connect a drive".
|
||||
$timeout(() => {
|
||||
availableDrives.setDrives(drives)
|
||||
})
|
||||
})
|
||||
$timeout()
|
||||
}
|
||||
|
||||
// eslint-disable-next-line require-jsdoc
|
||||
const getDrives = () => {
|
||||
return _.keyBy(availableDrives.getDrives() || [], 'device')
|
||||
}
|
||||
|
||||
// eslint-disable-next-line require-jsdoc
|
||||
const addDrive = (drive) => {
|
||||
const preparedDrive = prepareDrive(drive)
|
||||
if (!driveIsAllowed(preparedDrive)) {
|
||||
return
|
||||
}
|
||||
const drives = getDrives()
|
||||
drives[preparedDrive.device] = preparedDrive
|
||||
setDrives(drives)
|
||||
}
|
||||
|
||||
// eslint-disable-next-line require-jsdoc
|
||||
const removeDrive = (drive) => {
|
||||
const preparedDrive = prepareDrive(drive)
|
||||
const drives = getDrives()
|
||||
// eslint-disable-next-line prefer-reflect
|
||||
delete drives[preparedDrive.device]
|
||||
setDrives(drives)
|
||||
}
|
||||
|
||||
// eslint-disable-next-line require-jsdoc
|
||||
const updateDriveProgress = (drive, progress) => {
|
||||
const drives = getDrives()
|
||||
const driveInMap = drives[drive.device]
|
||||
if (driveInMap) {
|
||||
driveInMap.progress = progress
|
||||
setDrives(drives)
|
||||
}
|
||||
}
|
||||
|
||||
driveScanner.on('attach', addDrive)
|
||||
driveScanner.on('detach', removeDrive)
|
||||
|
||||
driveScanner.on('error', (error) => {
|
||||
// Stop the drive scanning loop in case of errors,
|
||||
@@ -232,7 +334,8 @@ app.run(($window) => {
|
||||
$window.addEventListener('beforeunload', (event) => {
|
||||
if (!flashState.isFlashing() || popupExists) {
|
||||
analytics.logEvent('Close application', {
|
||||
isFlashing: flashState.isFlashing()
|
||||
isFlashing: flashState.isFlashing(),
|
||||
applicationSessionUuid
|
||||
})
|
||||
return
|
||||
}
|
||||
@@ -243,7 +346,7 @@ app.run(($window) => {
|
||||
// Don't open any more popups
|
||||
popupExists = true
|
||||
|
||||
analytics.logEvent('Close attempt while flashing')
|
||||
analytics.logEvent('Close attempt while flashing', { applicationSessionUuid, flashingWorkflowUuid })
|
||||
|
||||
osDialog.showWarning({
|
||||
confirmationLabel: 'Yes, quit',
|
||||
@@ -253,7 +356,9 @@ app.run(($window) => {
|
||||
}).then((confirmed) => {
|
||||
if (confirmed) {
|
||||
analytics.logEvent('Close confirmed while flashing', {
|
||||
uuid: flashState.getFlashUuid()
|
||||
flashInstanceUuid: flashState.getFlashUuid(),
|
||||
applicationSessionUuid,
|
||||
flashingWorkflowUuid
|
||||
})
|
||||
|
||||
// This circumvents the 'beforeunload' event unlike
|
||||
@@ -261,10 +366,27 @@ app.run(($window) => {
|
||||
electron.remote.process.exit(EXIT_CODES.SUCCESS)
|
||||
}
|
||||
|
||||
analytics.logEvent('Close rejected while flashing')
|
||||
analytics.logEvent('Close rejected while flashing', { applicationSessionUuid, flashingWorkflowUuid })
|
||||
popupExists = false
|
||||
}).catch(exceptionReporter.report)
|
||||
})
|
||||
|
||||
/**
|
||||
* @summary Helper fn for events
|
||||
* @function
|
||||
* @private
|
||||
* @example
|
||||
* window.addEventListener('click', extendLock)
|
||||
*/
|
||||
const extendLock = () => {
|
||||
updateLock.extend()
|
||||
}
|
||||
|
||||
$window.addEventListener('click', extendLock)
|
||||
$window.addEventListener('touchstart', extendLock)
|
||||
|
||||
// Initial update lock acquisition
|
||||
extendLock()
|
||||
})
|
||||
|
||||
app.run(($rootScope) => {
|
||||
@@ -276,7 +398,8 @@ app.run(($rootScope) => {
|
||||
|
||||
analytics.logEvent('Navigate', {
|
||||
to: toState.name,
|
||||
from: fromState.name
|
||||
from: fromState.name,
|
||||
applicationSessionUuid
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -294,6 +417,13 @@ app.config(($provide) => {
|
||||
})
|
||||
})
|
||||
|
||||
app.config(($locationProvider) => {
|
||||
// NOTE(Shou): this seems to invoke a minor perf decrease when set to true
|
||||
$locationProvider.html5Mode({
|
||||
rewriteLinks: false
|
||||
})
|
||||
})
|
||||
|
||||
app.controller('HeaderController', function (OSOpenExternalService) {
|
||||
/**
|
||||
* @summary Open help page
|
||||
@@ -312,6 +442,46 @@ app.controller('HeaderController', function (OSOpenExternalService) {
|
||||
const supportUrl = selectionState.getImageSupportUrl() || DEFAULT_SUPPORT_URL
|
||||
OSOpenExternalService.open(supportUrl)
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Whether to show the help link
|
||||
* @function
|
||||
* @public
|
||||
*
|
||||
* @returns {Boolean}
|
||||
*
|
||||
* @example
|
||||
* HeaderController.shouldShowHelp()
|
||||
*/
|
||||
this.shouldShowHelp = () => {
|
||||
return !settings.get('disableExternalLinks')
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Whether to show the sleep button
|
||||
* @function
|
||||
* @public
|
||||
*
|
||||
* @returns {Boolean}
|
||||
*
|
||||
* @example
|
||||
* HeaderController.shouldShowSleep()
|
||||
*/
|
||||
this.shouldShowSleep = () => {
|
||||
return settings.get('showScreensaverDelay')
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Enables the screensaver
|
||||
* @function
|
||||
* @public
|
||||
*
|
||||
* @example
|
||||
* HeaderController.sleep()
|
||||
*/
|
||||
this.sleep = () => {
|
||||
screensaver.off()
|
||||
}
|
||||
})
|
||||
|
||||
app.controller('StateController', function ($rootScope, $scope) {
|
||||
@@ -365,3 +535,5 @@ angular.element(document).ready(() => {
|
||||
angular.bootstrap(document, [ 'Etcher' ])
|
||||
}).catch(exceptionReporter.report)
|
||||
})
|
||||
|
||||
screensaver.init()
|
||||
|
32
lib/gui/app/components/confirm-modal/confirm-modal.js
Normal file
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2018 resin.io
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
/**
|
||||
* @module Etcher.Components.ConfirmModal
|
||||
*/
|
||||
|
||||
const angular = require('angular')
|
||||
const MODULE_NAME = 'Etcher.Components.ConfirmModal'
|
||||
const ConfirmModal = angular.module(MODULE_NAME, [
|
||||
require('../modal/modal')
|
||||
])
|
||||
|
||||
ConfirmModal.controller('ConfirmModalController', require('./controllers/confirm-modal'))
|
||||
ConfirmModal.service('ConfirmModalService', require('./services/confirm-modal'))
|
||||
|
||||
module.exports = MODULE_NAME
|
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2018 resin.io
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
module.exports = function ($uibModalInstance, options) {
|
||||
/**
|
||||
* @summary Modal options
|
||||
* @type {Object}
|
||||
* @public
|
||||
*/
|
||||
this.options = options
|
||||
|
||||
/**
|
||||
* @summary Reject the warning prompt
|
||||
* @function
|
||||
* @public
|
||||
*
|
||||
* @example
|
||||
* WarningModalController.reject();
|
||||
*/
|
||||
this.reject = () => {
|
||||
$uibModalInstance.close(false)
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Accept the warning prompt
|
||||
* @function
|
||||
* @public
|
||||
*
|
||||
* @example
|
||||
* WarningModalController.accept();
|
||||
*/
|
||||
this.accept = () => {
|
||||
$uibModalInstance.close(true)
|
||||
}
|
||||
}
|
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright 2018 resin.io
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
const _ = require('lodash')
|
||||
|
||||
module.exports = function ($sce, ModalService) {
|
||||
/**
|
||||
* @summary show the confirm modal
|
||||
* @function
|
||||
* @public
|
||||
*
|
||||
* @param {Object} options - options
|
||||
* @param {String} options.description - danger message
|
||||
* @param {String} options.confirmationLabel - confirmation button text
|
||||
* @param {String} options.rejectionLabel - rejection button text
|
||||
* @fulfil {Boolean} - whether the user accepted or rejected the confirm
|
||||
* @returns {Promise}
|
||||
*
|
||||
* @example
|
||||
* ConfirmModalService.show({
|
||||
* description: 'Don\'t do this!',
|
||||
* confirmationLabel: 'Yes, continue!'
|
||||
* });
|
||||
*/
|
||||
this.show = (options = {}) => {
|
||||
options.description = $sce.trustAsHtml(options.description)
|
||||
return ModalService.open({
|
||||
name: 'confirm',
|
||||
template: require('../templates/confirm-modal.tpl.html'),
|
||||
controller: 'ConfirmModalController as modal',
|
||||
size: 'confirm-modal',
|
||||
resolve: {
|
||||
options: _.constant(options)
|
||||
}
|
||||
}).result
|
||||
}
|
||||
}
|
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2016 resin.io
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
.modal-confirm-modal .modal-content {
|
||||
width: 350px;
|
||||
}
|
||||
|
||||
.modal-confirm-modal .modal-title .glyphicon {
|
||||
color: $palette-theme-danger-background;
|
||||
}
|
||||
|
||||
.modal-confirm-modal .modal-body {
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
}
|
@@ -0,0 +1,36 @@
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">
|
||||
<span>{{ ::modal.options.title }}</span>
|
||||
</h4>
|
||||
<button class="close"
|
||||
tabindex="11"
|
||||
ng-click="modal.reject()">×</button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<p>{{ ::modal.options.message }}</p>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<div class="modal-menu">
|
||||
<button ng-if="modal.options.rejectionLabel" class="button button-block"
|
||||
tabindex="12"
|
||||
ng-class="{
|
||||
'button-default': modal.options.cancelButton === 'default',
|
||||
'button-primary': modal.options.cancelButton === 'primary',
|
||||
'button-warning': modal.options.cancelButton === 'warning',
|
||||
'button-danger': modal.options.cancelButton === 'danger',
|
||||
}"
|
||||
ng-click="modal.reject()">{{ ::modal.options.rejectionLabel }}</button>
|
||||
<button class="button button-block"
|
||||
tabindex="13"
|
||||
ng-class="{
|
||||
'button-default': modal.options.confirmButton === 'default',
|
||||
'button-primary': modal.options.confirmButton === 'primary',
|
||||
'button-warning': modal.options.confirmButton === 'warning',
|
||||
'button-danger': modal.options.confirmButton === 'danger',
|
||||
}"
|
||||
ng-click="modal.accept()">{{ ::modal.options.confirmationLabel }}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -18,15 +18,20 @@
|
||||
|
||||
const angular = require('angular')
|
||||
const _ = require('lodash')
|
||||
const constraints = require('../../../../../shared/drive-constraints')
|
||||
const Bluebird = require('bluebird')
|
||||
const constraints = require('../../../../../gui/app/modules/drive-constraints')
|
||||
const store = require('../../../models/store')
|
||||
const analytics = require('../../../modules/analytics')
|
||||
const availableDrives = require('../../../../../shared/models/available-drives')
|
||||
const selectionState = require('../../../../../shared/models/selection-state')
|
||||
const utils = require('../../../../../shared/utils')
|
||||
const availableDrives = require('../../../models/available-drives')
|
||||
const selectionState = require('../../../models/selection-state')
|
||||
// eslint-disable-next-line node/no-missing-require
|
||||
const utils = require('../../../../../gui/app/modules/utils')
|
||||
|
||||
module.exports = function (
|
||||
$q,
|
||||
$uibModalInstance
|
||||
$uibModalInstance,
|
||||
ConfirmModalService,
|
||||
OSOpenExternalService
|
||||
) {
|
||||
/**
|
||||
* @summary The drive selector state
|
||||
@@ -93,14 +98,60 @@ module.exports = function (
|
||||
if (canChangeDriveSelectionState) {
|
||||
analytics.logEvent('Toggle drive', {
|
||||
drive,
|
||||
previouslySelected: selectionState.isCurrentDrive(drive.device)
|
||||
previouslySelected: selectionState.isCurrentDrive(drive.device),
|
||||
applicationSessionUuid: store.getState().toJS().applicationSessionUuid,
|
||||
flashingWorkflowUuid: store.getState().toJS().flashingWorkflowUuid
|
||||
})
|
||||
|
||||
selectionState.toggleDrive(drive.device)
|
||||
}
|
||||
|
||||
return Bluebird.resolve()
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Prompt the user to install missing usbboot drivers
|
||||
* @function
|
||||
* @public
|
||||
*
|
||||
* @param {Object} drive - drive
|
||||
* @returns {Promise} - resolved promise
|
||||
*
|
||||
* @example
|
||||
* DriveSelectorController.installMissingDrivers({
|
||||
* linkTitle: 'Go to example.com',
|
||||
* linkMessage: 'Examples are great, right?',
|
||||
* linkCTA: 'Call To Action',
|
||||
* link: 'https://example.com'
|
||||
* });
|
||||
*/
|
||||
this.installMissingDrivers = (drive) => {
|
||||
if (drive.link) {
|
||||
analytics.logEvent('Open driver link modal', {
|
||||
url: drive.link,
|
||||
applicationSessionUuid: store.getState().toJS().applicationSessionUuid,
|
||||
flashingWorkflowUuid: store.getState().toJS().flashingWorkflowUuid
|
||||
})
|
||||
|
||||
return ConfirmModalService.show({
|
||||
confirmationLabel: 'Yes, continue',
|
||||
rejectionLabel: 'Cancel',
|
||||
title: drive.linkTitle,
|
||||
confirmButton: 'primary',
|
||||
message: drive.linkMessage || `Etcher will open ${drive.link} in your browser`
|
||||
}).then((shouldContinue) => {
|
||||
if (shouldContinue) {
|
||||
OSOpenExternalService.open(drive.link)
|
||||
}
|
||||
}).catch((error) => {
|
||||
analytics.logException(error)
|
||||
})
|
||||
}
|
||||
|
||||
return Bluebird.resolve()
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Close the modal and resolve the selected drive
|
||||
* @function
|
||||
@@ -142,7 +193,10 @@ module.exports = function (
|
||||
if (canChangeDriveSelectionState) {
|
||||
selectionState.selectDrive(drive.device)
|
||||
|
||||
analytics.logEvent('Drive selected (double click)')
|
||||
analytics.logEvent('Drive selected (double click)', {
|
||||
applicationSessionUuid: store.getState().toJS().applicationSessionUuid,
|
||||
flashingWorkflowUuid: store.getState().toJS().flashingWorkflowUuid
|
||||
})
|
||||
|
||||
this.closeModal()
|
||||
}
|
||||
|
@@ -24,7 +24,9 @@ const angular = require('angular')
|
||||
const MODULE_NAME = 'Etcher.Components.DriveSelector'
|
||||
const DriveSelector = angular.module(MODULE_NAME, [
|
||||
require('../modal/modal'),
|
||||
require('../../utils/byte-size/byte-size')
|
||||
require('../confirm-modal/confirm-modal'),
|
||||
require('../../utils/byte-size/byte-size'),
|
||||
require('../../os/open-external/open-external')
|
||||
])
|
||||
|
||||
DriveSelector.controller('DriveSelectorController', require('./controllers/drive-selector'))
|
||||
|
32
lib/gui/app/components/drive-selector/index.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2019 resin.io
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @module Etcher.Components.TargetSelector
|
||||
*/
|
||||
|
||||
import * as angular from 'angular';
|
||||
import { react2angular } from 'react2angular';
|
||||
|
||||
const MODULE_NAME = 'Etcher.Components.TargetSelector';
|
||||
const SelectTargetButton = angular.module(MODULE_NAME, []);
|
||||
|
||||
SelectTargetButton.component(
|
||||
'targetSelector',
|
||||
react2angular(require('./target-selector.jsx')),
|
||||
);
|
||||
|
||||
export = MODULE_NAME;
|
166
lib/gui/app/components/drive-selector/target-selector.jsx
Normal file
@@ -0,0 +1,166 @@
|
||||
/*
|
||||
* Copyright 2019 resin.io
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* eslint-disable no-magic-numbers */
|
||||
|
||||
'use strict'
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const React = require('react')
|
||||
const propTypes = require('prop-types')
|
||||
const { default: styled } = require('styled-components')
|
||||
const {
|
||||
ChangeButton,
|
||||
DetailsText,
|
||||
StepButton,
|
||||
StepNameButton,
|
||||
ThemedProvider
|
||||
} = require('./../../styled-components')
|
||||
const { Txt } = require('rendition')
|
||||
const middleEllipsis = require('./../../utils/middle-ellipsis')
|
||||
const { bytesToClosestUnit } = require('./../../../../gui/app/modules/units')
|
||||
|
||||
const TargetDetail = styled((props) => (
|
||||
<Txt.span {...props}>
|
||||
</Txt.span>
|
||||
)) `
|
||||
float: ${({ float }) => float}
|
||||
`
|
||||
|
||||
const TargetDisplayText = ({
|
||||
description,
|
||||
size,
|
||||
...props
|
||||
}) => {
|
||||
return (
|
||||
<Txt.span {...props}>
|
||||
<TargetDetail
|
||||
float='left'>
|
||||
{description}
|
||||
</TargetDetail>
|
||||
<TargetDetail
|
||||
float='right'
|
||||
>
|
||||
{size}
|
||||
</TargetDetail>
|
||||
</Txt.span>
|
||||
)
|
||||
}
|
||||
|
||||
const TargetSelector = (props) => {
|
||||
const targets = props.selection.getSelectedDrives()
|
||||
|
||||
if (targets.length === 1) {
|
||||
const target = targets[0]
|
||||
return (
|
||||
<ThemedProvider>
|
||||
<StepNameButton
|
||||
plain
|
||||
tooltip={props.tooltip}
|
||||
>
|
||||
{/* eslint-disable no-magic-numbers */}
|
||||
{ middleEllipsis(target.description, 20) }
|
||||
</StepNameButton>
|
||||
{ !props.flashing &&
|
||||
<ChangeButton
|
||||
plain
|
||||
mb={14}
|
||||
onClick={props.reselectDrive}
|
||||
>
|
||||
Change
|
||||
</ChangeButton>
|
||||
}
|
||||
<DetailsText>
|
||||
{ props.constraints.hasListDriveImageCompatibilityStatus(targets, props.image) &&
|
||||
<Txt.span className='glyphicon glyphicon-exclamation-sign'
|
||||
ml={2}
|
||||
tooltip={
|
||||
props.constraints.getListDriveImageCompatibilityStatuses(targets, props.image)[0].message
|
||||
}
|
||||
/>
|
||||
}
|
||||
{ bytesToClosestUnit(target.size) }
|
||||
</DetailsText>
|
||||
</ThemedProvider>
|
||||
)
|
||||
}
|
||||
|
||||
if (targets.length > 1) {
|
||||
const targetsTemplate = []
|
||||
for (const target of targets) {
|
||||
targetsTemplate.push((
|
||||
<DetailsText
|
||||
key={target.device}
|
||||
tooltip={
|
||||
`${target.description} ${target.displayName} ${bytesToClosestUnit(target.size)}`
|
||||
}
|
||||
px={21}
|
||||
>
|
||||
<TargetDisplayText
|
||||
description={middleEllipsis(target.description, 14)}
|
||||
size={bytesToClosestUnit(target.size)}
|
||||
>
|
||||
</TargetDisplayText>
|
||||
</DetailsText>
|
||||
))
|
||||
}
|
||||
return (
|
||||
<ThemedProvider>
|
||||
<StepNameButton
|
||||
plain
|
||||
tooltip={props.tooltip}
|
||||
>
|
||||
{targets.length} Targets
|
||||
</StepNameButton>
|
||||
{ !props.flashing &&
|
||||
<ChangeButton
|
||||
plain
|
||||
onClick={props.reselectDrive}
|
||||
mb={14}
|
||||
>
|
||||
Change
|
||||
</ChangeButton>
|
||||
}
|
||||
{targetsTemplate}
|
||||
</ThemedProvider>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<ThemedProvider>
|
||||
<StepButton
|
||||
tabindex={(targets.length > 0) ? -1 : 2 }
|
||||
disabled={props.disabled}
|
||||
onClick={props.openDriveSelector}
|
||||
>
|
||||
Select target
|
||||
</StepButton>
|
||||
</ThemedProvider>
|
||||
)
|
||||
}
|
||||
|
||||
TargetSelector.propTypes = {
|
||||
disabled: propTypes.bool,
|
||||
openDriveSelector: propTypes.func,
|
||||
selection: propTypes.object,
|
||||
reselectDrive: propTypes.func,
|
||||
flashing: propTypes.bool,
|
||||
constraints: propTypes.object,
|
||||
show: propTypes.bool,
|
||||
tooltip: propTypes.string
|
||||
}
|
||||
|
||||
module.exports = TargetSelector
|
@@ -23,7 +23,8 @@
|
||||
<span class="word-keep"
|
||||
ng-show="drive.size"> - {{ drive.size | closestUnit }}</span>
|
||||
</h4>
|
||||
<p class="list-group-item-text">{{ drive.displayName }}</p>
|
||||
<p class="list-group-item-text" ng-if="!drive.link">{{ drive.displayName }}</p>
|
||||
<p class="list-group-item-text" ng-if="drive.link">{{ drive.displayName }} - <b><a ng-click="modal.installMissingDrivers(drive)">{{ drive.linkCTA }}</a></b></p>
|
||||
|
||||
<footer class="list-group-item-footer">
|
||||
|
||||
@@ -51,7 +52,7 @@
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button class="button button-primary button-block"
|
||||
<button class="button button-primary"
|
||||
tabindex="{{ 15 + modal.getDrives().length }}"
|
||||
ng-class="{
|
||||
'button-warning': modal.constraints.hasListDriveImageCompatibilityStatus(modal.state.getSelectedDrives(), modal.state.getImage())
|
||||
|
264
lib/gui/app/components/drive-selector2/drive-selector.tsx
Normal file
@@ -0,0 +1,264 @@
|
||||
/*
|
||||
* Copyright 2019 resin.io
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Meter } from 'grommet';
|
||||
import { sortBy } from 'lodash';
|
||||
import * as React from 'react';
|
||||
import { Badge, Modal, Table } from 'rendition';
|
||||
|
||||
import { getDrives } from '../../models/available-drives';
|
||||
import { COMPATIBILITY_STATUS_TYPES } from '../../modules/drive-constraints';
|
||||
import { subscribe } from '../../models/store';
|
||||
import { ThemedProvider } from '../../styled-components';
|
||||
import { bytesToClosestUnit } from '../../modules/units';
|
||||
|
||||
interface Drive {
|
||||
description: string;
|
||||
device: string;
|
||||
isSystem: boolean;
|
||||
isReadOnly: boolean;
|
||||
progress?: number;
|
||||
size?: number;
|
||||
link?: string;
|
||||
linkCTA?: string;
|
||||
displayName: string;
|
||||
}
|
||||
|
||||
interface CompatibilityStatus {
|
||||
type: number;
|
||||
message: string;
|
||||
}
|
||||
|
||||
interface DriveSelectorProps {
|
||||
title: string;
|
||||
close: () => void;
|
||||
setSelectedDrives: (drives: Drive[]) => void;
|
||||
isDriveSelected: (drive: Drive) => boolean;
|
||||
isDriveValid: (drive: Drive) => boolean;
|
||||
getDriveBadges: (drive: Drive) => CompatibilityStatus[];
|
||||
}
|
||||
|
||||
interface DriveSelectorState {
|
||||
drives: Drive[];
|
||||
selected: Drive[];
|
||||
disabledDrives: string[];
|
||||
}
|
||||
|
||||
const modalStyle = {
|
||||
width: '800px',
|
||||
height: '600px',
|
||||
paddingTop: '20px',
|
||||
paddingLeft: '30px',
|
||||
paddingRight: '30px',
|
||||
paddingBottom: '11px',
|
||||
};
|
||||
|
||||
const titleStyle = {
|
||||
color: '#2a506f',
|
||||
};
|
||||
|
||||
const subtitleStyle = {
|
||||
marginLeft: '10px',
|
||||
fontSize: '11px',
|
||||
color: '#5b82a7',
|
||||
};
|
||||
|
||||
const wrapperStyle = {
|
||||
height: '250px',
|
||||
overflowX: 'hidden' as 'hidden',
|
||||
overflowY: 'auto' as 'auto',
|
||||
};
|
||||
|
||||
export class DriveSelector2 extends React.Component<
|
||||
DriveSelectorProps,
|
||||
DriveSelectorState
|
||||
> {
|
||||
private table: Table<Drive> | null = null;
|
||||
private columns: {
|
||||
field: keyof Drive;
|
||||
label: string;
|
||||
render?: (value: any, row: Drive) => string | number | JSX.Element | null;
|
||||
}[];
|
||||
private unsubscribe?: () => void;
|
||||
|
||||
constructor(props: DriveSelectorProps) {
|
||||
super(props);
|
||||
this.columns = [
|
||||
{
|
||||
field: 'description',
|
||||
label: 'Name',
|
||||
} as const,
|
||||
{
|
||||
field: 'size',
|
||||
label: 'Size',
|
||||
render: this.renderSize.bind(this),
|
||||
} as const,
|
||||
{
|
||||
field: 'displayName',
|
||||
label: 'Location',
|
||||
render: this.renderLocation.bind(this),
|
||||
} as const,
|
||||
{
|
||||
field: 'isReadOnly', // We don't use this, but a valid field that is not used in another column is required
|
||||
label: ' ',
|
||||
render: this.renderBadges.bind(this),
|
||||
} as const,
|
||||
];
|
||||
this.state = this.getNewState();
|
||||
}
|
||||
|
||||
public componentDidMount() {
|
||||
this.update();
|
||||
if (this.unsubscribe === undefined) {
|
||||
this.unsubscribe = subscribe(this.update.bind(this));
|
||||
}
|
||||
}
|
||||
|
||||
public componentWillUnmount() {
|
||||
if (this.unsubscribe !== undefined) {
|
||||
this.unsubscribe();
|
||||
this.unsubscribe = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private getNewState() {
|
||||
let drives: Drive[] = getDrives();
|
||||
for (let i = 0; i < drives.length; i++) {
|
||||
drives[i] = { ...drives[i] };
|
||||
}
|
||||
drives = sortBy(drives, 'device');
|
||||
const selected = drives.filter(d => this.props.isDriveSelected(d));
|
||||
const disabledDrives = drives
|
||||
.filter(d => !this.props.isDriveValid(d))
|
||||
.map(d => d.device);
|
||||
return { drives, disabledDrives, selected };
|
||||
}
|
||||
|
||||
private update() {
|
||||
this.setState(this.getNewState());
|
||||
this.updateTableSelection();
|
||||
}
|
||||
|
||||
private updateTableSelection() {
|
||||
if (this.table !== null) {
|
||||
this.table.setRowSelection(this.state.selected);
|
||||
}
|
||||
}
|
||||
|
||||
private renderSize(size: number) {
|
||||
if (size) {
|
||||
return bytesToClosestUnit(size);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private renderLocation(displayName: string, drive: Drive) {
|
||||
const result: Array<string | JSX.Element> = [displayName];
|
||||
if (drive.link && drive.linkCTA) {
|
||||
result.push(<a href={drive.link}>{drive.linkCTA}</a>);
|
||||
}
|
||||
return <React.Fragment>{result}</React.Fragment>;
|
||||
}
|
||||
|
||||
private renderBadges(_value: any, row: Drive) {
|
||||
const result = [];
|
||||
if (row.progress !== undefined) {
|
||||
result.push(
|
||||
<Meter
|
||||
size="small"
|
||||
thickness="xxsmall"
|
||||
values={[
|
||||
{
|
||||
value: row.progress,
|
||||
label: row.progress + '%',
|
||||
color: '#2297de',
|
||||
},
|
||||
]}
|
||||
/>,
|
||||
);
|
||||
}
|
||||
result.push(
|
||||
...this.props.getDriveBadges(row).map((status: CompatibilityStatus) => {
|
||||
const props: {
|
||||
key: string;
|
||||
xsmall: true;
|
||||
danger?: boolean;
|
||||
warning?: boolean;
|
||||
} = { xsmall: true, key: status.message };
|
||||
if (status.type === COMPATIBILITY_STATUS_TYPES.ERROR) {
|
||||
props.danger = true;
|
||||
} else if (status.type === COMPATIBILITY_STATUS_TYPES.WARNING) {
|
||||
props.warning = true;
|
||||
}
|
||||
return <Badge {...props}>{status.message}</Badge>;
|
||||
}),
|
||||
);
|
||||
return <React.Fragment>{result}</React.Fragment>;
|
||||
}
|
||||
|
||||
private renderTbodyPrefix() {
|
||||
if (this.state.drives.length === 0) {
|
||||
return (
|
||||
<tr>
|
||||
<td colSpan={this.columns.length} style={{ textAlign: 'center' }}>
|
||||
<b>Connect a drive</b>
|
||||
<div>No removable drive detected.</div>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<ThemedProvider>
|
||||
<Modal
|
||||
titleElement={
|
||||
<div style={titleStyle}>
|
||||
{this.props.title}
|
||||
<span style={subtitleStyle}>
|
||||
{this.state.drives.length} found
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
action={`Select (${this.state.selected.length})`}
|
||||
style={modalStyle}
|
||||
done={this.props.close}
|
||||
>
|
||||
<div style={wrapperStyle}>
|
||||
<Table<Drive>
|
||||
ref={t => {
|
||||
this.table = t;
|
||||
this.updateTableSelection();
|
||||
}}
|
||||
rowKey="device"
|
||||
onCheck={this.onCheck.bind(this)}
|
||||
columns={this.columns}
|
||||
data={this.state.drives}
|
||||
disabledRows={this.state.disabledDrives}
|
||||
tbodyPrefix={this.renderTbodyPrefix()}
|
||||
/>
|
||||
</div>
|
||||
</Modal>
|
||||
</ThemedProvider>
|
||||
);
|
||||
}
|
||||
|
||||
private onCheck(checkedDrives: Drive[]): void {
|
||||
this.props.setSelectedDrives(checkedDrives);
|
||||
}
|
||||
}
|
38
lib/gui/app/components/drive-selector2/index.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2019 resin.io
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import * as angular from 'angular';
|
||||
import { react2angular } from 'react2angular';
|
||||
|
||||
import { DriveSelector2 } from './drive-selector.tsx';
|
||||
|
||||
const MODULE_NAME = 'Etcher.Components.DriveSelector2';
|
||||
|
||||
angular
|
||||
.module(MODULE_NAME, [])
|
||||
.component(
|
||||
'driveSelector2',
|
||||
react2angular(DriveSelector2, [
|
||||
'close',
|
||||
'getDriveBadges',
|
||||
'isDriveSelected',
|
||||
'isDriveValid',
|
||||
'setSelectedDrives',
|
||||
'title',
|
||||
]),
|
||||
);
|
||||
|
||||
export = MODULE_NAME;
|
57
lib/gui/app/components/featured-project/featured-project.jsx
Normal file
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 2016 resin.io
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
const React = require('react')
|
||||
const propTypes = require('prop-types')
|
||||
const SafeWebview = require('../safe-webview/safe-webview.jsx')
|
||||
const settings = require('../../models/settings')
|
||||
const analytics = require('../../modules/analytics')
|
||||
|
||||
class FeaturedProject extends React.Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
endpoint: null
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
return settings.load()
|
||||
.then(() => {
|
||||
const endpoint = settings.get('featuredProjectEndpoint') || 'https://assets.balena.io/etcher-featured/index.html'
|
||||
this.setState({ endpoint })
|
||||
})
|
||||
.catch(analytics.logException)
|
||||
}
|
||||
|
||||
render () {
|
||||
return (this.state.endpoint) ? (
|
||||
<SafeWebview
|
||||
src={this.state.endpoint}
|
||||
{...this.props}>
|
||||
</SafeWebview>
|
||||
) : null
|
||||
}
|
||||
}
|
||||
|
||||
FeaturedProject.propTypes = {
|
||||
onWebviewShow: propTypes.func
|
||||
}
|
||||
|
||||
module.exports = FeaturedProject
|
@@ -17,28 +17,18 @@
|
||||
'use strict'
|
||||
|
||||
/**
|
||||
* @summary ProgressButton directive
|
||||
* @function
|
||||
* @public
|
||||
*
|
||||
* @description
|
||||
* This directive provides a button containing a progress bar inside.
|
||||
* The button is styled by default as a primary button.
|
||||
*
|
||||
* @returns {Object} directive
|
||||
*
|
||||
* @example
|
||||
* <progress-button percentage="{{ 40 }}" striped>My Progress Button</progress-button>
|
||||
* @module Etcher.Components.FeaturedProject
|
||||
*/
|
||||
module.exports = () => {
|
||||
return {
|
||||
template: require('../templates/progress-button.tpl.html'),
|
||||
restrict: 'E',
|
||||
replace: true,
|
||||
transclude: true,
|
||||
scope: {
|
||||
percentage: '=',
|
||||
striped: '@'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const angular = require('angular')
|
||||
const { react2angular } = require('react2angular')
|
||||
|
||||
const MODULE_NAME = 'Etcher.Components.FeaturedProject'
|
||||
const FeaturedProject = angular.module(MODULE_NAME, [])
|
||||
|
||||
FeaturedProject.component(
|
||||
'featuredProject',
|
||||
react2angular(require('./featured-project.jsx'))
|
||||
)
|
||||
|
||||
module.exports = MODULE_NAME
|
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright 2018 resin.io
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
const _ = require('lodash')
|
||||
const os = require('os')
|
||||
// eslint-disable-next-line node/no-missing-require
|
||||
const settings = require('../../../models/settings')
|
||||
// eslint-disable-next-line node/no-missing-require
|
||||
const utils = require('../../../../../gui/app/modules/utils')
|
||||
const angular = require('angular')
|
||||
|
||||
/* eslint-disable lodash/prefer-lodash-method */
|
||||
|
||||
module.exports = function (
|
||||
$uibModalInstance
|
||||
) {
|
||||
/**
|
||||
* @summary Close the modal
|
||||
* @function
|
||||
* @public
|
||||
*
|
||||
* @example
|
||||
* FileSelectorController.close();
|
||||
*/
|
||||
this.close = () => {
|
||||
$uibModalInstance.close()
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Folder to constrain the file picker to
|
||||
* @function
|
||||
* @public
|
||||
*
|
||||
* @returns {String} - folder to constrain by
|
||||
*
|
||||
* @example
|
||||
* FileSelectorController.getFolderConstraint()
|
||||
*/
|
||||
this.getFolderConstraint = utils.memoize(() => {
|
||||
return settings.has('fileBrowserConstraintPath')
|
||||
? settings.get('fileBrowserConstraintPath')
|
||||
: ''
|
||||
}, angular.equals)
|
||||
|
||||
/**
|
||||
* @summary Get initial path
|
||||
* @function
|
||||
* @public
|
||||
*
|
||||
* @returns {String} - path
|
||||
*
|
||||
* @example
|
||||
* <file-selector path="FileSelectorController.getPath()"></file-selector>
|
||||
*/
|
||||
this.getPath = () => {
|
||||
const constraintFolderPath = this.getFolderConstraint()
|
||||
return _.isEmpty(constraintFolderPath) ? os.homedir() : constraintFolderPath
|
||||
}
|
||||
}
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2017 resin.io
|
||||
* Copyright 2018 resin.io
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,32 +16,30 @@
|
||||
|
||||
'use strict'
|
||||
|
||||
const SDK = module.exports
|
||||
|
||||
/**
|
||||
* @summary Initialised adapters
|
||||
* @type {Object<String,Adapter>}
|
||||
* @summary Color scheme
|
||||
* @constant
|
||||
* @private
|
||||
*/
|
||||
SDK.adapters = require('./adapters')
|
||||
|
||||
/**
|
||||
* Adapter Scanner
|
||||
* @see scanner.js
|
||||
* @ignore
|
||||
*/
|
||||
SDK.Scanner = require('./scanner')
|
||||
|
||||
/**
|
||||
* @summary Create a new Scanner
|
||||
* @param {Object} [options] - options
|
||||
* @returns {SDK.Scanner}
|
||||
* @example
|
||||
* SDK.createScanner({
|
||||
* blockdevice: { ... },
|
||||
* usbboot: { ... }
|
||||
* })
|
||||
*/
|
||||
SDK.createScanner = (options) => {
|
||||
return new SDK.Scanner(options)
|
||||
const colors = {
|
||||
primary: {
|
||||
color: '#3a3c41',
|
||||
background: '#ffffff',
|
||||
subColor: '#ababab',
|
||||
faded: '#c3c4c6'
|
||||
},
|
||||
secondary: {
|
||||
color: '#1c1d1e',
|
||||
background: '#ebeff4',
|
||||
title: '#b3b6b9'
|
||||
},
|
||||
highlight: {
|
||||
color: 'white',
|
||||
background: '#2297de'
|
||||
},
|
||||
soft: {
|
||||
color: '#4d5056'
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = colors
|
321
lib/gui/app/components/file-selector/file-selector/file-list.jsx
Normal file
@@ -0,0 +1,321 @@
|
||||
/*
|
||||
* Copyright 2018 resin.io
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
const React = require('react')
|
||||
const propTypes = require('prop-types')
|
||||
const styled = require('styled-components').default
|
||||
const rendition = require('rendition')
|
||||
const colors = require('./colors')
|
||||
|
||||
const prettyBytes = require('pretty-bytes')
|
||||
const files = require('../../../models/files')
|
||||
const middleEllipsis = require('../../../utils/middle-ellipsis')
|
||||
const supportedFormats = require('../../../../../gui/app/modules/supported-formats')
|
||||
|
||||
const debug = require('debug')('etcher:gui:file-selector')
|
||||
|
||||
/**
|
||||
* @summary Character limit of a filename before a middle-ellipsis is added
|
||||
* @constant
|
||||
* @private
|
||||
*/
|
||||
const FILENAME_CHAR_LIMIT = 20
|
||||
|
||||
/**
|
||||
* @summary Pattern to match all supported formats for highlighting
|
||||
* @constant
|
||||
* @private
|
||||
*/
|
||||
const SUPPORTED_FORMATS_PATTERN = new RegExp(`^\\.(${supportedFormats.getAllExtensions().join('|')})$`, 'i')
|
||||
|
||||
/**
|
||||
* @summary Flex styled component
|
||||
* @function
|
||||
* @type {ReactElement}
|
||||
*/
|
||||
const Flex = styled.div`
|
||||
display: flex;
|
||||
flex: ${ props => props.flex };
|
||||
flex-direction: ${ props => props.direction };
|
||||
justify-content: ${ props => props.justifyContent };
|
||||
align-items: ${ props => props.alignItems };
|
||||
flex-wrap: ${ props => props.wrap };
|
||||
flex-grow: ${ props => props.grow };
|
||||
`
|
||||
|
||||
/**
|
||||
* @summary Anchor flex styled component
|
||||
* @function
|
||||
* @type {ReactElement}
|
||||
*/
|
||||
const ClickableFlex = styled.a`
|
||||
display: flex;
|
||||
flex: ${ props => props.flex };
|
||||
flex-direction: ${ props => props.direction };
|
||||
justify-content: ${ props => props.justifyContent };
|
||||
align-items: ${ props => props.alignItems };
|
||||
flex-wrap: ${ props => props.wrap };
|
||||
flex-grow: ${ props => props.grow };
|
||||
`
|
||||
|
||||
/**
|
||||
* @summary FileList scroll wrapper element
|
||||
* @class
|
||||
* @type {ReactElement}
|
||||
*/
|
||||
class UnstyledFileListWrap extends React.PureComponent {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
this.scrollElem = null
|
||||
}
|
||||
|
||||
render () {
|
||||
return (
|
||||
<Flex className={ this.props.className }
|
||||
ref={ ::this.setScrollElem }
|
||||
wrap="wrap">
|
||||
{ this.props.children }
|
||||
</Flex>
|
||||
)
|
||||
}
|
||||
|
||||
setScrollElem (element) {
|
||||
this.scrollElem = element
|
||||
}
|
||||
|
||||
componentDidUpdate (prevProps) {
|
||||
if (this.scrollElem) {
|
||||
this.scrollElem.scrollTop = 0
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary FileList scroll wrapper element
|
||||
* @class
|
||||
* @type {StyledComponent}
|
||||
*/
|
||||
const FileListWrap = styled(UnstyledFileListWrap)`
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
padding: 0 20px;
|
||||
`
|
||||
|
||||
/**
|
||||
* @summary File element
|
||||
* @class
|
||||
* @type {ReactElement}
|
||||
*/
|
||||
class UnstyledFile extends React.PureComponent {
|
||||
|
||||
static getFileIconClass (file) {
|
||||
return file.isDirectory
|
||||
? 'fas fa-folder'
|
||||
: 'fas fa-file-alt'
|
||||
}
|
||||
|
||||
onHighlight (event) {
|
||||
event.preventDefault()
|
||||
this.props.onHighlight(this.props.file)
|
||||
}
|
||||
|
||||
onSelect (event) {
|
||||
event.preventDefault()
|
||||
this.props.onSelect(this.props.file)
|
||||
}
|
||||
|
||||
render () {
|
||||
const file = this.props.file
|
||||
return (
|
||||
<ClickableFlex
|
||||
data-path={ file.path }
|
||||
href={ `file://${file.path}` }
|
||||
direction="column"
|
||||
alignItems="stretch"
|
||||
className={ this.props.className }
|
||||
onClick={ ::this.onHighlight }
|
||||
onDoubleClick={ ::this.onSelect }>
|
||||
<span className={ UnstyledFile.getFileIconClass(file) } />
|
||||
<span>{ middleEllipsis(file.basename, FILENAME_CHAR_LIMIT) }</span>
|
||||
<div>{ file.isDirectory ? '' : prettyBytes(file.size || 0) }</div>
|
||||
</ClickableFlex>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary File element
|
||||
* @class
|
||||
* @type {StyledComponent}
|
||||
*/
|
||||
const File = styled(UnstyledFile)`
|
||||
width: 100px;
|
||||
min-height: 100px;
|
||||
max-height: 128px;
|
||||
margin: 5px 10px;
|
||||
padding: 5px;
|
||||
background-color: none;
|
||||
transition: 0.05s background-color ease-out;
|
||||
color: ${ colors.primary.color };
|
||||
cursor: pointer;
|
||||
border-radius: 5px;
|
||||
word-break: break-word;
|
||||
|
||||
> span:first-of-type {
|
||||
align-self: center;
|
||||
line-height: 1;
|
||||
margin-bottom: 6px;
|
||||
font-size: 48px;
|
||||
color: ${ props => props.disabled ? colors.primary.faded : colors.soft.color };
|
||||
}
|
||||
|
||||
> span:last-of-type {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
> div:last-child {
|
||||
background-color: none;
|
||||
color: ${ colors.primary.subColor };
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
:hover, :visited {
|
||||
color: ${ colors.primary.color };
|
||||
}
|
||||
|
||||
:focus,
|
||||
:active {
|
||||
color: ${ colors.highlight.color };
|
||||
background-color: ${ colors.highlight.background };
|
||||
}
|
||||
|
||||
:focus > span:first-of-type,
|
||||
:active > span:first-of-type {
|
||||
color: ${ colors.highlight.color };
|
||||
}
|
||||
|
||||
:focus > div:last-child,
|
||||
:active > div:last-child {
|
||||
color: ${ colors.highlight.color };
|
||||
}
|
||||
`
|
||||
|
||||
/**
|
||||
* @summary FileList element
|
||||
* @class
|
||||
* @type {ReactElement}
|
||||
*/
|
||||
class FileList extends React.Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
path: props.path,
|
||||
highlighted: null,
|
||||
files: [],
|
||||
}
|
||||
|
||||
debug('FileList', props)
|
||||
}
|
||||
|
||||
readdir (dirname) {
|
||||
debug('FileList:readdir', dirname)
|
||||
|
||||
if (this.props.constraintPath && dirname === '/') {
|
||||
if (this.props.constraint) {
|
||||
const mountpoints = this.props.constraint.mountpoints.map(( mount ) => {
|
||||
const entry = new files.FileEntry(mount.path, {
|
||||
size: 0,
|
||||
isFile: () => false,
|
||||
isDirectory: () => true
|
||||
})
|
||||
entry.name = mount.label
|
||||
return entry
|
||||
})
|
||||
debug('FileList:readdir', mountpoints)
|
||||
window.requestAnimationFrame(() => {
|
||||
this.setState({ files: mountpoints })
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
files.readdirAsync(dirname).then((files) => {
|
||||
window.requestAnimationFrame(() => {
|
||||
this.setState({ files: files })
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
process.nextTick(() => {
|
||||
this.readdir(this.state.path)
|
||||
})
|
||||
}
|
||||
|
||||
onHighlight (file) {
|
||||
debug('FileList:onHighlight', file)
|
||||
this.props.onHighlight(file)
|
||||
}
|
||||
|
||||
onSelect (file) {
|
||||
debug('FileList:onSelect', file.path, file.isDirectory)
|
||||
this.props.onSelect(file)
|
||||
}
|
||||
|
||||
shouldComponentUpdate (nextProps, nextState) {
|
||||
const shouldUpdate = (this.state.files !== nextState.files)
|
||||
debug('FileList:shouldComponentUpdate', shouldUpdate)
|
||||
if (this.props.path !== nextProps.path || this.props.constraint !== nextProps.constraint) {
|
||||
process.nextTick(() => {
|
||||
this.readdir(nextProps.path)
|
||||
})
|
||||
}
|
||||
return shouldUpdate
|
||||
}
|
||||
|
||||
static isSelectable (file) {
|
||||
return file.isDirectory || !file.ext ||
|
||||
SUPPORTED_FORMATS_PATTERN.test(file.ext)
|
||||
}
|
||||
|
||||
render () {
|
||||
return (
|
||||
<FileListWrap wrap="wrap">
|
||||
{
|
||||
this.state.files.map((file) => {
|
||||
return (
|
||||
<File key={ file.path }
|
||||
file={ file }
|
||||
disabled={ !FileList.isSelectable(file) }
|
||||
onSelect={ ::this.onSelect }
|
||||
onHighlight={ ::this.onHighlight }/>
|
||||
)
|
||||
})
|
||||
}
|
||||
</FileListWrap>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = FileList
|
@@ -0,0 +1,358 @@
|
||||
/*
|
||||
* Copyright 2018 resin.io
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
const path = require('path')
|
||||
const sdk = require('etcher-sdk')
|
||||
|
||||
const Bluebird = require('bluebird')
|
||||
const React = require('react')
|
||||
const propTypes = require('prop-types')
|
||||
const styled = require('styled-components').default
|
||||
const rendition = require('rendition')
|
||||
const colors = require('./colors')
|
||||
|
||||
const Breadcrumbs = require('./path-breadcrumbs')
|
||||
const FileList = require('./file-list')
|
||||
const RecentFiles = require('./recent-files')
|
||||
const files = require('../../../models/files')
|
||||
|
||||
const selectionState = require('../../../models/selection-state')
|
||||
const store = require('../../../models/store')
|
||||
const osDialog = require('../../../os/dialog')
|
||||
const exceptionReporter = require('../../../modules/exception-reporter')
|
||||
const messages = require('../../../../../gui/app/modules/messages')
|
||||
const errors = require('../../../../../gui/app/modules/errors')
|
||||
const supportedFormats = require('../../../../../gui/app/modules/supported-formats')
|
||||
const analytics = require('../../../modules/analytics')
|
||||
|
||||
const debug = require('debug')('etcher:gui:file-selector')
|
||||
|
||||
/**
|
||||
* @summary Flex styled component
|
||||
* @function
|
||||
* @type {ReactElement}
|
||||
*/
|
||||
const Flex = styled.div`
|
||||
display: flex;
|
||||
flex: ${ props => props.flex };
|
||||
flex-direction: ${ props => props.direction };
|
||||
justify-content: ${ props => props.justifyContent };
|
||||
align-items: ${ props => props.alignItems };
|
||||
flex-wrap: ${ props => props.wrap };
|
||||
flex-grow: ${ props => props.grow };
|
||||
overflow: ${ props => props.overflow };
|
||||
`
|
||||
|
||||
const Header = styled(Flex) `
|
||||
padding: 10px 15px 0;
|
||||
border-bottom: 1px solid ${ colors.primary.faded };
|
||||
|
||||
> * {
|
||||
margin: 5px;
|
||||
}
|
||||
`
|
||||
|
||||
const Main = styled(Flex) ``
|
||||
|
||||
const Footer = styled(Flex) `
|
||||
padding: 10px;
|
||||
flex: 0 0 auto;
|
||||
border-top: 1px solid ${ colors.primary.faded };
|
||||
|
||||
> * {
|
||||
margin: 0 10px;
|
||||
}
|
||||
|
||||
> button {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
`
|
||||
|
||||
class UnstyledFilePath extends React.PureComponent {
|
||||
render () {
|
||||
return (
|
||||
<div className={ this.props.className }>
|
||||
<span>{
|
||||
this.props.file && !this.props.file.isDirectory
|
||||
? this.props.file.basename
|
||||
: ''
|
||||
}</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const FilePath = styled(UnstyledFilePath)`
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
|
||||
> span {
|
||||
font-size: 16px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
`
|
||||
|
||||
class FileSelector extends React.PureComponent {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
path: props.path,
|
||||
highlighted: null,
|
||||
constraint: null,
|
||||
files: [],
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (this.props.constraintpath) {
|
||||
const device = files.getConstraintDevice(this.props.constraintpath)
|
||||
debug('FileSelector:getConstraintDevice', device)
|
||||
if (device !== undefined) {
|
||||
this.setState({ constraint: device.drive })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
confirmSelection () {
|
||||
if (this.state.highlighted) {
|
||||
this.selectFile(this.state.highlighted)
|
||||
}
|
||||
}
|
||||
|
||||
close () {
|
||||
this.props.close()
|
||||
}
|
||||
|
||||
componentDidUpdate () {
|
||||
debug('FileSelector:componentDidUpdate')
|
||||
}
|
||||
|
||||
containPath (newPath) {
|
||||
if (this.state.constraint) {
|
||||
const isContained = this.state.constraint.mountpoints.some((mount) => {
|
||||
return !path.relative(mount.path, newPath).startsWith('..')
|
||||
})
|
||||
if (!isContained) {
|
||||
return '/'
|
||||
}
|
||||
}
|
||||
return newPath
|
||||
}
|
||||
|
||||
navigate (newPath) {
|
||||
debug('FileSelector:navigate', newPath)
|
||||
this.setState({ path: this.containPath(newPath) })
|
||||
}
|
||||
|
||||
navigateUp () {
|
||||
let newPath = this.containPath(path.join(this.state.path, '..'))
|
||||
debug('FileSelector:navigateUp', this.state.path, '->', newPath)
|
||||
this.setState({ path: newPath })
|
||||
}
|
||||
|
||||
selectImage (image) {
|
||||
debug('FileSelector:selectImage', image)
|
||||
|
||||
if (!supportedFormats.isSupportedImage(image.path)) {
|
||||
const invalidImageError = errors.createUserError({
|
||||
title: 'Invalid image',
|
||||
description: messages.error.invalidImage(image.path)
|
||||
})
|
||||
|
||||
osDialog.showError(invalidImageError)
|
||||
analytics.logEvent('Invalid image', {
|
||||
image,
|
||||
applicationSessionUuid: store.getState().toJS().applicationSessionUuid,
|
||||
flashingWorkflowUuid: store.getState().toJS().flashingWorkflowUuid
|
||||
})
|
||||
return Bluebird.resolve()
|
||||
}
|
||||
|
||||
return Bluebird.try(() => {
|
||||
let message = null
|
||||
|
||||
if (supportedFormats.looksLikeWindowsImage(image.path)) {
|
||||
analytics.logEvent('Possibly Windows image', {
|
||||
image,
|
||||
applicationSessionUuid: store.getState().toJS().applicationSessionUuid,
|
||||
flashingWorkflowUuid: store.getState().toJS().flashingWorkflowUuid
|
||||
})
|
||||
message = messages.warning.looksLikeWindowsImage()
|
||||
} else if (!image.hasMBR) {
|
||||
analytics.logEvent('Missing partition table', {
|
||||
image,
|
||||
applicationSessionUuid: store.getState().toJS().applicationSessionUuid,
|
||||
flashingWorkflowUuid: store.getState().toJS().flashingWorkflowUuid
|
||||
})
|
||||
message = messages.warning.missingPartitionTable()
|
||||
}
|
||||
|
||||
if (message) {
|
||||
// TODO: `Continue` should be on a red background (dangerous action) instead of `Change`.
|
||||
// We want `X` to act as `Continue`, that's why `Continue` is the `rejectionLabel`
|
||||
return osDialog.showWarning({
|
||||
confirmationLabel: 'Change',
|
||||
rejectionLabel: 'Continue',
|
||||
title: 'Warning',
|
||||
description: message
|
||||
})
|
||||
}
|
||||
|
||||
return false
|
||||
}).then((shouldChange) => {
|
||||
if (shouldChange) {
|
||||
return
|
||||
}
|
||||
|
||||
selectionState.selectImage(image)
|
||||
|
||||
this.close()
|
||||
|
||||
// An easy way so we can quickly identify if we're making use of
|
||||
// certain features without printing pages of text to DevTools.
|
||||
image.logo = Boolean(image.logo)
|
||||
image.blockMap = Boolean(image.blockMap)
|
||||
|
||||
analytics.logEvent('Select image', {
|
||||
image,
|
||||
applicationSessionUuid: store.getState().toJS().applicationSessionUuid,
|
||||
flashingWorkflowUuid: store.getState().toJS().flashingWorkflowUuid
|
||||
})
|
||||
}).catch(exceptionReporter.report)
|
||||
}
|
||||
|
||||
selectFile (file) {
|
||||
debug('FileSelector:selectFile', file)
|
||||
|
||||
if (file.isDirectory) {
|
||||
this.navigate(file.path)
|
||||
return
|
||||
}
|
||||
|
||||
if (!supportedFormats.isSupportedImage(file.path)) {
|
||||
const invalidImageError = errors.createUserError({
|
||||
title: 'Invalid image',
|
||||
description: messages.error.invalidImage(file.path)
|
||||
})
|
||||
|
||||
osDialog.showError(invalidImageError)
|
||||
analytics.logEvent('Invalid image', { path: file.path })
|
||||
return
|
||||
}
|
||||
|
||||
debug('FileSelector:getImageMetadata', file)
|
||||
|
||||
const source = new sdk.sourceDestination.File(file.path, sdk.sourceDestination.File.OpenFlags.Read)
|
||||
source.getInnerSource()
|
||||
.then((innerSource) => {
|
||||
return innerSource.getMetadata()
|
||||
.then((imageMetadata) => {
|
||||
debug('FileSelector:getImageMetadata', imageMetadata)
|
||||
imageMetadata.path = file.path
|
||||
imageMetadata.extension = path.extname(file.path).slice(1)
|
||||
return innerSource.getPartitionTable()
|
||||
.then((partitionTable) => {
|
||||
if (partitionTable !== undefined) {
|
||||
imageMetadata.hasMBR = true
|
||||
imageMetadata.partitions = partitionTable.partitions
|
||||
}
|
||||
return this.selectImage(imageMetadata)
|
||||
})
|
||||
})
|
||||
})
|
||||
.catch((error) => {
|
||||
debug('FileSelector:getImageMetadata', error)
|
||||
const imageError = errors.createUserError({
|
||||
title: 'Error opening image',
|
||||
description: messages.error.openImage(path.basename(file.path), error.message)
|
||||
})
|
||||
|
||||
osDialog.showError(imageError)
|
||||
analytics.logException(error)
|
||||
})
|
||||
}
|
||||
|
||||
onHighlight (file) {
|
||||
this.setState({ highlighted: file })
|
||||
}
|
||||
|
||||
render () {
|
||||
const styles = {
|
||||
display: 'flex',
|
||||
height: 'calc(100vh - 20px)',
|
||||
}
|
||||
return (
|
||||
<rendition.Provider style={ styles }>
|
||||
{/*<RecentFiles flex="0 0 auto"
|
||||
selectFile={ ::this.selectFile }
|
||||
navigate={ ::this.navigate } />*/}
|
||||
<Flex direction="column" grow="1" overflow="auto">
|
||||
<Header flex="0 0 auto" alignItems="baseline">
|
||||
<rendition.Button
|
||||
bg={ colors.secondary.background }
|
||||
color={ colors.primary.color }
|
||||
onClick={ ::this.navigateUp }>
|
||||
<span className="fas fa-angle-left" />
|
||||
Back
|
||||
</rendition.Button>
|
||||
<span className="fas fa-hdd" />
|
||||
<Breadcrumbs
|
||||
path={ this.state.path }
|
||||
navigate={ ::this.navigate }
|
||||
constraintPath={ this.props.constraintpath }
|
||||
constraint={ this.state.constraint }
|
||||
/>
|
||||
</Header>
|
||||
<Main flex="1">
|
||||
<Flex direction="column" grow="1">
|
||||
<FileList path={ this.state.path }
|
||||
constraintPath={ this.props.constraintpath }
|
||||
constraint={ this.state.constraint }
|
||||
onHighlight={ ::this.onHighlight }
|
||||
onSelect={ ::this.selectFile }></FileList>
|
||||
</Flex>
|
||||
</Main>
|
||||
<Footer justifyContent="flex-end">
|
||||
<FilePath file={ this.state.highlighted }></FilePath>
|
||||
<rendition.Button onClick={ ::this.close }>Cancel</rendition.Button>
|
||||
<rendition.Button
|
||||
primary
|
||||
onClick={ ::this.confirmSelection }>
|
||||
Select file
|
||||
</rendition.Button>
|
||||
</Footer>
|
||||
</Flex>
|
||||
</rendition.Provider>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
FileSelector.propTypes = {
|
||||
path: propTypes.string,
|
||||
close: propTypes.func,
|
||||
constraintpath: propTypes.string,
|
||||
}
|
||||
|
||||
module.exports = FileSelector
|
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* Copyright 2018 resin.io
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
const path = require('path')
|
||||
|
||||
const React = require('react')
|
||||
const propTypes = require('prop-types')
|
||||
const styled = require('styled-components').default
|
||||
const rendition = require('rendition')
|
||||
|
||||
const middleEllipsis = require('../../../utils/middle-ellipsis')
|
||||
|
||||
/**
|
||||
* @summary How many directories to show with the breadcrumbs
|
||||
* @type {Number}
|
||||
* @constant
|
||||
* @private
|
||||
*/
|
||||
const MAX_DIR_CRUMBS = 3
|
||||
|
||||
/**
|
||||
* @summary Character limit of a filename before a middle-ellipsis is added
|
||||
* @constant
|
||||
* @private
|
||||
*/
|
||||
const FILENAME_CHAR_LIMIT_SHORT = 15
|
||||
|
||||
function splitComponents(dirname, root) {
|
||||
const components = []
|
||||
let basename = null
|
||||
root = root || path.parse(dirname).root
|
||||
while( dirname !== root ) {
|
||||
basename = path.basename(dirname)
|
||||
components.unshift({
|
||||
path: dirname,
|
||||
basename: basename,
|
||||
name: basename
|
||||
})
|
||||
dirname = path.join( dirname, '..' )
|
||||
}
|
||||
if (components.length < MAX_DIR_CRUMBS) {
|
||||
components.unshift({
|
||||
path: root,
|
||||
basename: root,
|
||||
name: 'Root'
|
||||
})
|
||||
}
|
||||
return components
|
||||
}
|
||||
|
||||
class Crumb extends React.PureComponent {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
}
|
||||
|
||||
render () {
|
||||
return (
|
||||
<rendition.Button
|
||||
onClick={ ::this.navigate }
|
||||
plain={ true }>
|
||||
<rendition.Txt bold={ this.props.bold }>
|
||||
{ middleEllipsis(this.props.dir.name, FILENAME_CHAR_LIMIT_SHORT) }
|
||||
</rendition.Txt>
|
||||
</rendition.Button>
|
||||
)
|
||||
}
|
||||
|
||||
navigate () {
|
||||
this.props.navigate(this.props.dir.path)
|
||||
}
|
||||
}
|
||||
|
||||
class UnstyledBreadcrumbs extends React.PureComponent {
|
||||
render () {
|
||||
const components = splitComponents(this.props.path).slice(-MAX_DIR_CRUMBS)
|
||||
return (
|
||||
<div className={ this.props.className }>
|
||||
{
|
||||
components.map((dir, index) => {
|
||||
return (
|
||||
<Crumb
|
||||
key={ dir.path }
|
||||
bold={ index === components.length - 1 }
|
||||
dir={ dir }
|
||||
navigate={ ::this.props.navigate }
|
||||
/>
|
||||
)
|
||||
})
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const Breadcrumbs = styled(UnstyledBreadcrumbs)`
|
||||
font-size: 18px;
|
||||
|
||||
& > button:not(:last-child)::after {
|
||||
content: '/';
|
||||
margin: 9px;
|
||||
}
|
||||
`
|
||||
|
||||
module.exports = Breadcrumbs
|
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* Copyright 2018 resin.io
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
const React = require('react')
|
||||
const propTypes = require('prop-types')
|
||||
const styled = require('styled-components').default
|
||||
const rendition = require('rendition')
|
||||
const colors = require('./colors')
|
||||
|
||||
const middleEllipsis = require('../../../utils/middle-ellipsis')
|
||||
|
||||
/**
|
||||
* @summary Flex styled component
|
||||
* @function
|
||||
* @type {ReactElement}
|
||||
*/
|
||||
const Flex = styled.div`
|
||||
display: flex;
|
||||
flex: ${ props => props.flex };
|
||||
flex-direction: ${ props => props.direction };
|
||||
justify-content: ${ props => props.justifyContent };
|
||||
align-items: ${ props => props.alignItems };
|
||||
flex-wrap: ${ props => props.wrap };
|
||||
flex-grow: ${ props => props.grow };
|
||||
`
|
||||
|
||||
class RecentFileLink extends React.PureComponent {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
}
|
||||
|
||||
render () {
|
||||
const file = this.props.file
|
||||
return (
|
||||
<rendition.Button
|
||||
onClick={ ::this.select }
|
||||
plain={ true }>
|
||||
{ middleEllipsis(file.name, FILENAME_CHAR_LIMIT_SHORT) }
|
||||
</rendition.Button>
|
||||
)
|
||||
}
|
||||
|
||||
select () {
|
||||
this.props.onSelect(this.props.file)
|
||||
}
|
||||
}
|
||||
|
||||
class UnstyledRecentFiles extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
recent: [],
|
||||
favorites: []
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
return (
|
||||
<Flex className={ this.props.className }>
|
||||
<h5>Recent</h5>
|
||||
{
|
||||
this.state.recent.map((file) => {
|
||||
<RecentFileLink key={ file.path }
|
||||
file={ file }
|
||||
onSelect={ this.props.selectFile }/>
|
||||
})
|
||||
}
|
||||
<h5>Favorite</h5>
|
||||
{
|
||||
this.state.favorites.map((file) => {
|
||||
<RecentFileLink key={ file.path }
|
||||
file={ file }
|
||||
onSelect={ this.props.navigate }/>
|
||||
})
|
||||
}
|
||||
</Flex>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const RecentFiles = styled(UnstyledRecentFiles)`
|
||||
display: flex;
|
||||
flex: 0 0 auto;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
width: 130px;
|
||||
background-color: ${ colors.secondary.background };
|
||||
padding: 20px;
|
||||
color: ${ colors.secondary.color };
|
||||
|
||||
> h5 {
|
||||
color: ${ colors.secondary.title };
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
> h5:last-of-type {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
> button {
|
||||
margin-bottom: 10px;
|
||||
text-align: start;
|
||||
font-size: 16px;
|
||||
}
|
||||
`
|
||||
|
||||
module.exports = RecentFiles
|
37
lib/gui/app/components/file-selector/index.js
Normal file
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2018 resin.io
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
/* eslint-disable jsdoc/require-example */
|
||||
|
||||
/**
|
||||
* @module Etcher.Components.SVGIcon
|
||||
*/
|
||||
|
||||
const angular = require('angular')
|
||||
const react2angular = require('react2angular').react2angular
|
||||
|
||||
const MODULE_NAME = 'Etcher.Components.FileSelector'
|
||||
const angularFileSelector = angular.module(MODULE_NAME, [
|
||||
require('../modal/modal')
|
||||
])
|
||||
|
||||
angularFileSelector.component('fileSelector', react2angular(require('./file-selector/file-selector.jsx')))
|
||||
angularFileSelector.controller('FileSelectorController', require('./controllers/file-selector'))
|
||||
angularFileSelector.service('FileSelectorService', require('./services/file-selector'))
|
||||
|
||||
module.exports = MODULE_NAME
|
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright 2018 resin.io
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
module.exports = function (ModalService, $q) {
|
||||
let modal = null
|
||||
|
||||
/**
|
||||
* @summary Open the file selector widget
|
||||
* @function
|
||||
* @public
|
||||
*
|
||||
* @example
|
||||
* DriveSelectorService.open()
|
||||
*/
|
||||
this.open = () => {
|
||||
modal = ModalService.open({
|
||||
name: 'file-selector',
|
||||
template: require('../templates/file-selector-modal.tpl.html'),
|
||||
controller: 'FileSelectorController as selector',
|
||||
size: 'file-selector-modal'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Close the file selector widget
|
||||
* @function
|
||||
* @public
|
||||
*
|
||||
* @example
|
||||
* DriveSelectorService.close()
|
||||
*/
|
||||
this.close = () => {
|
||||
if (modal) {
|
||||
modal.close()
|
||||
}
|
||||
modal = null
|
||||
}
|
||||
}
|
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright 2018 resin.io
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
.modal-file-selector-modal {
|
||||
width: calc(100vw - 10px);
|
||||
|
||||
> .modal-content {
|
||||
height: calc(100vh - 20px);
|
||||
}
|
||||
}
|
@@ -0,0 +1,4 @@
|
||||
<file-selector
|
||||
constraintpath="selector.getFolderConstraint()"
|
||||
path="selector.getPath()"
|
||||
close="selector.close"></file-selector>
|
49
lib/gui/app/components/flash-another/flash-another.jsx
Normal file
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2018 resin.io
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const React = require('react')
|
||||
const PropTypes = require('prop-types')
|
||||
const styled = require('styled-components').default
|
||||
const { position, right } = require('styled-system')
|
||||
const { BaseButton, ThemedProvider } = require('../../styled-components')
|
||||
|
||||
const Div = styled.div `
|
||||
${position}
|
||||
${right}
|
||||
`
|
||||
|
||||
const FlashAnother = (props) => {
|
||||
return (
|
||||
<ThemedProvider>
|
||||
<Div position='absolute' right='152px'>
|
||||
<BaseButton
|
||||
primary
|
||||
onClick={props.onClick.bind(null, { preserveImage: true })}>
|
||||
Flash Another
|
||||
</BaseButton>
|
||||
</Div>
|
||||
</ThemedProvider>
|
||||
)
|
||||
}
|
||||
|
||||
FlashAnother.propTypes = {
|
||||
onClick: PropTypes.func
|
||||
}
|
||||
|
||||
module.exports = FlashAnother
|
34
lib/gui/app/components/flash-another/index.js
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2018 resin.io
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
/**
|
||||
* @module Etcher.Components.FlashAnother
|
||||
*/
|
||||
|
||||
const angular = require('angular')
|
||||
const { react2angular } = require('react2angular')
|
||||
|
||||
const MODULE_NAME = 'Etcher.Components.FlashAnother'
|
||||
const FlashAnother = angular.module(MODULE_NAME, [])
|
||||
|
||||
FlashAnother.component(
|
||||
'flashAnother',
|
||||
react2angular(require('./flash-another.jsx'))
|
||||
)
|
||||
|
||||
module.exports = MODULE_NAME
|
@@ -16,8 +16,9 @@
|
||||
|
||||
'use strict'
|
||||
|
||||
const flashState = require('../../../../../shared/models/flash-state')
|
||||
const selectionState = require('../../../../../shared/models/selection-state')
|
||||
const flashState = require('../../../models/flash-state')
|
||||
const selectionState = require('../../../models/selection-state')
|
||||
const store = require('../../../models/store')
|
||||
const analytics = require('../../../modules/analytics')
|
||||
|
||||
module.exports = function (WarningModalService) {
|
||||
@@ -40,7 +41,10 @@ module.exports = function (WarningModalService) {
|
||||
flashState.resetState()
|
||||
|
||||
if (confirmed) {
|
||||
analytics.logEvent('Restart after failure')
|
||||
analytics.logEvent('Restart after failure', {
|
||||
applicationSessionUuid: store.getState().toJS().applicationSessionUuid,
|
||||
flashingWorkflowUuid: store.getState().toJS().flashingWorkflowUuid
|
||||
})
|
||||
} else {
|
||||
selectionState.clear()
|
||||
}
|
||||
|
66
lib/gui/app/components/flash-results/flash-results.jsx
Normal file
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright 2018 resin.io
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
const React = require('react')
|
||||
const PropTypes = require('prop-types')
|
||||
const _ = require('lodash')
|
||||
const styled = require('styled-components').default
|
||||
const { position, left, top, space } = require('styled-system')
|
||||
const { Underline } = require('./../../styled-components')
|
||||
|
||||
const Div = styled.div `
|
||||
${position}
|
||||
${top}
|
||||
${left}
|
||||
${space}
|
||||
`
|
||||
|
||||
/* eslint-disable no-inline-comments */
|
||||
|
||||
const FlashResults = (props) => {
|
||||
return (
|
||||
<Div position='absolute' left='153px' top='66px'>
|
||||
<div className="inline-flex title">
|
||||
<span className="tick tick--success space-right-medium"></span>
|
||||
<h3>Flash Complete!</h3>
|
||||
</div>
|
||||
<Div className="results" mt='11px' mr='0' mb='0' ml='40px'>
|
||||
<Underline
|
||||
tooltip={props.errors()}>
|
||||
{_.map(props.results.devices, (quantity, type) => {
|
||||
return (quantity) ? (
|
||||
<div key={type} className={`target-status-line target-status-${type}`}>
|
||||
<span className="target-status-dot"></span>
|
||||
<span className="target-status-quantity">{ quantity }</span>
|
||||
<span className="target-status-message">{ props.message[type](quantity) }</span>
|
||||
</div>
|
||||
) : null
|
||||
})}
|
||||
</Underline>
|
||||
</Div>
|
||||
</Div>
|
||||
)
|
||||
}
|
||||
|
||||
FlashResults.propTypes = {
|
||||
results: PropTypes.object,
|
||||
message: PropTypes.object,
|
||||
errors: PropTypes.func
|
||||
}
|
||||
|
||||
module.exports = FlashResults
|
34
lib/gui/app/components/flash-results/index.js
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2018 resin.io
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
/**
|
||||
* @module Etcher.Components.FlashResults
|
||||
*/
|
||||
|
||||
const angular = require('angular')
|
||||
const { react2angular } = require('react2angular')
|
||||
|
||||
const MODULE_NAME = 'Etcher.Components.FlashResults'
|
||||
const FlashResults = angular.module(MODULE_NAME, [])
|
||||
|
||||
FlashResults.component(
|
||||
'flashResults',
|
||||
react2angular(require('./flash-results.jsx'))
|
||||
)
|
||||
|
||||
module.exports = MODULE_NAME
|
128
lib/gui/app/components/image-selector/image-selector.jsx
Normal file
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* Copyright 2016 resin.io
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
/* eslint-disable no-unused-vars */
|
||||
const React = require('react')
|
||||
const propTypes = require('prop-types')
|
||||
const { Badge, DropDownButton, Select } = require('rendition')
|
||||
const { default: styled } = require('styled-components')
|
||||
|
||||
const middleEllipsis = require('./../../utils/middle-ellipsis')
|
||||
|
||||
const shared = require('./../../../../gui/app/modules/units')
|
||||
const {
|
||||
StepButton,
|
||||
StepNameButton,
|
||||
StepSelection,
|
||||
Footer,
|
||||
Underline,
|
||||
DetailsText,
|
||||
ChangeButton,
|
||||
ThemedProvider
|
||||
} = require('./../../styled-components')
|
||||
|
||||
const DropdownItem = styled.p`
|
||||
padding-top: 10px;
|
||||
text-align: left;
|
||||
width: 150px;
|
||||
cursor: pointer;
|
||||
`
|
||||
|
||||
const DropdownItemIcon = styled.i`
|
||||
padding-right: 10px;
|
||||
`
|
||||
|
||||
const SelectImageButton = (props) => {
|
||||
if (props.hasImage) {
|
||||
return (
|
||||
<ThemedProvider>
|
||||
<StepNameButton
|
||||
plain
|
||||
onClick={props.showSelectedImageDetails}
|
||||
tooltip={props.imageBasename}
|
||||
>
|
||||
{/* eslint-disable no-magic-numbers */}
|
||||
{ middleEllipsis(props.imageName || props.imageBasename, 20) }
|
||||
</StepNameButton>
|
||||
{ !props.flashing &&
|
||||
<ChangeButton
|
||||
plain
|
||||
mb={14}
|
||||
onClick={props.deselectImage}
|
||||
>
|
||||
Remove
|
||||
</ChangeButton>
|
||||
}
|
||||
<DetailsText>
|
||||
{shared.bytesToClosestUnit(props.imageSize)}
|
||||
</DetailsText>
|
||||
</ThemedProvider>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<ThemedProvider>
|
||||
<StepSelection>
|
||||
<DropDownButton
|
||||
primary
|
||||
label={
|
||||
<div onClick={props.openImageSelector}>Select image</div>
|
||||
}
|
||||
style={{height: '48px'}}
|
||||
>
|
||||
<DropdownItem
|
||||
onClick={props.openImageSelector}
|
||||
>
|
||||
<DropdownItemIcon className="far fa-file"/>
|
||||
Select image file
|
||||
</DropdownItem>
|
||||
<DropdownItem
|
||||
onClick={props.openDriveSelector}
|
||||
>
|
||||
<DropdownItemIcon className="far fa-copy"/>
|
||||
Duplicate drive
|
||||
</DropdownItem>
|
||||
</DropDownButton>
|
||||
<Footer>
|
||||
{ props.mainSupportedExtensions.join(', ') }, and{' '}
|
||||
<Underline
|
||||
tooltip={ props.extraSupportedExtensions.join(', ') }
|
||||
>
|
||||
many more
|
||||
</Underline>
|
||||
</Footer>
|
||||
</StepSelection>
|
||||
</ThemedProvider>
|
||||
)
|
||||
}
|
||||
|
||||
SelectImageButton.propTypes = {
|
||||
openImageSelector: propTypes.func,
|
||||
openDriveSelector: propTypes.func,
|
||||
mainSupportedExtensions: propTypes.array,
|
||||
extraSupportedExtensions: propTypes.array,
|
||||
hasImage: propTypes.bool,
|
||||
showSelectedImageDetails: propTypes.func,
|
||||
imageName: propTypes.string,
|
||||
imageBasename: propTypes.string,
|
||||
deselectImage: propTypes.func,
|
||||
flashing: propTypes.bool,
|
||||
imageSize: propTypes.number,
|
||||
sourceType: propTypes.string
|
||||
}
|
||||
|
||||
module.exports = SelectImageButton
|
34
lib/gui/app/components/image-selector/index.js
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2018 resin.io
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
/**
|
||||
* @module Etcher.Components.ImageSelector
|
||||
*/
|
||||
|
||||
const angular = require('angular')
|
||||
const { react2angular } = require('react2angular')
|
||||
|
||||
const MODULE_NAME = 'Etcher.Components.ImageSelector'
|
||||
const SelectImageButton = angular.module(MODULE_NAME, [])
|
||||
|
||||
SelectImageButton.component(
|
||||
'imageSelector',
|
||||
react2angular(require('./image-selector.jsx'))
|
||||
)
|
||||
|
||||
module.exports = MODULE_NAME
|
@@ -17,6 +17,7 @@
|
||||
'use strict'
|
||||
|
||||
const _ = require('lodash')
|
||||
const store = require('../../../models/store')
|
||||
const analytics = require('../../../modules/analytics')
|
||||
|
||||
module.exports = function ($uibModal, $q) {
|
||||
@@ -45,7 +46,9 @@ module.exports = function ($uibModal, $q) {
|
||||
})
|
||||
|
||||
analytics.logEvent('Open modal', {
|
||||
name: options.name
|
||||
name: options.name,
|
||||
applicationSessionUuid: store.getState().toJS().applicationSessionUuid,
|
||||
flashingWorkflowUuid: store.getState().toJS().flashingWorkflowUuid
|
||||
})
|
||||
|
||||
const modal = $uibModal.open({
|
||||
@@ -53,7 +56,8 @@ module.exports = function ($uibModal, $q) {
|
||||
template: options.template,
|
||||
controller: options.controller,
|
||||
size: options.size,
|
||||
resolve: options.resolve
|
||||
resolve: options.resolve,
|
||||
backdrop: 'static'
|
||||
})
|
||||
|
||||
return {
|
||||
@@ -62,16 +66,20 @@ module.exports = function ($uibModal, $q) {
|
||||
modal.result.then((value) => {
|
||||
analytics.logEvent('Modal accepted', {
|
||||
name: options.name,
|
||||
value
|
||||
value,
|
||||
applicationSessionUuid: store.getState().toJS().applicationSessionUuid,
|
||||
flashingWorkflowUuid: store.getState().toJS().flashingWorkflowUuid
|
||||
})
|
||||
|
||||
resolve(value)
|
||||
}).catch((error) => {
|
||||
// Bootstrap doesn't 'resolve' these but cancels the dialog
|
||||
if (error === 'escape key press' || error === 'backdrop click') {
|
||||
if (error === 'escape key press') {
|
||||
analytics.logEvent('Modal rejected', {
|
||||
name: options.name,
|
||||
method: error
|
||||
method: error,
|
||||
applicationSessionUuid: store.getState().toJS().applicationSessionUuid,
|
||||
flashingWorkflowUuid: store.getState().toJS().flashingWorkflowUuid
|
||||
})
|
||||
|
||||
return resolve()
|
||||
@@ -79,7 +87,9 @@ module.exports = function ($uibModal, $q) {
|
||||
|
||||
analytics.logEvent('Modal rejected', {
|
||||
name: options.name,
|
||||
value: error
|
||||
value: error,
|
||||
applicationSessionUuid: store.getState().toJS().applicationSessionUuid,
|
||||
flashingWorkflowUuid: store.getState().toJS().flashingWorkflowUuid
|
||||
})
|
||||
|
||||
return reject(error)
|
||||
|
@@ -20,6 +20,7 @@
|
||||
flex-direction: column;
|
||||
margin: 0 auto;
|
||||
height: auto;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
@@ -82,6 +83,7 @@
|
||||
.modal-footer {
|
||||
flex-grow: 0;
|
||||
border: 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.modal {
|
||||
@@ -101,5 +103,4 @@
|
||||
.modal-dialog {
|
||||
margin: 0;
|
||||
position: initial;
|
||||
max-width: 50%;
|
||||
}
|
||||
|
@@ -21,8 +21,14 @@
|
||||
*/
|
||||
|
||||
const angular = require('angular')
|
||||
const { react2angular } = require('react2angular')
|
||||
|
||||
const MODULE_NAME = 'Etcher.Components.ProgressButton'
|
||||
const ProgressButton = angular.module(MODULE_NAME, [])
|
||||
ProgressButton.directive('progressButton', require('./directives/progress-button'))
|
||||
|
||||
ProgressButton.component(
|
||||
'progressButton',
|
||||
react2angular(require('./progress-button.jsx'))
|
||||
)
|
||||
|
||||
module.exports = MODULE_NAME
|
161
lib/gui/app/components/progress-button/progress-button.jsx
Normal file
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* Copyright 2016 resin.io
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
const React = require('react')
|
||||
const propTypes = require('prop-types')
|
||||
const Color = require('color')
|
||||
|
||||
const {
|
||||
default: styled,
|
||||
css,
|
||||
keyframes
|
||||
} = require('styled-components')
|
||||
|
||||
const { ProgressBar, Provider } = require('rendition')
|
||||
|
||||
const { colors } = require('./../../theme')
|
||||
const { StepButton, StepSelection } = require('./../../styled-components')
|
||||
|
||||
const darkenForegroundStripes = 0.18
|
||||
const desaturateForegroundStripes = 0.2
|
||||
const progressButtonStripesForegroundColor = Color(colors.primary.background)
|
||||
.darken(darkenForegroundStripes)
|
||||
.desaturate(desaturateForegroundStripes)
|
||||
.string()
|
||||
|
||||
const desaturateBackgroundStripes = 0.05
|
||||
const progressButtonStripesBackgroundColor = Color(colors.primary.background)
|
||||
.desaturate(desaturateBackgroundStripes)
|
||||
.string()
|
||||
|
||||
const ProgressButtonStripes = keyframes `
|
||||
0% {
|
||||
background-position: 0 0;
|
||||
}
|
||||
|
||||
100% {
|
||||
background-position: 20px 20px;
|
||||
}
|
||||
`
|
||||
|
||||
const ProgressButtonStripesRule = css `
|
||||
${ProgressButtonStripes} 1s linear infinite;
|
||||
`
|
||||
|
||||
const FlashProgressBar = styled(ProgressBar) `
|
||||
> div {
|
||||
width: 200px;
|
||||
height: 48px;
|
||||
color: white !important;
|
||||
text-shadow: none !important;
|
||||
}
|
||||
|
||||
width: 200px;
|
||||
height: 48px;
|
||||
font-size: 16px;
|
||||
line-height: 48px;
|
||||
|
||||
background: ${Color(colors.warning.background).darken(darkenForegroundStripes).string()};
|
||||
`
|
||||
|
||||
const FlashProgressBarValidating = styled(FlashProgressBar) `
|
||||
|
||||
// Notice that we add 0.01 to certain gradient stop positions.
|
||||
// That workarounds a Chrome rendering issue where diagonal
|
||||
// lines look spiky.
|
||||
// See https://github.com/resin-io/etcher/issues/472
|
||||
|
||||
background-image: -webkit-gradient(linear, 0 0, 100% 100%,
|
||||
color-stop(0.25, ${progressButtonStripesForegroundColor}),
|
||||
color-stop(0.26, ${progressButtonStripesBackgroundColor}),
|
||||
color-stop(0.50, ${progressButtonStripesBackgroundColor}),
|
||||
color-stop(0.51, ${progressButtonStripesForegroundColor}),
|
||||
color-stop(0.75, ${progressButtonStripesForegroundColor}),
|
||||
color-stop(0.76 , ${progressButtonStripesBackgroundColor}),
|
||||
to(${progressButtonStripesBackgroundColor}));
|
||||
|
||||
background-color: white;
|
||||
|
||||
animation: ${ProgressButtonStripesRule};
|
||||
overflow: hidden;
|
||||
|
||||
background-size: 20px 20px;
|
||||
`
|
||||
|
||||
/**
|
||||
* Progress Button component
|
||||
*/
|
||||
class ProgressButton extends React.Component {
|
||||
render () {
|
||||
if (this.props.active) {
|
||||
if (this.props.striped) {
|
||||
return (
|
||||
<Provider>
|
||||
<StepSelection>
|
||||
<FlashProgressBarValidating
|
||||
primary
|
||||
emphasized
|
||||
value= { this.props.percentage }
|
||||
>
|
||||
{ this.props.label }
|
||||
</FlashProgressBarValidating>
|
||||
</StepSelection>
|
||||
</Provider>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Provider>
|
||||
<StepSelection>
|
||||
<FlashProgressBar
|
||||
warning
|
||||
emphasized
|
||||
value= { this.props.percentage }
|
||||
>
|
||||
{ this.props.label }
|
||||
</FlashProgressBar>
|
||||
</StepSelection>
|
||||
</Provider>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Provider>
|
||||
<StepSelection>
|
||||
<StepButton
|
||||
onClick= { this.props.callback }
|
||||
disabled= { this.props.disabled }
|
||||
>
|
||||
{this.props.label}
|
||||
</StepButton>
|
||||
</StepSelection>
|
||||
</Provider>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
ProgressButton.propTypes = {
|
||||
striped: propTypes.bool,
|
||||
active: propTypes.bool,
|
||||
percentage: propTypes.number,
|
||||
label: propTypes.string,
|
||||
disabled: propTypes.bool,
|
||||
callback: propTypes.func
|
||||
}
|
||||
|
||||
module.exports = ProgressButton
|
@@ -1,126 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 resin.io
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A button with a progress bar inside.
|
||||
*
|
||||
* From http://tympanus.net/Development/ProgressButtonStyles/
|
||||
*
|
||||
* The state of the progress bar is controller by the width, in percentage,
|
||||
* of `.progress-button__bar`.
|
||||
*
|
||||
* If there is an action in place, the `active` attribute must be set to `true`.
|
||||
* This is useful to determine if the progress bar is paused from the point of view
|
||||
* of the styling.
|
||||
*
|
||||
* You can optionally pass the `.progress-button--striped` modified to get a striped
|
||||
* progress bar.
|
||||
*
|
||||
* The stripe implementation idea was taken from:
|
||||
*
|
||||
* https://css-tricks.com/css3-progress-bars/
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* <button class="progress-button" active="true">
|
||||
* <span class="progress-button__content">Button text</span>
|
||||
* <span class="progress-button__bar" style="width: 50%;"></span>
|
||||
* </button>
|
||||
*/
|
||||
|
||||
$progress-button-stripes-width: 20px;
|
||||
$progress-button-stripes-animation-duration: 1s;
|
||||
|
||||
.progress-button {
|
||||
@extend .button;
|
||||
@extend .button-primary;
|
||||
|
||||
overflow: hidden;
|
||||
|
||||
&[active="true"] {
|
||||
background-color: $palette-theme-warning-background;
|
||||
}
|
||||
|
||||
.progress-button__bar {
|
||||
background-color: lighten($palette-theme-warning-background, 5%);
|
||||
}
|
||||
|
||||
&.progress-button--striped {
|
||||
$progress-button-stripes-background-color: desaturate($palette-theme-primary-background, 5%);
|
||||
$progress-button-stripes-foreground-color: desaturate(darken($palette-theme-primary-background, 18%), 20%);
|
||||
|
||||
// Notice that we add `0.01` to certain gradient stop positions.
|
||||
// That workarounds a Chrome rendering issue where diagonal
|
||||
// lines look spiky.
|
||||
// See https://github.com/resin-io/etcher/issues/472
|
||||
background-image: -webkit-gradient(linear, 0 0, 100% 100%,
|
||||
color-stop(0.25, $progress-button-stripes-foreground-color),
|
||||
color-stop(0.25 + 0.01, $progress-button-stripes-background-color),
|
||||
color-stop(0.50, $progress-button-stripes-background-color),
|
||||
color-stop(0.50 + 0.01, $progress-button-stripes-foreground-color),
|
||||
color-stop(0.75, $progress-button-stripes-foreground-color),
|
||||
color-stop(0.75 + 0.01, $progress-button-stripes-background-color),
|
||||
to($progress-button-stripes-background-color));
|
||||
|
||||
.progress-button__bar {
|
||||
background-color: lighten($palette-theme-primary-background, 5%);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Prevent the button from being clickable
|
||||
// when it has an active progress bar.
|
||||
.progress-button[active="true"] {
|
||||
@extend .button-no-hover;
|
||||
}
|
||||
|
||||
.progress-button__content {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.progress-button__bar {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
|
||||
width: 0;
|
||||
height: 100%;
|
||||
|
||||
// Subtle progress bar animation
|
||||
transition: width 0.3s;
|
||||
|
||||
}
|
||||
|
||||
.progress-button--striped {
|
||||
background-size: $progress-button-stripes-width $progress-button-stripes-width;
|
||||
animation: progress-button-stripes $progress-button-stripes-animation-duration linear infinite;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@keyframes progress-button-stripes {
|
||||
|
||||
0% {
|
||||
background-position: 0 0;
|
||||
}
|
||||
|
||||
100% {
|
||||
background-position: $progress-button-stripes-width $progress-button-stripes-width;
|
||||
}
|
||||
|
||||
}
|
@@ -1,7 +0,0 @@
|
||||
<button class="progress-button"
|
||||
ng-class="{
|
||||
'progress-button--striped': striped && striped != 'false'
|
||||
}">
|
||||
<span class="progress-button__content" ng-transclude></span>
|
||||
<span class="progress-button__bar" ng-style="{ width: (percentage > 100 ? 100 : percentage) + '%' }"></span>
|
||||
</button>
|
@@ -16,32 +16,19 @@
|
||||
|
||||
'use strict'
|
||||
|
||||
const chalk = require('chalk')
|
||||
const errors = require('../shared/errors')
|
||||
|
||||
/**
|
||||
* @summary Print an error to stderr
|
||||
* @function
|
||||
* @public
|
||||
*
|
||||
* @param {Error} error - error
|
||||
*
|
||||
* @example
|
||||
* utils.printError(new Error('Oops!'));
|
||||
* @module Etcher.Components.ReducedFlashingInfos
|
||||
*/
|
||||
exports.printError = (error) => {
|
||||
const title = errors.getTitle(error)
|
||||
const description = errors.getDescription(error, {
|
||||
userFriendlyDescriptionsOnly: true
|
||||
})
|
||||
|
||||
console.error(chalk.red(title))
|
||||
const angular = require('angular')
|
||||
const { react2angular } = require('react2angular')
|
||||
|
||||
if (description) {
|
||||
console.error(`\n${chalk.red(description)}`)
|
||||
}
|
||||
const MODULE_NAME = 'Etcher.Components.ReducedFlashingInfos'
|
||||
const ReducedFlashingInfos = angular.module(MODULE_NAME, [])
|
||||
|
||||
if (process.env.ETCHER_CLI_DEBUG && error.stack) {
|
||||
console.error(`\n${chalk.red(error.stack)}`)
|
||||
}
|
||||
}
|
||||
ReducedFlashingInfos.component(
|
||||
'reducedFlashingInfos',
|
||||
react2angular(require('./reduced-flashing-infos.jsx'))
|
||||
)
|
||||
|
||||
module.exports = MODULE_NAME
|
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright 2016 resin.io
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
const React = require('react')
|
||||
const propTypes = require('prop-types')
|
||||
const styled = require('styled-components').default
|
||||
const { color } = require('styled-system')
|
||||
const SvgIcon = require('../svg-icon/svg-icon.jsx')
|
||||
|
||||
const Div = styled.div `
|
||||
position: absolute;
|
||||
top: 45px;
|
||||
left: 545px;
|
||||
|
||||
> span.step-name {
|
||||
justify-content: flex-start;
|
||||
|
||||
> span {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
> span:nth-child(2) {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
> span:nth-child(3) {
|
||||
font-weight: 400;
|
||||
font-style: italic;
|
||||
}
|
||||
}
|
||||
|
||||
.svg-icon[disabled] {
|
||||
opacity: 0.4;
|
||||
}
|
||||
`
|
||||
|
||||
const Span = styled.span `
|
||||
${color}
|
||||
`
|
||||
|
||||
const ReducedFlashingInfos = (props) => {
|
||||
return (props.shouldShow) ? (
|
||||
<Div>
|
||||
<Span className="step-name">
|
||||
<SvgIcon disabled contents={[ props.imageLogo ]} paths={[ '../../assets/image.svg' ]} width='20px'></SvgIcon>
|
||||
<Span>{ props.imageName }</Span>
|
||||
<Span color='#7e8085'>{ props.imageSize }</Span>
|
||||
</Span>
|
||||
|
||||
<Span className="step-name">
|
||||
<SvgIcon disabled paths={[ '../../assets/drive.svg' ]} width='20px'></SvgIcon>
|
||||
<Span>{ props.driveTitle }</Span>
|
||||
</Span>
|
||||
</Div>
|
||||
) : null
|
||||
}
|
||||
|
||||
ReducedFlashingInfos.propTypes = {
|
||||
imageLogo: propTypes.string,
|
||||
imageName: propTypes.string,
|
||||
imageSize: propTypes.string,
|
||||
driveTitle: propTypes.string,
|
||||
shouldShow: propTypes.bool
|
||||
}
|
||||
|
||||
module.exports = ReducedFlashingInfos
|
34
lib/gui/app/components/safe-webview/index.js
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2018 resin.io
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
/**
|
||||
* @module Etcher.Components.SafeWebview
|
||||
*/
|
||||
|
||||
const angular = require('angular')
|
||||
const { react2angular } = require('react2angular')
|
||||
|
||||
const MODULE_NAME = 'Etcher.Components.SafeWebview'
|
||||
const SafeWebview = angular.module(MODULE_NAME, [])
|
||||
|
||||
SafeWebview.component(
|
||||
'safeWebview',
|
||||
react2angular(require('./safe-webview.jsx'))
|
||||
)
|
||||
|
||||
module.exports = MODULE_NAME
|
@@ -20,15 +20,12 @@
|
||||
|
||||
const _ = require('lodash')
|
||||
const electron = require('electron')
|
||||
const angular = require('angular')
|
||||
const react = require('react')
|
||||
const propTypes = require('prop-types')
|
||||
const { react2angular } = require('react2angular')
|
||||
const analytics = require('../modules/analytics')
|
||||
const packageJSON = require('../../../../package.json')
|
||||
|
||||
const MODULE_NAME = 'Etcher.Components.SafeWebview'
|
||||
const angularSafeWebview = angular.module(MODULE_NAME, [])
|
||||
const analytics = require('../../modules/analytics')
|
||||
const store = require('../../models/store')
|
||||
const settings = require('../../models/settings')
|
||||
const packageJSON = require('../../../../../package.json')
|
||||
|
||||
/**
|
||||
* @summary Electron session identifier
|
||||
@@ -54,6 +51,14 @@ const ETCHER_VERSION_PARAM = 'etcher-version'
|
||||
*/
|
||||
const API_VERSION_PARAM = 'api-version'
|
||||
|
||||
/**
|
||||
* @summary Opt-out analytics search-parameter key
|
||||
* @constant
|
||||
* @private
|
||||
* @type {String}
|
||||
*/
|
||||
const OPT_OUT_ANALYTICS_PARAM = 'optOutAnalytics'
|
||||
|
||||
/**
|
||||
* @summary Webview API version
|
||||
* @constant
|
||||
@@ -65,8 +70,10 @@ const API_VERSION_PARAM = 'api-version'
|
||||
* should only be changed when truly necessary as it introduces breaking changes.
|
||||
* This version number is exposed to the banner such that it can determine what
|
||||
* features are safe to utilize.
|
||||
*
|
||||
* See `git blame -L n` where n is the line below for the history of version changes.
|
||||
*/
|
||||
const API_VERSION = 1
|
||||
const API_VERSION = 2
|
||||
|
||||
/**
|
||||
* @summary Webviews that hide/show depending on the HTTP status returned
|
||||
@@ -92,6 +99,7 @@ class SafeWebview extends react.PureComponent {
|
||||
// We set the version GET parameters here.
|
||||
url.searchParams.set(ETCHER_VERSION_PARAM, packageJSON.version)
|
||||
url.searchParams.set(API_VERSION_PARAM, API_VERSION)
|
||||
url.searchParams.set(OPT_OUT_ANALYTICS_PARAM, !settings.get('errorReporting'))
|
||||
|
||||
this.entryHref = url.href
|
||||
|
||||
@@ -101,13 +109,11 @@ class SafeWebview extends react.PureComponent {
|
||||
|
||||
this.eventTuples = [
|
||||
[ 'did-fail-load', this.didFailLoad ],
|
||||
[ 'did-get-response-details', this.didGetResponseDetails ],
|
||||
[ 'new-window', this.constructor.newWindow ],
|
||||
[ 'console-message', this.constructor.consoleMessage ]
|
||||
[ 'new-window', this.constructor.newWindow ]
|
||||
]
|
||||
|
||||
// Make a persistent electron session for the webview
|
||||
electron.remote.session.fromPartition(ELECTRON_SESSION, {
|
||||
this.session = electron.remote.session.fromPartition(ELECTRON_SESSION, {
|
||||
|
||||
// Disable the cache for the session such that new content shows up when refreshing
|
||||
cache: false
|
||||
@@ -120,6 +126,7 @@ class SafeWebview extends react.PureComponent {
|
||||
render () {
|
||||
return react.createElement('webview', {
|
||||
ref: 'webview',
|
||||
partition: ELECTRON_SESSION,
|
||||
style: {
|
||||
flex: this.state.shouldShow ? null : '0 1',
|
||||
width: this.state.shouldShow ? null : '0',
|
||||
@@ -137,8 +144,7 @@ class SafeWebview extends react.PureComponent {
|
||||
this.refs.webview.addEventListener(...tuple)
|
||||
})
|
||||
|
||||
// Use the 'success-banner' session
|
||||
this.refs.webview.partition = ELECTRON_SESSION
|
||||
this.session.webRequest.onCompleted(this.didGetResponseDetails)
|
||||
|
||||
// It's important that this comes after the partition setting, otherwise it will
|
||||
// use another session and we can't change it without destroying the element again
|
||||
@@ -153,6 +159,7 @@ class SafeWebview extends react.PureComponent {
|
||||
_.map(this.eventTuples, (tuple) => {
|
||||
this.refs.webview.removeEventListener(...tuple)
|
||||
})
|
||||
this.session.webRequest.onCompleted(null)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -195,11 +202,18 @@ class SafeWebview extends react.PureComponent {
|
||||
if (event.resourceType === 'mainFrame') {
|
||||
const HTTP_OK = 200
|
||||
|
||||
analytics.logEvent(event)
|
||||
analytics.logEvent('SafeWebview loaded', {
|
||||
event,
|
||||
applicationSessionUuid: store.getState().toJS().applicationSessionUuid,
|
||||
flashingWorkflowUuid: store.getState().toJS().flashingWorkflowUuid
|
||||
})
|
||||
|
||||
this.setState({
|
||||
shouldShow: event.httpResponseCode === HTTP_OK
|
||||
shouldShow: event.statusCode === HTTP_OK
|
||||
})
|
||||
if (this.props.onWebviewShow) {
|
||||
this.props.onWebviewShow(event.statusCode === HTTP_OK)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,39 +226,14 @@ class SafeWebview extends react.PureComponent {
|
||||
|
||||
if (_.every([
|
||||
url.protocol === 'http:' || url.protocol === 'https:',
|
||||
event.disposition === 'foreground-tab'
|
||||
event.disposition === 'foreground-tab',
|
||||
|
||||
// Don't open links if they're disabled by the env var
|
||||
!settings.get('disableExternalLinks')
|
||||
])) {
|
||||
electron.shell.openExternal(url.href)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Forward specially-formatted console messages from the webview
|
||||
* @param {Event} event - event object
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* // In the webview
|
||||
* console.log('Good night!')
|
||||
*/
|
||||
static consoleMessage (event) {
|
||||
if (_.isNil(event.message)) {
|
||||
return
|
||||
}
|
||||
|
||||
let message = event.message
|
||||
try {
|
||||
message = JSON.parse(event.message)
|
||||
} catch (error) {
|
||||
// Ignore
|
||||
}
|
||||
|
||||
if (message.command === 'error') {
|
||||
analytics.logException(message.data)
|
||||
} else {
|
||||
analytics.logEvent(message.data || message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SafeWebview.propTypes = {
|
||||
@@ -257,10 +246,13 @@ SafeWebview.propTypes = {
|
||||
/**
|
||||
* @summary Refresh the webview
|
||||
*/
|
||||
refreshNow: propTypes.bool
|
||||
refreshNow: propTypes.bool,
|
||||
|
||||
/**
|
||||
* @summary Webview lifecycle event
|
||||
*/
|
||||
onWebviewShow: propTypes.func
|
||||
|
||||
}
|
||||
|
||||
angularSafeWebview.component('safeWebview', react2angular(SafeWebview))
|
||||
|
||||
module.exports = MODULE_NAME
|
||||
module.exports = SafeWebview
|
32
lib/gui/app/components/svg-icon/index.js
Normal file
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2016 resin.io
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
/* eslint-disable jsdoc/require-example */
|
||||
|
||||
/**
|
||||
* @module Etcher.Components.SVGIcon
|
||||
*/
|
||||
|
||||
const angular = require('angular')
|
||||
const react2angular = require('react2angular').react2angular
|
||||
|
||||
const MODULE_NAME = 'Etcher.Components.SVGIcon'
|
||||
const angularSVGIcon = angular.module(MODULE_NAME, [])
|
||||
|
||||
angularSVGIcon.component('svgIcon', react2angular(require('./svg-icon.jsx')))
|
||||
module.exports = MODULE_NAME
|