+10
-2
changelog
+10
-2
changelog
···
14
14
- Fixed the control icons do they actually do what they're supposed to do
15
15
- Fixed window not focusing when opening from tray
16
16
17
-
v0.1.8:
17
+
v0.2.0:
18
18
- Migrate to tauri v2
19
-
- Photo loading is slightly faster
19
+
20
+
- Photos shouldn't cause the ui to lag while loading
21
+
- Removed the metadata loading screen in favour of loading the metadata just before an image it rendered
22
+
- Added the context menu back to the photo viewer screen
23
+
- Fixed some weird bugs where the world data cache would be ignored
24
+
- Fixed the ui forgetting the user account in some cases where the token stored it still valid
25
+
26
+
Dev Stuff:
27
+
- Fixed indentation to be more constistant
+1
public/icon/clock-regular.svg
+1
public/icon/clock-regular.svg
···
1
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M464 256A208 208 0 1 1 48 256a208 208 0 1 1 416 0zM0 256a256 256 0 1 0 512 0A256 256 0 1 0 0 256zM232 120l0 136c0 8 4 15.5 10.7 20l96 64c11 7.4 25.9 4.4 33.3-6.7s4.4-25.9-6.7-33.3L280 243.2 280 120c0-13.3-10.7-24-24-24s-24 10.7-24 24z"/></svg>
+1
public/icon/sliders-solid.svg
+1
public/icon/sliders-solid.svg
···
1
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M0 416c0 17.7 14.3 32 32 32l54.7 0c12.3 28.3 40.5 48 73.3 48s61-19.7 73.3-48L480 448c17.7 0 32-14.3 32-32s-14.3-32-32-32l-246.7 0c-12.3-28.3-40.5-48-73.3-48s-61 19.7-73.3 48L32 384c-17.7 0-32 14.3-32 32zm128 0a32 32 0 1 1 64 0 32 32 0 1 1 -64 0zM320 256a32 32 0 1 1 64 0 32 32 0 1 1 -64 0zm32-80c-32.8 0-61 19.7-73.3 48L32 224c-17.7 0-32 14.3-32 32s14.3 32 32 32l246.7 0c12.3 28.3 40.5 48 73.3 48s61-19.7 73.3-48l54.7 0c17.7 0 32-14.3 32-32s-14.3-32-32-32l-54.7 0c-12.3-28.3-40.5-48-73.3-48zM192 128a32 32 0 1 1 0-64 32 32 0 1 1 0 64zm73.3-64C253 35.7 224.8 16 192 16s-61 19.7-73.3 48L32 64C14.3 64 0 78.3 0 96s14.3 32 32 32l86.7 0c12.3 28.3 40.5 48 73.3 48s61-19.7 73.3-48L480 128c17.7 0 32-14.3 32-32s-14.3-32-32-32L265.3 64z"/></svg>
+559
src-tauri/Cargo.lock
+559
src-tauri/Cargo.lock
···
33
33
]
34
34
35
35
[[package]]
36
+
name = "aligned-vec"
37
+
version = "0.5.0"
38
+
source = "registry+https://github.com/rust-lang/crates.io-index"
39
+
checksum = "4aa90d7ce82d4be67b64039a3d588d38dbcc6736577de4a847025ce5b0c468d1"
40
+
41
+
[[package]]
36
42
name = "alloc-no-stdlib"
37
43
version = "2.0.4"
38
44
source = "registry+https://github.com/rust-lang/crates.io-index"
···
69
75
checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da"
70
76
71
77
[[package]]
78
+
name = "arbitrary"
79
+
version = "1.3.2"
80
+
source = "registry+https://github.com/rust-lang/crates.io-index"
81
+
checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110"
82
+
83
+
[[package]]
84
+
name = "arg_enum_proc_macro"
85
+
version = "0.3.4"
86
+
source = "registry+https://github.com/rust-lang/crates.io-index"
87
+
checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea"
88
+
dependencies = [
89
+
"proc-macro2",
90
+
"quote",
91
+
"syn 2.0.75",
92
+
]
93
+
94
+
[[package]]
95
+
name = "arrayvec"
96
+
version = "0.7.6"
97
+
source = "registry+https://github.com/rust-lang/crates.io-index"
98
+
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
99
+
100
+
[[package]]
72
101
name = "atk"
73
102
version = "0.18.0"
74
103
source = "registry+https://github.com/rust-lang/crates.io-index"
···
104
133
checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
105
134
106
135
[[package]]
136
+
name = "av1-grain"
137
+
version = "0.2.3"
138
+
source = "registry+https://github.com/rust-lang/crates.io-index"
139
+
checksum = "6678909d8c5d46a42abcf571271e15fdbc0a225e3646cf23762cd415046c78bf"
140
+
dependencies = [
141
+
"anyhow",
142
+
"arrayvec",
143
+
"log",
144
+
"nom",
145
+
"num-rational",
146
+
"v_frame",
147
+
]
148
+
149
+
[[package]]
150
+
name = "avif-serialize"
151
+
version = "0.8.1"
152
+
source = "registry+https://github.com/rust-lang/crates.io-index"
153
+
checksum = "876c75a42f6364451a033496a14c44bffe41f5f4a8236f697391f11024e596d2"
154
+
dependencies = [
155
+
"arrayvec",
156
+
]
157
+
158
+
[[package]]
107
159
name = "backtrace"
108
160
version = "0.3.73"
109
161
source = "registry+https://github.com/rust-lang/crates.io-index"
···
131
183
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
132
184
133
185
[[package]]
186
+
name = "bit_field"
187
+
version = "0.10.2"
188
+
source = "registry+https://github.com/rust-lang/crates.io-index"
189
+
checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61"
190
+
191
+
[[package]]
134
192
name = "bitflags"
135
193
version = "1.3.2"
136
194
source = "registry+https://github.com/rust-lang/crates.io-index"
···
144
202
dependencies = [
145
203
"serde",
146
204
]
205
+
206
+
[[package]]
207
+
name = "bitstream-io"
208
+
version = "2.5.3"
209
+
source = "registry+https://github.com/rust-lang/crates.io-index"
210
+
checksum = "b81e1519b0d82120d2fd469d5bfb2919a9361c48b02d82d04befc1cdd2002452"
147
211
148
212
[[package]]
149
213
name = "block"
···
189
253
"alloc-no-stdlib",
190
254
"alloc-stdlib",
191
255
]
256
+
257
+
[[package]]
258
+
name = "built"
259
+
version = "0.7.4"
260
+
source = "registry+https://github.com/rust-lang/crates.io-index"
261
+
checksum = "236e6289eda5a812bc6b53c3b024039382a2895fbbeef2d748b2931546d392c4"
192
262
193
263
[[package]]
194
264
name = "bumpalo"
···
209
279
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
210
280
211
281
[[package]]
282
+
name = "byteorder-lite"
283
+
version = "0.1.0"
284
+
source = "registry+https://github.com/rust-lang/crates.io-index"
285
+
checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
286
+
287
+
[[package]]
212
288
name = "bytes"
213
289
version = "1.7.1"
214
290
source = "registry+https://github.com/rust-lang/crates.io-index"
···
290
366
source = "registry+https://github.com/rust-lang/crates.io-index"
291
367
checksum = "72db2f7947ecee9b03b510377e8bb9077afa27176fdbff55c51027e976fdcc48"
292
368
dependencies = [
369
+
"jobserver",
370
+
"libc",
293
371
"shlex",
294
372
]
295
373
···
376
454
]
377
455
378
456
[[package]]
457
+
name = "color_quant"
458
+
version = "1.1.0"
459
+
source = "registry+https://github.com/rust-lang/crates.io-index"
460
+
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
461
+
462
+
[[package]]
379
463
name = "combine"
380
464
version = "4.6.7"
381
465
source = "registry+https://github.com/rust-lang/crates.io-index"
···
521
605
]
522
606
523
607
[[package]]
608
+
name = "crossbeam-deque"
609
+
version = "0.8.5"
610
+
source = "registry+https://github.com/rust-lang/crates.io-index"
611
+
checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d"
612
+
dependencies = [
613
+
"crossbeam-epoch",
614
+
"crossbeam-utils",
615
+
]
616
+
617
+
[[package]]
618
+
name = "crossbeam-epoch"
619
+
version = "0.9.18"
620
+
source = "registry+https://github.com/rust-lang/crates.io-index"
621
+
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
622
+
dependencies = [
623
+
"crossbeam-utils",
624
+
]
625
+
626
+
[[package]]
524
627
name = "crossbeam-utils"
525
628
version = "0.8.20"
526
629
source = "registry+https://github.com/rust-lang/crates.io-index"
527
630
checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80"
528
631
529
632
[[package]]
633
+
name = "crunchy"
634
+
version = "0.2.2"
635
+
source = "registry+https://github.com/rust-lang/crates.io-index"
636
+
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
637
+
638
+
[[package]]
530
639
name = "crypto-common"
531
640
version = "0.1.6"
532
641
source = "registry+https://github.com/rust-lang/crates.io-index"
···
698
807
]
699
808
700
809
[[package]]
810
+
name = "document-features"
811
+
version = "0.2.10"
812
+
source = "registry+https://github.com/rust-lang/crates.io-index"
813
+
checksum = "cb6969eaabd2421f8a2775cfd2471a2b634372b4a25d41e3bd647b79912850a0"
814
+
dependencies = [
815
+
"litrs",
816
+
]
817
+
818
+
[[package]]
701
819
name = "dpi"
702
820
version = "0.1.1"
703
821
source = "registry+https://github.com/rust-lang/crates.io-index"
···
732
850
version = "1.0.17"
733
851
source = "registry+https://github.com/rust-lang/crates.io-index"
734
852
checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125"
853
+
854
+
[[package]]
855
+
name = "either"
856
+
version = "1.13.0"
857
+
source = "registry+https://github.com/rust-lang/crates.io-index"
858
+
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
735
859
736
860
[[package]]
737
861
name = "embed-resource"
···
789
913
]
790
914
791
915
[[package]]
916
+
name = "exr"
917
+
version = "1.72.0"
918
+
source = "registry+https://github.com/rust-lang/crates.io-index"
919
+
checksum = "887d93f60543e9a9362ef8a21beedd0a833c5d9610e18c67abe15a5963dcb1a4"
920
+
dependencies = [
921
+
"bit_field",
922
+
"flume",
923
+
"half",
924
+
"lebe",
925
+
"miniz_oxide 0.7.4",
926
+
"rayon-core",
927
+
"smallvec",
928
+
"zune-inflate",
929
+
]
930
+
931
+
[[package]]
932
+
name = "fast_image_resize"
933
+
version = "4.2.1"
934
+
source = "registry+https://github.com/rust-lang/crates.io-index"
935
+
checksum = "2ca4b58827213977eabab8ee8d8258db8441338f3a1832a1c0f2de3372175531"
936
+
dependencies = [
937
+
"bytemuck",
938
+
"cfg-if",
939
+
"document-features",
940
+
"image 0.25.2",
941
+
"num-traits",
942
+
"thiserror",
943
+
]
944
+
945
+
[[package]]
792
946
name = "fastrand"
793
947
version = "2.1.0"
794
948
source = "registry+https://github.com/rust-lang/crates.io-index"
···
842
996
checksum = "17c704e9dbe1ddd863da1e6ff3567795087b1eb201ce80d8fa81162e1516500d"
843
997
dependencies = [
844
998
"bitflags 1.3.2",
999
+
]
1000
+
1001
+
[[package]]
1002
+
name = "flume"
1003
+
version = "0.11.0"
1004
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1005
+
checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181"
1006
+
dependencies = [
1007
+
"spin",
845
1008
]
846
1009
847
1010
[[package]]
···
1147
1310
]
1148
1311
1149
1312
[[package]]
1313
+
name = "gif"
1314
+
version = "0.13.1"
1315
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1316
+
checksum = "3fb2d69b19215e18bb912fa30f7ce15846e301408695e44e0ef719f1da9e19f2"
1317
+
dependencies = [
1318
+
"color_quant",
1319
+
"weezl",
1320
+
]
1321
+
1322
+
[[package]]
1150
1323
name = "gimli"
1151
1324
version = "0.29.0"
1152
1325
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1320
1493
]
1321
1494
1322
1495
[[package]]
1496
+
name = "half"
1497
+
version = "2.4.1"
1498
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1499
+
checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888"
1500
+
dependencies = [
1501
+
"cfg-if",
1502
+
"crunchy",
1503
+
]
1504
+
1505
+
[[package]]
1323
1506
name = "hashbrown"
1324
1507
version = "0.12.3"
1325
1508
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1543
1726
]
1544
1727
1545
1728
[[package]]
1729
+
name = "image"
1730
+
version = "0.24.9"
1731
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1732
+
checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d"
1733
+
dependencies = [
1734
+
"bytemuck",
1735
+
"byteorder",
1736
+
"color_quant",
1737
+
"num-traits",
1738
+
"png",
1739
+
]
1740
+
1741
+
[[package]]
1742
+
name = "image"
1743
+
version = "0.25.2"
1744
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1745
+
checksum = "99314c8a2152b8ddb211f924cdae532d8c5e4c8bb54728e12fff1b0cd5963a10"
1746
+
dependencies = [
1747
+
"bytemuck",
1748
+
"byteorder-lite",
1749
+
"color_quant",
1750
+
"exr",
1751
+
"gif",
1752
+
"image-webp",
1753
+
"num-traits",
1754
+
"png",
1755
+
"qoi",
1756
+
"ravif",
1757
+
"rayon",
1758
+
"rgb",
1759
+
"tiff",
1760
+
"zune-core",
1761
+
"zune-jpeg",
1762
+
]
1763
+
1764
+
[[package]]
1765
+
name = "image-webp"
1766
+
version = "0.1.3"
1767
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1768
+
checksum = "f79afb8cbee2ef20f59ccd477a218c12a93943d075b492015ecb1bb81f8ee904"
1769
+
dependencies = [
1770
+
"byteorder-lite",
1771
+
"quick-error",
1772
+
]
1773
+
1774
+
[[package]]
1775
+
name = "imgref"
1776
+
version = "1.10.1"
1777
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1778
+
checksum = "44feda355f4159a7c757171a77de25daf6411e217b4cabd03bd6650690468126"
1779
+
1780
+
[[package]]
1546
1781
name = "indexmap"
1547
1782
version = "1.9.3"
1548
1783
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1612
1847
]
1613
1848
1614
1849
[[package]]
1850
+
name = "interpolate_name"
1851
+
version = "0.2.4"
1852
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1853
+
checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60"
1854
+
dependencies = [
1855
+
"proc-macro2",
1856
+
"quote",
1857
+
"syn 2.0.75",
1858
+
]
1859
+
1860
+
[[package]]
1615
1861
name = "interprocess"
1616
1862
version = "1.2.1"
1617
1863
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1647
1893
dependencies = [
1648
1894
"is-docker",
1649
1895
"once_cell",
1896
+
]
1897
+
1898
+
[[package]]
1899
+
name = "itertools"
1900
+
version = "0.12.1"
1901
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1902
+
checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
1903
+
dependencies = [
1904
+
"either",
1650
1905
]
1651
1906
1652
1907
[[package]]
···
1707
1962
checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"
1708
1963
1709
1964
[[package]]
1965
+
name = "jobserver"
1966
+
version = "0.1.32"
1967
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1968
+
checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0"
1969
+
dependencies = [
1970
+
"libc",
1971
+
]
1972
+
1973
+
[[package]]
1974
+
name = "jpeg-decoder"
1975
+
version = "0.3.1"
1976
+
source = "registry+https://github.com/rust-lang/crates.io-index"
1977
+
checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0"
1978
+
1979
+
[[package]]
1710
1980
name = "js-sys"
1711
1981
version = "0.3.70"
1712
1982
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1800
2070
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
1801
2071
1802
2072
[[package]]
2073
+
name = "lebe"
2074
+
version = "0.5.2"
2075
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2076
+
checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8"
2077
+
2078
+
[[package]]
1803
2079
name = "libappindicator"
1804
2080
version = "0.9.0"
1805
2081
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1830
2106
checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439"
1831
2107
1832
2108
[[package]]
2109
+
name = "libfuzzer-sys"
2110
+
version = "0.4.7"
2111
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2112
+
checksum = "a96cfd5557eb82f2b83fed4955246c988d331975a002961b07c81584d107e7f7"
2113
+
dependencies = [
2114
+
"arbitrary",
2115
+
"cc",
2116
+
"once_cell",
2117
+
]
2118
+
2119
+
[[package]]
1833
2120
name = "libloading"
1834
2121
version = "0.7.4"
1835
2122
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1857
2144
checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
1858
2145
1859
2146
[[package]]
2147
+
name = "litrs"
2148
+
version = "0.4.1"
2149
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2150
+
checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5"
2151
+
2152
+
[[package]]
1860
2153
name = "lock_api"
1861
2154
version = "0.4.12"
1862
2155
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1888
2181
]
1889
2182
1890
2183
[[package]]
2184
+
name = "loop9"
2185
+
version = "0.1.5"
2186
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2187
+
checksum = "0fae87c125b03c1d2c0150c90365d7d6bcc53fb73a9acaef207d2d065860f062"
2188
+
dependencies = [
2189
+
"imgref",
2190
+
]
2191
+
2192
+
[[package]]
1891
2193
name = "mac"
1892
2194
version = "0.1.1"
1893
2195
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1932
2234
checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5"
1933
2235
1934
2236
[[package]]
2237
+
name = "maybe-rayon"
2238
+
version = "0.1.1"
2239
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2240
+
checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519"
2241
+
dependencies = [
2242
+
"cfg-if",
2243
+
]
2244
+
2245
+
[[package]]
1935
2246
name = "memchr"
1936
2247
version = "2.7.4"
1937
2248
source = "registry+https://github.com/rust-lang/crates.io-index"
···
1953
2264
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
1954
2265
1955
2266
[[package]]
2267
+
name = "minimal-lexical"
2268
+
version = "0.2.1"
2269
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2270
+
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
2271
+
2272
+
[[package]]
1956
2273
name = "miniz_oxide"
1957
2274
version = "0.7.4"
1958
2275
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2085
2402
checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb"
2086
2403
2087
2404
[[package]]
2405
+
name = "nom"
2406
+
version = "7.1.3"
2407
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2408
+
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
2409
+
dependencies = [
2410
+
"memchr",
2411
+
"minimal-lexical",
2412
+
]
2413
+
2414
+
[[package]]
2415
+
name = "noop_proc_macro"
2416
+
version = "0.3.0"
2417
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2418
+
checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8"
2419
+
2420
+
[[package]]
2088
2421
name = "notify"
2089
2422
version = "6.1.1"
2090
2423
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2114
2447
]
2115
2448
2116
2449
[[package]]
2450
+
name = "num-bigint"
2451
+
version = "0.4.6"
2452
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2453
+
checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
2454
+
dependencies = [
2455
+
"num-integer",
2456
+
"num-traits",
2457
+
]
2458
+
2459
+
[[package]]
2117
2460
name = "num-conv"
2118
2461
version = "0.1.0"
2119
2462
source = "registry+https://github.com/rust-lang/crates.io-index"
2120
2463
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
2121
2464
2122
2465
[[package]]
2466
+
name = "num-derive"
2467
+
version = "0.4.2"
2468
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2469
+
checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202"
2470
+
dependencies = [
2471
+
"proc-macro2",
2472
+
"quote",
2473
+
"syn 2.0.75",
2474
+
]
2475
+
2476
+
[[package]]
2477
+
name = "num-integer"
2478
+
version = "0.1.46"
2479
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2480
+
checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
2481
+
dependencies = [
2482
+
"num-traits",
2483
+
]
2484
+
2485
+
[[package]]
2486
+
name = "num-rational"
2487
+
version = "0.4.2"
2488
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2489
+
checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824"
2490
+
dependencies = [
2491
+
"num-bigint",
2492
+
"num-integer",
2493
+
"num-traits",
2494
+
]
2495
+
2496
+
[[package]]
2123
2497
name = "num-traits"
2124
2498
version = "0.2.19"
2125
2499
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2434
2808
]
2435
2809
2436
2810
[[package]]
2811
+
name = "paste"
2812
+
version = "1.0.15"
2813
+
source = "registry+https://github.com/rust-lang/crates.io-index"
2814
+
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
2815
+
2816
+
[[package]]
2437
2817
name = "pathdiff"
2438
2818
version = "0.2.1"
2439
2819
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2724
3104
]
2725
3105
2726
3106
[[package]]
3107
+
name = "profiling"
3108
+
version = "1.0.15"
3109
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3110
+
checksum = "43d84d1d7a6ac92673717f9f6d1518374ef257669c24ebc5ac25d5033828be58"
3111
+
dependencies = [
3112
+
"profiling-procmacros",
3113
+
]
3114
+
3115
+
[[package]]
3116
+
name = "profiling-procmacros"
3117
+
version = "1.0.15"
3118
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3119
+
checksum = "8021cf59c8ec9c432cfc2526ac6b8aa508ecaf29cd415f271b8406c1b851c3fd"
3120
+
dependencies = [
3121
+
"quote",
3122
+
"syn 2.0.75",
3123
+
]
3124
+
3125
+
[[package]]
2727
3126
name = "psl-types"
2728
3127
version = "2.0.11"
2729
3128
source = "registry+https://github.com/rust-lang/crates.io-index"
···
2738
3137
"idna 0.3.0",
2739
3138
"psl-types",
2740
3139
]
3140
+
3141
+
[[package]]
3142
+
name = "qoi"
3143
+
version = "0.4.1"
3144
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3145
+
checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001"
3146
+
dependencies = [
3147
+
"bytemuck",
3148
+
]
3149
+
3150
+
[[package]]
3151
+
name = "quick-error"
3152
+
version = "2.0.1"
3153
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3154
+
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
2741
3155
2742
3156
[[package]]
2743
3157
name = "quick-xml"
···
2887
3301
]
2888
3302
2889
3303
[[package]]
3304
+
name = "rav1e"
3305
+
version = "0.7.1"
3306
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3307
+
checksum = "cd87ce80a7665b1cce111f8a16c1f3929f6547ce91ade6addf4ec86a8dda5ce9"
3308
+
dependencies = [
3309
+
"arbitrary",
3310
+
"arg_enum_proc_macro",
3311
+
"arrayvec",
3312
+
"av1-grain",
3313
+
"bitstream-io",
3314
+
"built",
3315
+
"cfg-if",
3316
+
"interpolate_name",
3317
+
"itertools",
3318
+
"libc",
3319
+
"libfuzzer-sys",
3320
+
"log",
3321
+
"maybe-rayon",
3322
+
"new_debug_unreachable",
3323
+
"noop_proc_macro",
3324
+
"num-derive",
3325
+
"num-traits",
3326
+
"once_cell",
3327
+
"paste",
3328
+
"profiling",
3329
+
"rand 0.8.5",
3330
+
"rand_chacha 0.3.1",
3331
+
"simd_helpers",
3332
+
"system-deps",
3333
+
"thiserror",
3334
+
"v_frame",
3335
+
"wasm-bindgen",
3336
+
]
3337
+
3338
+
[[package]]
3339
+
name = "ravif"
3340
+
version = "0.11.10"
3341
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3342
+
checksum = "a8f0bfd976333248de2078d350bfdf182ff96e168a24d23d2436cef320dd4bdd"
3343
+
dependencies = [
3344
+
"avif-serialize",
3345
+
"imgref",
3346
+
"loop9",
3347
+
"quick-error",
3348
+
"rav1e",
3349
+
"rgb",
3350
+
]
3351
+
3352
+
[[package]]
2890
3353
name = "raw-window-handle"
2891
3354
version = "0.6.2"
2892
3355
source = "registry+https://github.com/rust-lang/crates.io-index"
2893
3356
checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539"
3357
+
3358
+
[[package]]
3359
+
name = "rayon"
3360
+
version = "1.10.0"
3361
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3362
+
checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa"
3363
+
dependencies = [
3364
+
"either",
3365
+
"rayon-core",
3366
+
]
3367
+
3368
+
[[package]]
3369
+
name = "rayon-core"
3370
+
version = "1.12.1"
3371
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3372
+
checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
3373
+
dependencies = [
3374
+
"crossbeam-deque",
3375
+
"crossbeam-utils",
3376
+
]
2894
3377
2895
3378
[[package]]
2896
3379
name = "redox_syscall"
···
3010
3493
]
3011
3494
3012
3495
[[package]]
3496
+
name = "rgb"
3497
+
version = "0.8.48"
3498
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3499
+
checksum = "0f86ae463694029097b846d8f99fd5536740602ae00022c0c50c5600720b2f71"
3500
+
dependencies = [
3501
+
"bytemuck",
3502
+
]
3503
+
3504
+
[[package]]
3013
3505
name = "ring"
3014
3506
version = "0.17.8"
3015
3507
source = "registry+https://github.com/rust-lang/crates.io-index"
···
3410
3902
checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
3411
3903
3412
3904
[[package]]
3905
+
name = "simd_helpers"
3906
+
version = "0.1.0"
3907
+
source = "registry+https://github.com/rust-lang/crates.io-index"
3908
+
checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6"
3909
+
dependencies = [
3910
+
"quote",
3911
+
]
3912
+
3913
+
[[package]]
3413
3914
name = "siphasher"
3414
3915
version = "0.3.11"
3415
3916
source = "registry+https://github.com/rust-lang/crates.io-index"
···
3494
3995
version = "0.9.8"
3495
3996
source = "registry+https://github.com/rust-lang/crates.io-index"
3496
3997
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
3998
+
dependencies = [
3999
+
"lock_api",
4000
+
]
3497
4001
3498
4002
[[package]]
3499
4003
name = "stable_deref_trait"
···
3698
4202
"gtk",
3699
4203
"heck 0.5.0",
3700
4204
"http",
4205
+
"image 0.24.9",
3701
4206
"jni",
3702
4207
"libc",
3703
4208
"log",
···
4071
4576
]
4072
4577
4073
4578
[[package]]
4579
+
name = "tiff"
4580
+
version = "0.9.1"
4581
+
source = "registry+https://github.com/rust-lang/crates.io-index"
4582
+
checksum = "ba1310fcea54c6a9a4fd1aad794ecc02c31682f6bfbecdf460bf19533eed1e3e"
4583
+
dependencies = [
4584
+
"flate2",
4585
+
"jpeg-decoder",
4586
+
"weezl",
4587
+
]
4588
+
4589
+
[[package]]
4074
4590
name = "time"
4075
4591
version = "0.3.36"
4076
4592
source = "registry+https://github.com/rust-lang/crates.io-index"
···
4484
5000
]
4485
5001
4486
5002
[[package]]
5003
+
name = "v_frame"
5004
+
version = "0.3.8"
5005
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5006
+
checksum = "d6f32aaa24bacd11e488aa9ba66369c7cd514885742c9fe08cfe85884db3e92b"
5007
+
dependencies = [
5008
+
"aligned-vec",
5009
+
"num-traits",
5010
+
"wasm-bindgen",
5011
+
]
5012
+
5013
+
[[package]]
4487
5014
name = "valuable"
4488
5015
version = "0.1.0"
4489
5016
source = "registry+https://github.com/rust-lang/crates.io-index"
···
4512
5039
version = "0.1.7"
4513
5040
dependencies = [
4514
5041
"dirs",
5042
+
"fast_image_resize",
5043
+
"image 0.25.2",
4515
5044
"mslnk",
4516
5045
"notify",
4517
5046
"open",
···
4756
5285
"windows 0.58.0",
4757
5286
"windows-core 0.58.0",
4758
5287
]
5288
+
5289
+
[[package]]
5290
+
name = "weezl"
5291
+
version = "0.1.8"
5292
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5293
+
checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082"
4759
5294
4760
5295
[[package]]
4761
5296
name = "winapi"
···
5233
5768
version = "1.8.1"
5234
5769
source = "registry+https://github.com/rust-lang/crates.io-index"
5235
5770
checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
5771
+
5772
+
[[package]]
5773
+
name = "zune-core"
5774
+
version = "0.4.12"
5775
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5776
+
checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a"
5777
+
5778
+
[[package]]
5779
+
name = "zune-inflate"
5780
+
version = "0.2.54"
5781
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5782
+
checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02"
5783
+
dependencies = [
5784
+
"simd-adler32",
5785
+
]
5786
+
5787
+
[[package]]
5788
+
name = "zune-jpeg"
5789
+
version = "0.4.13"
5790
+
source = "registry+https://github.com/rust-lang/crates.io-index"
5791
+
checksum = "16099418600b4d8f028622f73ff6e3deaabdff330fb9a2a131dea781ee8b0768"
5792
+
dependencies = [
5793
+
"zune-core",
5794
+
]
+3
-1
src-tauri/Cargo.toml
+3
-1
src-tauri/Cargo.toml
···
11
11
tauri-build = { version = "2.0.0-rc", features = [] }
12
12
13
13
[dependencies]
14
-
tauri = { version = "2.0.0-rc", features = ["tray-icon"] }
14
+
tauri = { version = "2.0.0-rc", features = ["tray-icon", "image-png"] }
15
15
serde = { version = "1.0", features = ["derive"] }
16
16
serde_json = "1.0"
17
17
open = "5.1.2"
···
24
24
tauri-plugin-shell = "2.0.0-rc.2"
25
25
tauri-plugin-http = "2.0.0-rc.0"
26
26
tauri-plugin-process = "2.0.0-rc.0"
27
+
image = "0.25.2"
28
+
fast_image_resize = { version = "4.2.1", features = [ "image" ] }
27
29
28
30
[features]
29
31
# this feature is used for production builds or when `devPath` points to the filesystem
+1
src-tauri/capabilities/migrated.json
+1
src-tauri/capabilities/migrated.json
+1
-1
src-tauri/gen/schemas/capabilities.json
+1
-1
src-tauri/gen/schemas/capabilities.json
···
1
-
{"migrated":{"identifier":"migrated","description":"permissions that were migrated from v1","local":true,"windows":["main"],"permissions":["core:default","core:window:allow-maximize","core:window:allow-unmaximize","core:window:allow-minimize","core:window:allow-unminimize","core:window:allow-show","core:window:allow-hide","core:window:allow-close","core:window:allow-start-dragging","shell:allow-open",{"identifier":"http:default","allow":[{"url":"https://photos.phazed.xyz/*"},{"url":"https://photos-cdn.phazed.xyz/*"}]},"process:allow-restart","shell:default","http:default","process:default"]}}
1
+
{"migrated":{"identifier":"migrated","description":"permissions that were migrated from v1","local":true,"windows":["main"],"permissions":["core:default","core:window:allow-maximize","core:window:allow-unmaximize","core:window:allow-minimize","core:window:allow-unminimize","core:window:allow-toggle-maximize","core:window:allow-show","core:window:allow-hide","core:window:allow-close","core:window:allow-start-dragging","shell:allow-open",{"identifier":"http:default","allow":[{"url":"https://photos.phazed.xyz/*"},{"url":"https://photos-cdn.phazed.xyz/*"}]},"process:allow-restart","shell:default","http:default","process:default"]}}
+419
-360
src-tauri/src/main.rs
+419
-360
src-tauri/src/main.rs
···
5
5
mod worldscraper;
6
6
7
7
use core::time;
8
+
use image::{ codecs::png::{ PngDecoder, PngEncoder }, DynamicImage, ImageEncoder };
9
+
use fast_image_resize::{ images::Image, IntoImageView, ResizeOptions, Resizer };
8
10
use mslnk::ShellLink;
9
11
use notify::{EventKind, RecursiveMode, Watcher};
10
12
use pngmeta::PNGImage;
11
13
use regex::Regex;
12
14
use std::{
13
-
env, fs,
14
-
io::Read,
15
-
path,
16
-
process::{self, Command},
17
-
thread,
18
-
time::Duration,
15
+
env, fs,
16
+
io::{ BufReader, Read },
17
+
path,
18
+
process::{self, Command},
19
+
thread,
20
+
time::Duration,
19
21
};
20
22
use tauri::{
21
-
http::Response, menu::{ MenuBuilder, MenuItemBuilder }, tray::{ MouseButton, MouseButtonState, TrayIconBuilder, TrayIconEvent }, AppHandle, Emitter, Manager, WindowEvent
23
+
http::Response, menu::{MenuBuilder, MenuItemBuilder}, tray::{MouseButton, MouseButtonState, TrayIconBuilder, TrayIconEvent}, AppHandle, Emitter, Manager, WindowEvent
22
24
};
23
25
use worldscraper::World;
26
+
27
+
// TODO: for the love of fuck please seperate this file out into multiple files at some point
28
+
// TODO: Linux support
24
29
25
30
// Scans all files under the "Pictures/VRChat" path
26
31
// then sends the list of photos to the frontend
27
32
#[derive(Clone, serde::Serialize)]
28
33
struct PhotosLoadedResponse {
29
-
photos: Vec<path::PathBuf>,
30
-
size: usize,
34
+
photos: Vec<path::PathBuf>,
35
+
size: usize,
31
36
}
32
37
33
38
const VERSION: &str = env!("CARGO_PKG_VERSION");
34
39
35
40
pub fn get_photo_path() -> path::PathBuf {
36
-
let config_path = dirs::home_dir()
37
-
.unwrap()
38
-
.join("AppData\\Roaming\\PhazeDev\\VRChatPhotoManager\\.photos_path");
41
+
let config_path = dirs::home_dir()
42
+
.unwrap()
43
+
.join("AppData\\Roaming\\PhazeDev\\VRChatPhotoManager\\.photos_path");
39
44
40
-
match fs::read_to_string(config_path) {
41
-
Ok(path) => {
42
-
if path
43
-
!= dirs::picture_dir()
44
-
.unwrap()
45
-
.join("VRChat")
46
-
.to_str()
47
-
.unwrap()
48
-
.to_owned()
49
-
{
50
-
path::PathBuf::from(path)
51
-
} else {
52
-
dirs::picture_dir().unwrap().join("VRChat")
53
-
}
54
-
}
55
-
Err(_) => dirs::picture_dir().unwrap().join("VRChat"),
45
+
match fs::read_to_string(config_path) {
46
+
Ok(path) => {
47
+
if path
48
+
!= dirs::picture_dir()
49
+
.unwrap()
50
+
.join("VRChat")
51
+
.to_str()
52
+
.unwrap()
53
+
.to_owned()
54
+
{
55
+
path::PathBuf::from(path)
56
+
} else {
57
+
dirs::picture_dir().unwrap().join("VRChat")
58
+
}
56
59
}
60
+
Err(_) => dirs::picture_dir().unwrap().join("VRChat"),
61
+
}
57
62
}
58
63
59
64
#[tauri::command]
60
65
fn close_splashscreen(window: tauri::Window) {
61
-
window.get_webview_window("main").unwrap().show().unwrap();
66
+
window.get_webview_window("main").unwrap().show().unwrap();
62
67
}
63
68
64
69
#[tauri::command]
65
70
fn start_user_auth() {
66
-
open::that("https://photos.phazed.xyz/api/v1/auth").unwrap();
71
+
open::that("https://photos.phazed.xyz/api/v1/auth").unwrap();
67
72
}
68
73
69
74
#[tauri::command]
70
75
fn open_url(url: &str) {
71
-
open::that(url).unwrap();
76
+
open::that(url).unwrap();
77
+
}
78
+
79
+
#[tauri::command]
80
+
fn open_folder(url: &str) {
81
+
Command::new("explorer.exe")
82
+
.arg(format!("/select,{}", url))
83
+
.spawn()
84
+
.unwrap();
72
85
}
73
86
74
87
// Check if the photo config file exists
75
88
// if not just return the default vrchat path
76
89
#[tauri::command]
77
90
fn get_user_photos_path() -> path::PathBuf {
78
-
get_photo_path()
91
+
get_photo_path()
79
92
}
80
93
81
94
// When the user changes the start with windows toggle
82
95
// create and delete the shortcut from the startup folder
83
96
#[tauri::command]
84
97
fn start_with_win(start: bool) {
85
-
thread::spawn(move || {
86
-
if start {
87
-
let target = dirs::home_dir()
88
-
.unwrap()
89
-
.join("AppData\\Roaming\\PhazeDev\\VRChatPhotoManager\\vrchat-photo-manager.exe");
90
-
match fs::metadata(&target) {
91
-
Ok(_) => {
92
-
let lnk = dirs::home_dir().unwrap().join("AppData\\Roaming\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\VRChat Photo Manager.lnk");
98
+
thread::spawn(move || {
99
+
if start {
100
+
let target = dirs::home_dir()
101
+
.unwrap()
102
+
.join("AppData\\Roaming\\PhazeDev\\VRChatPhotoManager\\vrchat-photo-manager.exe");
93
103
94
-
let sl = ShellLink::new(target).unwrap();
95
-
sl.create_lnk(lnk).unwrap();
96
-
}
97
-
Err(_) => {}
98
-
}
99
-
} else {
100
-
let lnk = dirs::home_dir().unwrap().join("AppData\\Roaming\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\VRChat Photo Manager.lnk");
101
-
fs::remove_file(lnk).unwrap();
104
+
match fs::metadata(&target) {
105
+
Ok(_) => {
106
+
let lnk = dirs::home_dir().unwrap().join("AppData\\Roaming\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\VRChat Photo Manager.lnk");
107
+
108
+
let sl = ShellLink::new(target).unwrap();
109
+
sl.create_lnk(lnk).unwrap();
102
110
}
103
-
});
111
+
Err(_) => {}
112
+
}
113
+
} else {
114
+
let lnk = dirs::home_dir().unwrap().join("AppData\\Roaming\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\VRChat Photo Manager.lnk");
115
+
fs::remove_file(lnk).unwrap();
116
+
}
117
+
});
104
118
}
105
119
106
120
// Load vrchat world data
107
121
#[tauri::command]
108
122
fn find_world_by_id(world_id: String, window: tauri::Window) {
109
-
thread::spawn(move || {
110
-
let world = World::new(world_id);
111
-
window.emit("world_data", world).unwrap();
112
-
});
123
+
thread::spawn(move || {
124
+
let world = World::new(world_id);
125
+
window.emit("world_data", world).unwrap();
126
+
});
113
127
}
114
128
115
129
// On requested sync the photos to the cloud
116
130
#[tauri::command]
117
131
fn sync_photos(token: String, window: tauri::Window) {
118
-
thread::spawn(move || {
119
-
photosync::sync_photos(token, get_photo_path(), window);
120
-
});
132
+
thread::spawn(move || {
133
+
photosync::sync_photos(token, get_photo_path(), window);
134
+
});
121
135
}
122
136
123
137
#[tauri::command]
124
138
fn load_photos(window: tauri::Window) {
125
-
thread::spawn(move || {
126
-
let base_dir = get_photo_path();
139
+
thread::spawn(move || {
140
+
let base_dir = get_photo_path();
127
141
128
-
let mut photos: Vec<path::PathBuf> = Vec::new();
129
-
let mut size: usize = 0;
142
+
let mut photos: Vec<path::PathBuf> = Vec::new();
143
+
let mut size: usize = 0;
130
144
131
-
for folder in fs::read_dir(&base_dir).unwrap() {
132
-
let f = folder.unwrap();
145
+
for folder in fs::read_dir(&base_dir).unwrap() {
146
+
let f = folder.unwrap();
133
147
134
-
if f.metadata().unwrap().is_dir() {
135
-
for photo in fs::read_dir(f.path()).unwrap() {
136
-
let p = photo.unwrap();
148
+
if f.metadata().unwrap().is_dir() {
149
+
for photo in fs::read_dir(f.path()).unwrap() {
150
+
let p = photo.unwrap();
137
151
138
-
if p.metadata().unwrap().is_file() {
139
-
let fname = p.path();
152
+
if p.metadata().unwrap().is_file() {
153
+
let fname = p.path();
140
154
141
-
let re1 = Regex::new(r"(?m)VRChat_[0-9]{4}-[0-9]{2}-[0-9]{2}_[0-9]{2}-[0-9]{2}-[0-9]{2}.[0-9]{3}_[0-9]{4}x[0-9]{4}.png").unwrap();
142
-
let re2 = Regex::new(
143
-
r"(?m)VRChat_[0-9]{4}-[0-9]{2}-[0-9]{2}_[0-9]{2}-[0-9]{2}-[0-9]{2}.[0-9]{3}_[0-9]{4}x[0-9]{4}_wrld_[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}.png").unwrap();
155
+
let re1 = Regex::new(r"(?m)VRChat_[0-9]{4}-[0-9]{2}-[0-9]{2}_[0-9]{2}-[0-9]{2}-[0-9]{2}.[0-9]{3}_[0-9]{4}x[0-9]{4}.png").unwrap();
156
+
let re2 = Regex::new(
157
+
r"(?m)VRChat_[0-9]{4}-[0-9]{2}-[0-9]{2}_[0-9]{2}-[0-9]{2}-[0-9]{2}.[0-9]{3}_[0-9]{4}x[0-9]{4}_wrld_[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}.png").unwrap();
144
158
145
-
if re1.is_match(p.file_name().to_str().unwrap())
146
-
|| re2.is_match(p.file_name().to_str().unwrap())
147
-
{
148
-
let path = fname.to_path_buf().clone();
149
-
let metadata = fs::metadata(&path).unwrap();
159
+
if re1.is_match(p.file_name().to_str().unwrap())
160
+
|| re2.is_match(p.file_name().to_str().unwrap())
161
+
{
162
+
let path = fname.to_path_buf().clone();
163
+
let metadata = fs::metadata(&path).unwrap();
150
164
151
-
if metadata.is_file() {
152
-
size += metadata.len() as usize;
165
+
if metadata.is_file() {
166
+
size += metadata.len() as usize;
153
167
154
-
let path = path.strip_prefix(&base_dir).unwrap().to_path_buf();
155
-
photos.push(path);
156
-
}
157
-
} else {
158
-
println!("Ignoring {:#?} as it doesn't match regex", p.file_name());
159
-
}
160
-
} else {
161
-
println!("Ignoring {:#?} as it is a directory", p.file_name());
162
-
}
163
-
}
168
+
let path = path.strip_prefix(&base_dir).unwrap().to_path_buf();
169
+
photos.push(path);
170
+
}
164
171
} else {
165
-
println!("Ignoring {:#?} as it isn't a directory", f.file_name());
172
+
println!("Ignoring {:#?} as it doesn't match regex", p.file_name());
166
173
}
174
+
} else {
175
+
println!("Ignoring {:#?} as it is a directory", p.file_name());
176
+
}
167
177
}
178
+
} else {
179
+
println!("Ignoring {:#?} as it isn't a directory", f.file_name());
180
+
}
181
+
}
168
182
169
-
println!("Found {} photos", photos.len());
170
-
window
171
-
.emit("photos_loaded", PhotosLoadedResponse { photos, size })
172
-
.unwrap();
173
-
});
183
+
println!("Found {} photos", photos.len());
184
+
window
185
+
.emit("photos_loaded", PhotosLoadedResponse { photos, size })
186
+
.unwrap();
187
+
});
174
188
}
175
189
176
190
// Reads the PNG file and loads the image metadata from it
177
191
// then sends the metadata to the frontend, returns width, height, colour depth and so on... more info "pngmeta.rs"
178
192
#[tauri::command]
179
193
fn load_photo_meta(photo: &str, window: tauri::Window) {
180
-
let photo = photo.to_string();
194
+
let photo = photo.to_string();
181
195
182
-
thread::spawn(move || {
183
-
let base_dir = get_photo_path().join(&photo);
196
+
thread::spawn(move || {
197
+
let base_dir = get_photo_path().join(&photo);
184
198
185
-
let file = fs::File::open(base_dir.clone());
199
+
let file = fs::File::open(base_dir.clone());
186
200
187
-
match file {
188
-
Ok(mut file) => {
189
-
let mut buffer = Vec::new();
201
+
match file {
202
+
Ok(mut file) => {
203
+
let mut buffer = Vec::new();
190
204
191
-
let _out = file.read_to_end(&mut buffer);
192
-
window
193
-
.emit("photo_meta_loaded", PNGImage::new(buffer, photo))
194
-
.unwrap();
195
-
}
196
-
Err(_) => {
197
-
println!("Cannot read image file");
198
-
}
199
-
}
200
-
});
205
+
let _out = file.read_to_end(&mut buffer);
206
+
window
207
+
.emit("photo_meta_loaded", PNGImage::new(buffer, photo))
208
+
.unwrap();
209
+
}
210
+
Err(_) => {
211
+
println!("Cannot read image file");
212
+
}
213
+
}
214
+
});
201
215
}
202
216
203
217
// Delete a photo when the users confirms the prompt in the ui
204
218
#[tauri::command]
205
219
fn delete_photo(path: String, token: String, is_syncing: bool) {
206
-
thread::spawn(move || {
207
-
let p = get_photo_path().join(&path);
208
-
fs::remove_file(p).unwrap();
220
+
thread::spawn(move || {
221
+
let p = get_photo_path().join(&path);
222
+
fs::remove_file(p).unwrap();
209
223
210
-
let photo = path.split("\\").last().unwrap();
224
+
let photo = path.split("\\").last().unwrap();
211
225
212
-
if is_syncing {
213
-
let client = reqwest::blocking::Client::new();
214
-
client
215
-
.delete(format!(
216
-
"https://photos-cdn.phazed.xyz/api/v1/photos?token={}&photo={}",
217
-
token, photo
218
-
))
219
-
.timeout(Duration::from_secs(120))
220
-
.send()
221
-
.unwrap();
222
-
}
223
-
});
226
+
if is_syncing {
227
+
let client = reqwest::blocking::Client::new();
228
+
client
229
+
.delete(format!(
230
+
"https://photos-cdn.phazed.xyz/api/v1/photos?token={}&photo={}",
231
+
token, photo
232
+
))
233
+
.timeout(Duration::from_secs(120))
234
+
.send()
235
+
.unwrap();
236
+
}
237
+
});
224
238
}
225
239
226
240
#[tauri::command]
227
241
fn change_final_path(new_path: &str) {
228
-
let config_path = dirs::home_dir()
229
-
.unwrap()
230
-
.join("AppData\\Roaming\\PhazeDev\\VRChatPhotoManager\\.photos_path");
231
-
fs::write(&config_path, new_path.as_bytes()).unwrap();
242
+
let config_path = dirs::home_dir()
243
+
.unwrap()
244
+
.join("AppData\\Roaming\\PhazeDev\\VRChatPhotoManager\\.photos_path");
232
245
233
-
match fs::metadata(&new_path) {
234
-
Ok(_) => {}
235
-
Err(_) => {
236
-
fs::create_dir(&new_path).unwrap();
237
-
}
238
-
};
246
+
fs::write(&config_path, new_path.as_bytes()).unwrap();
247
+
248
+
match fs::metadata(&new_path) {
249
+
Ok(_) => {}
250
+
Err(_) => {
251
+
fs::create_dir(&new_path).unwrap();
252
+
}
253
+
};
239
254
}
240
255
241
256
#[tauri::command]
242
257
fn get_version() -> String {
243
-
String::from(VERSION)
258
+
String::from(VERSION)
244
259
}
245
260
246
261
#[tauri::command]
247
262
fn relaunch() {
248
-
let container_folder = dirs::home_dir()
249
-
.unwrap()
250
-
.join("AppData\\Roaming\\PhazeDev\\VRChatPhotoManager");
263
+
let container_folder = dirs::home_dir()
264
+
.unwrap()
265
+
.join("AppData\\Roaming\\PhazeDev\\VRChatPhotoManager");
251
266
252
-
let mut cmd = Command::new(&container_folder.join("./vrchat-photo-manager.exe"));
253
-
cmd.current_dir(container_folder);
254
-
cmd.spawn().expect("Cannot run updater");
267
+
let mut cmd = Command::new(&container_folder.join("./vrchat-photo-manager.exe"));
268
+
cmd.current_dir(container_folder);
269
+
cmd.spawn().expect("Cannot run updater");
255
270
256
-
process::exit(0);
271
+
process::exit(0);
257
272
}
258
273
259
274
fn main() {
260
-
std::env::set_var(
261
-
"WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS",
262
-
"--ignore-gpu-blacklist",
263
-
);
264
-
tauri_plugin_deep_link::prepare("uk.phaz.vrcpm");
275
+
tauri_plugin_deep_link::prepare("uk.phaz.vrcpm");
276
+
277
+
// Double check the app has an install directory
278
+
let container_folder = dirs::home_dir()
279
+
.unwrap()
280
+
.join("AppData\\Roaming\\PhazeDev\\VRChatPhotoManager");
265
281
266
-
// Double check the app has an install directory
267
-
let container_folder = dirs::home_dir()
268
-
.unwrap()
269
-
.join("AppData\\Roaming\\PhazeDev\\VRChatPhotoManager");
270
-
match fs::metadata(&container_folder) {
271
-
Ok(meta) => {
272
-
if meta.is_file() {
273
-
panic!("Cannot launch app as the container path is a file not a directory");
274
-
}
275
-
}
276
-
Err(_) => {
277
-
fs::create_dir(&container_folder).unwrap();
278
-
}
282
+
match fs::metadata(&container_folder) {
283
+
Ok(meta) => {
284
+
if meta.is_file() {
285
+
panic!("Cannot launch app as the container path is a file not a directory");
286
+
}
279
287
}
288
+
Err(_) => {
289
+
fs::create_dir(&container_folder).unwrap();
290
+
}
291
+
}
280
292
281
-
let sync_lock_path = dirs::home_dir()
282
-
.unwrap()
283
-
.join("AppData\\Roaming\\PhazeDev\\VRChatPhotoManager\\.sync_lock");
284
-
match fs::metadata(&sync_lock_path) {
285
-
Ok(_) => {
286
-
fs::remove_file(&sync_lock_path).unwrap();
287
-
}
288
-
Err(_) => {}
293
+
let sync_lock_path = dirs::home_dir()
294
+
.unwrap()
295
+
.join("AppData\\Roaming\\PhazeDev\\VRChatPhotoManager\\.sync_lock");
296
+
297
+
match fs::metadata(&sync_lock_path) {
298
+
Ok(_) => {
299
+
fs::remove_file(&sync_lock_path).unwrap();
289
300
}
301
+
Err(_) => {}
302
+
}
290
303
291
-
println!("Loading App...");
292
-
let photos_path = get_photo_path();
304
+
println!("Loading App...");
305
+
let photos_path = get_photo_path();
293
306
294
-
match fs::metadata(&photos_path) {
295
-
Ok(_) => {}
296
-
Err(_) => {
297
-
fs::create_dir(&photos_path).unwrap();
298
-
}
299
-
};
307
+
match fs::metadata(&photos_path) {
308
+
Ok(_) => {}
309
+
Err(_) => {
310
+
fs::create_dir(&photos_path).unwrap();
311
+
}
312
+
};
300
313
301
-
let args: Vec<String> = env::args().collect();
314
+
let args: Vec<String> = env::args().collect();
302
315
303
-
let mut update = true;
304
-
for arg in args {
305
-
if arg == "--no-update" {
306
-
update = false;
307
-
}
316
+
let mut update = true;
317
+
for arg in args {
318
+
if arg == "--no-update" {
319
+
update = false;
308
320
}
321
+
}
309
322
310
-
if update {
311
-
// Auto update
312
-
thread::spawn(move || {
313
-
let client = reqwest::blocking::Client::new();
323
+
if update {
324
+
// Auto update
325
+
thread::spawn(move || {
326
+
let client = reqwest::blocking::Client::new();
314
327
315
-
let latest_version = client
316
-
.get("https://cdn.phaz.uk/vrcpm/latest")
317
-
.send()
318
-
.unwrap()
319
-
.text()
320
-
.unwrap();
328
+
let latest_version = client
329
+
.get("https://cdn.phaz.uk/vrcpm/latest")
330
+
.send()
331
+
.unwrap()
332
+
.text()
333
+
.unwrap();
321
334
322
-
if latest_version != VERSION {
323
-
match fs::metadata(&container_folder.join("./updater.exe")) {
324
-
Ok(_) => {}
325
-
Err(_) => {
326
-
let latest_installer = client
327
-
.get("https://cdn.phaz.uk/vrcpm/vrcpm-installer.exe")
328
-
.timeout(Duration::from_secs(120))
329
-
.send()
330
-
.unwrap()
331
-
.bytes()
332
-
.unwrap();
335
+
if latest_version != VERSION {
336
+
match fs::metadata(&container_folder.join("./updater.exe")) {
337
+
Ok(_) => {}
338
+
Err(_) => {
339
+
let latest_installer = client
340
+
.get("https://cdn.phaz.uk/vrcpm/vrcpm-installer.exe")
341
+
.timeout(Duration::from_secs(120))
342
+
.send()
343
+
.unwrap()
344
+
.bytes()
345
+
.unwrap();
333
346
334
-
fs::write(&container_folder.join("./updater.exe"), latest_installer)
335
-
.unwrap();
336
-
}
337
-
}
347
+
fs::write(&container_folder.join("./updater.exe"), latest_installer)
348
+
.unwrap();
349
+
}
350
+
}
338
351
339
-
let mut cmd = Command::new(&container_folder.join("./updater.exe"));
340
-
cmd.current_dir(container_folder);
341
-
cmd.spawn().expect("Cannot run updater");
352
+
let mut cmd = Command::new(&container_folder.join("./updater.exe"));
353
+
cmd.current_dir(container_folder);
354
+
cmd.spawn().expect("Cannot run updater");
342
355
343
-
process::exit(0);
344
-
}
345
-
});
346
-
}
356
+
process::exit(0);
357
+
}
358
+
});
359
+
}
347
360
348
-
// Listen for file updates, store each update in an mpsc channel and send to the frontend
349
-
let (sender, receiver) = std::sync::mpsc::channel();
350
-
let mut watcher = notify::recommended_watcher(move | res: Result<notify::Event, notify::Error> | {
361
+
// Listen for file updates, store each update in an mpsc channel and send to the frontend
362
+
let (sender, receiver) = std::sync::mpsc::channel();
363
+
let mut watcher = notify::recommended_watcher(move | res: Result<notify::Event, notify::Error> | {
351
364
match res {
352
-
Ok(event) => {
365
+
Ok(event) => {
353
366
match event.kind{
354
367
EventKind::Remove(_) => {
355
368
let path = event.paths.first().unwrap();
···
385
398
}
386
399
}).unwrap();
387
400
388
-
watcher
389
-
.watch(&get_photo_path(), RecursiveMode::Recursive)
390
-
.unwrap();
401
+
watcher
402
+
.watch(&get_photo_path(), RecursiveMode::Recursive)
403
+
.unwrap();
404
+
405
+
tauri::Builder::default()
406
+
.plugin(tauri_plugin_process::init())
407
+
.plugin(tauri_plugin_http::init())
408
+
.plugin(tauri_plugin_shell::init())
409
+
.register_asynchronous_uri_scheme_protocol("photo", move |_app, request, responder| {
410
+
thread::spawn(move || {
411
+
// Loads the requested image file, sends data back to the user
412
+
let uri = request.uri();
413
+
414
+
if request.method() != "GET" {
415
+
responder.respond(
416
+
Response::builder()
417
+
.status(404)
418
+
.header("Access-Control-Allow-Origin", "*")
419
+
.body(Vec::new())
420
+
.unwrap(),
421
+
);
422
+
423
+
return;
424
+
}
425
+
426
+
let path = uri.path().split_at(1).1;
427
+
let file = fs::File::open(path);
428
+
429
+
match file {
430
+
Ok(mut file) => {
431
+
match uri.query().unwrap(){
432
+
"downscale" => {
433
+
let decoder = PngDecoder::new(BufReader::new(&file)).unwrap();
434
+
let src_image = DynamicImage::from_decoder(decoder).unwrap();
435
+
436
+
let size_multiplier: f32 = 200.0 / src_image.height() as f32;
437
+
438
+
let dst_width = (src_image.width() as f32 * size_multiplier).floor() as u32;
439
+
let dst_height: u32 = 200;
440
+
441
+
let mut dst_image = Image::new(dst_width, dst_height, src_image.pixel_type().unwrap());
442
+
let mut resizer = Resizer::new();
391
443
392
-
tauri::Builder::default()
393
-
.plugin(tauri_plugin_process::init())
394
-
.plugin(tauri_plugin_http::init())
395
-
.plugin(tauri_plugin_shell::init())
396
-
.register_asynchronous_uri_scheme_protocol("photo", move |_app, request, responder| {
397
-
thread::spawn(move || {
398
-
// Loads the requested image file, sends data back to the user
399
-
let uri = request.uri();
444
+
let opts = ResizeOptions::new()
445
+
.resize_alg(fast_image_resize::ResizeAlg::Nearest);
400
446
401
-
if request.method() != "GET" {
402
-
responder.respond(Response::builder()
403
-
.status(404)
404
-
.header("Access-Control-Allow-Origin", "*")
405
-
.body(Vec::new())
406
-
.unwrap());
447
+
resizer.resize(&src_image, &mut dst_image, Some(&opts)).unwrap();
407
448
408
-
return;
409
-
}
449
+
let mut buf = Vec::new();
450
+
let encoder = PngEncoder::new(&mut buf);
410
451
411
-
let path = uri.path().split_at(1).1;
412
-
let file = fs::File::open(path);
452
+
encoder.write_image(dst_image.buffer(), dst_width, dst_height, src_image.color().into()).unwrap();
413
453
414
-
match file {
415
-
Ok(mut file) => {
416
-
let mut buffer = Vec::new();
454
+
let res = Response::builder()
455
+
.status(200)
456
+
.header("Access-Control-Allow-Origin", "*")
457
+
.body(buf)
458
+
.unwrap();
417
459
418
-
let _out = file.read_to_end(&mut buffer);
460
+
responder.respond(res);
461
+
},
462
+
_ => {
463
+
let mut buf = Vec::new();
464
+
file.read_to_end(&mut buf).unwrap();
419
465
420
-
let res = Response::builder()
421
-
.status(200)
422
-
.header("Access-Control-Allow-Origin", "*")
423
-
.body(buffer)
424
-
.unwrap();
466
+
let res = Response::builder()
467
+
.status(200)
468
+
.header("Access-Control-Allow-Origin", "*")
469
+
.body(buf)
470
+
.unwrap();
425
471
426
-
responder.respond(res);
427
-
}
428
-
Err(_) => {
429
-
responder.respond(Response::builder()
430
-
.status(404)
431
-
.header("Access-Control-Allow-Origin", "*")
432
-
.body(b"File Not Found")
433
-
.unwrap());
434
-
}
472
+
responder.respond(res);
435
473
}
436
-
});
437
-
})
438
-
.on_window_event(| window, event | match event {
439
-
WindowEvent::CloseRequested { api, .. } => {
440
-
window.hide().unwrap();
441
-
api.prevent_close();
474
+
}
442
475
}
443
-
_ => {}
444
-
})
445
-
.setup(|app| {
446
-
let handle = app.handle().clone();
476
+
Err(_) => {
477
+
responder.respond(
478
+
Response::builder()
479
+
.status(404)
480
+
.header("Access-Control-Allow-Origin", "*")
481
+
.body(b"File Not Found")
482
+
.unwrap(),
483
+
);
484
+
}
485
+
}
486
+
});
487
+
})
488
+
.on_window_event(|window, event| match event {
489
+
WindowEvent::CloseRequested { api, .. } => {
490
+
window.hide().unwrap();
491
+
api.prevent_close();
492
+
}
493
+
_ => {}
494
+
})
495
+
.setup(|app| {
496
+
let handle = app.handle().clone();
447
497
448
-
// Setup the tray icon and menu buttons
449
-
let quit = MenuItemBuilder::new("Quit").id("quit").build(&handle).unwrap();
450
-
let hide = MenuItemBuilder::new("Hide / Show").id("hide").build(&handle).unwrap();
498
+
// Setup the tray icon and menu buttons
499
+
let quit = MenuItemBuilder::new("Quit")
500
+
.id("quit")
501
+
.build(&handle)
502
+
.unwrap();
451
503
452
-
let tray_menu = MenuBuilder::new(&handle)
453
-
.items(&[ &quit, &hide ])
454
-
.build().unwrap();
504
+
let hide = MenuItemBuilder::new("Hide / Show")
505
+
.id("hide")
506
+
.build(&handle)
507
+
.unwrap();
455
508
456
-
TrayIconBuilder::with_id("vrcpm-tray")
457
-
.menu(&tray_menu)
458
-
.on_menu_event(move | app: &AppHandle, event |{
459
-
match event.id().as_ref() {
460
-
"quit" => {
461
-
std::process::exit(0);
462
-
}
463
-
"hide" => {
464
-
let window = app.get_webview_window("main").unwrap();
509
+
let tray_menu = MenuBuilder::new(&handle)
510
+
.items(&[&quit, &hide])
511
+
.build()
512
+
.unwrap();
465
513
466
-
if window.is_visible().unwrap() {
467
-
window.hide().unwrap();
468
-
} else {
469
-
window.show().unwrap();
470
-
window.set_focus().unwrap();
471
-
}
472
-
}
473
-
_ => {}
474
-
}
475
-
})
476
-
.on_tray_icon_event(| tray, event |{
477
-
if let TrayIconEvent::Click {
478
-
button: MouseButton::Left,
479
-
button_state: MouseButtonState::Up,
480
-
..
481
-
} = event{
482
-
let window = tray.app_handle().get_webview_window("main").unwrap();
514
+
TrayIconBuilder::with_id("main")
515
+
.icon(tauri::image::Image::from_bytes(include_bytes!("../icons/32x32.png")).unwrap())
516
+
.menu(&tray_menu)
517
+
.on_menu_event(move |app: &AppHandle, event| match event.id().as_ref() {
518
+
"quit" => {
519
+
std::process::exit(0);
520
+
}
521
+
"hide" => {
522
+
let window = app.get_webview_window("main").unwrap();
483
523
484
-
window.show().unwrap();
485
-
window.set_focus().unwrap();
486
-
}
487
-
})
488
-
.build(&handle).unwrap();
524
+
if window.is_visible().unwrap() {
525
+
window.hide().unwrap();
526
+
} else {
527
+
window.show().unwrap();
528
+
window.set_focus().unwrap();
529
+
}
530
+
}
531
+
_ => {}
532
+
})
533
+
.on_tray_icon_event(|tray, event| {
534
+
if let TrayIconEvent::Click {
535
+
button: MouseButton::Left,
536
+
button_state: MouseButtonState::Up,
537
+
..
538
+
} = event
539
+
{
540
+
let window = tray.app_handle().get_webview_window("main").unwrap();
489
541
490
-
// Register "deep link" for authentication via vrcpm://
491
-
tauri_plugin_deep_link::register("vrcpm", move |request| {
492
-
let mut command: u8 = 0;
493
-
let mut index: u8 = 0;
542
+
window.show().unwrap();
543
+
window.set_focus().unwrap();
544
+
}
545
+
})
546
+
.build(&handle)
547
+
.unwrap();
548
+
// Register "deep link" for authentication via vrcpm://
549
+
tauri_plugin_deep_link::register("vrcpm", move |request| {
550
+
let mut command: u8 = 0;
551
+
let mut index: u8 = 0;
494
552
495
-
for part in request.split('/').into_iter() {
496
-
index += 1;
553
+
for part in request.split('/').into_iter() {
554
+
index += 1;
497
555
498
-
if index == 3 && part == "auth-callback" {
499
-
command = 1;
500
-
}
556
+
if index == 3 && part == "auth-callback" {
557
+
command = 1;
558
+
}
501
559
502
-
if index == 3 && part == "auth-denied" {
503
-
handle.emit("auth-denied", "null").unwrap();
504
-
}
560
+
if index == 3 && part == "auth-denied" {
561
+
handle.emit("auth-denied", "null").unwrap();
562
+
}
505
563
506
-
if index == 4 && command == 1 {
507
-
handle.emit("auth-callback", part).unwrap();
508
-
}
509
-
}
510
-
})
511
-
.unwrap();
564
+
if index == 4 && command == 1 {
565
+
handle.emit("auth-callback", part).unwrap();
566
+
}
567
+
}
568
+
})
569
+
.unwrap();
512
570
513
-
// I hate this approach but i have no clue how else to do this...
514
-
// reads the mpsc channel and sends the events to the frontend
515
-
let window = app.get_webview_window("main").unwrap();
516
-
thread::spawn(move || {
517
-
thread::sleep(time::Duration::from_millis(100));
571
+
// I hate this approach but i have no clue how else to do this...
572
+
// reads the mpsc channel and sends the events to the frontend
573
+
let window = app.get_webview_window("main").unwrap();
574
+
thread::spawn(move || {
575
+
thread::sleep(time::Duration::from_millis(100));
518
576
519
-
for event in receiver {
520
-
match event.0 {
521
-
1 => {
522
-
window.emit("photo_create", event.1).unwrap();
523
-
}
524
-
2 => {
525
-
window.emit("photo_remove", event.1).unwrap();
526
-
}
527
-
_ => {}
528
-
}
529
-
}
530
-
});
577
+
for event in receiver {
578
+
match event.0 {
579
+
1 => {
580
+
window.emit("photo_create", event.1).unwrap();
581
+
}
582
+
2 => {
583
+
window.emit("photo_remove", event.1).unwrap();
584
+
}
585
+
_ => {}
586
+
}
587
+
}
588
+
});
531
589
532
-
Ok(())
533
-
})
534
-
.invoke_handler(tauri::generate_handler![
535
-
start_user_auth,
536
-
load_photos,
537
-
close_splashscreen,
538
-
load_photo_meta,
539
-
delete_photo,
540
-
open_url,
541
-
find_world_by_id,
542
-
start_with_win,
543
-
get_user_photos_path,
544
-
change_final_path,
545
-
sync_photos,
546
-
get_version,
547
-
relaunch
548
-
])
549
-
.run(tauri::generate_context!())
550
-
.expect("error while running tauri application");
590
+
Ok(())
591
+
})
592
+
.invoke_handler(tauri::generate_handler![
593
+
start_user_auth,
594
+
load_photos,
595
+
close_splashscreen,
596
+
load_photo_meta,
597
+
delete_photo,
598
+
open_url,
599
+
open_folder,
600
+
find_world_by_id,
601
+
start_with_win,
602
+
get_user_photos_path,
603
+
change_final_path,
604
+
sync_photos,
605
+
get_version,
606
+
relaunch
607
+
])
608
+
.run(tauri::generate_context!())
609
+
.expect("error while running tauri application");
551
610
}
+204
-200
src-tauri/src/photosync.rs
+204
-200
src-tauri/src/photosync.rs
···
7
7
8
8
#[derive(Clone, Serialize)]
9
9
struct PhotoUploadMeta {
10
-
photos_uploading: usize,
11
-
photos_total: usize,
10
+
photos_uploading: usize,
11
+
photos_total: usize,
12
12
}
13
13
14
14
pub fn sync_photos(token: String, path: path::PathBuf, window: tauri::Window) {
15
-
let sync_lock_path = dirs::home_dir()
16
-
.unwrap()
17
-
.join("AppData\\Roaming\\PhazeDev\\VRChatPhotoManager\\.sync_lock");
18
-
match fs::metadata(&sync_lock_path) {
19
-
Ok(_) => {
20
-
return;
21
-
}
22
-
Err(_) => {}
15
+
let sync_lock_path = dirs::home_dir()
16
+
.unwrap()
17
+
.join("AppData\\Roaming\\PhazeDev\\VRChatPhotoManager\\.sync_lock");
18
+
19
+
match fs::metadata(&sync_lock_path) {
20
+
Ok(_) => {
21
+
return;
23
22
}
23
+
Err(_) => {}
24
+
}
24
25
25
-
fs::write(&sync_lock_path, "Currently Syncing").unwrap();
26
+
fs::write(&sync_lock_path, "Currently Syncing").unwrap();
26
27
27
-
match fs::metadata(&path) {
28
-
Ok(_) => {}
29
-
Err(_) => {
30
-
fs::create_dir(&path).unwrap();
31
-
}
32
-
};
28
+
match fs::metadata(&path) {
29
+
Ok(_) => {}
30
+
Err(_) => {
31
+
fs::create_dir(&path).unwrap();
32
+
}
33
+
};
33
34
34
-
let mut photos: Vec<String> = Vec::new();
35
+
let mut photos: Vec<String> = Vec::new();
35
36
36
-
for folder in fs::read_dir(&path).unwrap() {
37
-
let f = folder.unwrap();
37
+
for folder in fs::read_dir(&path).unwrap() {
38
+
let f = folder.unwrap();
38
39
39
-
if f.metadata().unwrap().is_dir() {
40
-
match fs::read_dir(f.path()) {
41
-
Ok(dir) => {
42
-
for photo in dir {
43
-
let p = photo.unwrap();
40
+
if f.metadata().unwrap().is_dir() {
41
+
match fs::read_dir(f.path()) {
42
+
Ok(dir) => {
43
+
for photo in dir {
44
+
let p = photo.unwrap();
44
45
45
-
let re1 = Regex::new(r"(?m)VRChat_[0-9]{4}-[0-9]{2}-[0-9]{2}_[0-9]{2}-[0-9]{2}-[0-9]{2}.[0-9]{3}_[0-9]{4}x[0-9]{4}.png").unwrap();
46
-
let re2 = Regex::new(
47
-
r"(?m)VRChat_[0-9]{4}-[0-9]{2}-[0-9]{2}_[0-9]{2}-[0-9]{2}-[0-9]{2}.[0-9]{3}_[0-9]{4}x[0-9]{4}_wrld_[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}.png").unwrap();
46
+
let re1 = Regex::new(r"(?m)VRChat_[0-9]{4}-[0-9]{2}-[0-9]{2}_[0-9]{2}-[0-9]{2}-[0-9]{2}.[0-9]{3}_[0-9]{4}x[0-9]{4}.png").unwrap();
47
+
let re2 = Regex::new(
48
+
r"(?m)VRChat_[0-9]{4}-[0-9]{2}-[0-9]{2}_[0-9]{2}-[0-9]{2}-[0-9]{2}.[0-9]{3}_[0-9]{4}x[0-9]{4}_wrld_[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}.png").unwrap();
48
49
49
-
if re1.is_match(p.file_name().to_str().unwrap())
50
-
|| re2.is_match(p.file_name().to_str().unwrap())
51
-
{
52
-
photos.push(p.file_name().into_string().unwrap());
53
-
}
54
-
}
55
-
}
56
-
Err(_) => {}
50
+
if re1.is_match(p.file_name().to_str().unwrap())
51
+
|| re2.is_match(p.file_name().to_str().unwrap())
52
+
{
53
+
photos.push(p.file_name().into_string().unwrap());
57
54
}
55
+
}
58
56
}
57
+
Err(_) => {}
58
+
}
59
59
}
60
+
}
60
61
61
-
let body = reqwest::blocking::get(format!(
62
-
"https://photos-cdn.phazed.xyz/api/v1/photos/exists?token={}",
63
-
&token
64
-
))
65
-
.unwrap()
66
-
.text()
67
-
.unwrap();
62
+
let body = reqwest::blocking::get(format!(
63
+
"https://photos-cdn.phazed.xyz/api/v1/photos/exists?token={}",
64
+
&token
65
+
))
66
+
.unwrap()
67
+
.text()
68
+
.unwrap();
68
69
69
-
let body: Value = serde_json::from_str(&body).unwrap();
70
+
let body: Value = serde_json::from_str(&body).unwrap();
70
71
71
-
let mut photos_to_upload: Vec<String> = Vec::new();
72
-
let uploaded_photos = body["files"].as_array().unwrap();
72
+
let mut photos_to_upload: Vec<String> = Vec::new();
73
+
let uploaded_photos = body["files"].as_array().unwrap();
73
74
74
-
let photos_len = photos.len();
75
+
let photos_len = photos.len();
75
76
76
-
for photo in &photos {
77
-
let mut found_photo = false;
77
+
for photo in &photos {
78
+
let mut found_photo = false;
78
79
79
-
for uploaded_photo in uploaded_photos {
80
-
if photo == uploaded_photo.as_str().unwrap() {
81
-
found_photo = true;
82
-
break;
83
-
}
84
-
}
80
+
for uploaded_photo in uploaded_photos {
81
+
if photo == uploaded_photo.as_str().unwrap() {
82
+
found_photo = true;
83
+
break;
84
+
}
85
+
}
85
86
86
-
if !found_photo {
87
-
photos_to_upload.push(photo.clone());
88
-
}
87
+
if !found_photo {
88
+
photos_to_upload.push(photo.clone());
89
89
}
90
+
}
90
91
91
-
window
92
-
.emit(
93
-
"photos-upload-meta",
94
-
PhotoUploadMeta {
95
-
photos_uploading: photos_to_upload.len(),
96
-
photos_total: photos_len,
97
-
},
98
-
)
99
-
.unwrap();
100
-
let mut photos_left = photos_to_upload.len();
92
+
window
93
+
.emit(
94
+
"photos-upload-meta",
95
+
PhotoUploadMeta {
96
+
photos_uploading: photos_to_upload.len(),
97
+
photos_total: photos_len,
98
+
},
99
+
)
100
+
.unwrap();
101
101
102
-
let client = reqwest::blocking::Client::new();
102
+
let mut photos_left = photos_to_upload.len();
103
103
104
-
loop {
105
-
match photos_to_upload.pop() {
106
-
Some(photo) => {
107
-
let folder_name = photo.clone().replace("VRChat_", "");
108
-
let mut folder_name = folder_name.split("-");
109
-
let folder_name = format!(
110
-
"{}-{}",
111
-
folder_name.nth(0).unwrap(),
112
-
folder_name.nth(0).unwrap()
113
-
);
104
+
let client = reqwest::blocking::Client::new();
114
105
115
-
let full_path = format!("{}\\{}\\{}", path.to_str().unwrap(), folder_name, photo);
116
-
let file = fs::File::open(full_path);
106
+
loop {
107
+
match photos_to_upload.pop() {
108
+
Some(photo) => {
109
+
let folder_name = photo.clone().replace("VRChat_", "");
110
+
let mut folder_name = folder_name.split("-");
111
+
let folder_name = format!(
112
+
"{}-{}",
113
+
folder_name.nth(0).unwrap(),
114
+
folder_name.nth(0).unwrap()
115
+
);
117
116
118
-
match file {
119
-
Ok(file) => {
120
-
let res = client
121
-
.put(format!(
122
-
"https://photos-cdn.phazed.xyz/api/v1/photos?token={}",
123
-
&token
124
-
))
125
-
.header("Content-Type", "image/png")
126
-
.header("filename", photo)
127
-
.body(file)
128
-
.timeout(Duration::from_secs(120))
129
-
.send()
130
-
.unwrap()
131
-
.text()
132
-
.unwrap();
117
+
let full_path = format!("{}\\{}\\{}", path.to_str().unwrap(), folder_name, photo);
118
+
let file = fs::File::open(full_path);
133
119
134
-
let res: Result<Value, Error> = serde_json::from_str(&res);
120
+
match file {
121
+
Ok(file) => {
122
+
let res = client
123
+
.put(format!(
124
+
"https://photos-cdn.phazed.xyz/api/v1/photos?token={}",
125
+
&token
126
+
))
127
+
.header("Content-Type", "image/png")
128
+
.header("filename", photo)
129
+
.body(file)
130
+
.timeout(Duration::from_secs(120))
131
+
.send()
132
+
.unwrap()
133
+
.text()
134
+
.unwrap();
135
135
136
-
match res {
137
-
Ok(res) => {
138
-
if !res["ok"].as_bool().unwrap() {
139
-
println!(
140
-
"Failed to upload: {}",
141
-
res["error"].as_str().unwrap()
142
-
);
143
-
window
144
-
.emit("sync-failed", res["error"].as_str().unwrap())
145
-
.unwrap();
146
-
break;
147
-
}
148
-
}
149
-
Err(err) => {
150
-
dbg!(err);
151
-
}
152
-
}
136
+
let res: Result<Value, Error> = serde_json::from_str(&res);
137
+
138
+
match res {
139
+
Ok(res) => {
140
+
if !res["ok"].as_bool().unwrap() {
141
+
println!(
142
+
"Failed to upload: {}",
143
+
res["error"].as_str().unwrap()
144
+
);
145
+
146
+
window
147
+
.emit("sync-failed", res["error"].as_str().unwrap())
148
+
.unwrap();
149
+
150
+
break;
153
151
}
154
-
Err(_) => {}
152
+
}
153
+
Err(err) => {
154
+
dbg!(err);
155
+
}
155
156
}
156
-
157
-
photos_left -= 1;
158
-
window
159
-
.emit(
160
-
"photos-upload-meta",
161
-
PhotoUploadMeta {
162
-
photos_uploading: photos_left,
163
-
photos_total: photos_len,
164
-
},
165
-
)
166
-
.unwrap();
167
157
}
168
-
None => {
169
-
break;
170
-
}
158
+
Err(_) => {}
171
159
}
160
+
161
+
photos_left -= 1;
162
+
window
163
+
.emit(
164
+
"photos-upload-meta",
165
+
PhotoUploadMeta {
166
+
photos_uploading: photos_left,
167
+
photos_total: photos_len,
168
+
},
169
+
)
170
+
.unwrap();
171
+
}
172
+
None => {
173
+
break;
174
+
}
172
175
}
176
+
}
173
177
174
-
println!("Finished Uploading.");
175
-
let mut photos_to_download: Vec<String> = Vec::new();
178
+
println!("Finished Uploading.");
179
+
let mut photos_to_download: Vec<String> = Vec::new();
176
180
177
-
for photo in uploaded_photos {
178
-
let mut found_photo = false;
179
-
let photo = photo.as_str().unwrap().to_string();
180
-
181
-
for uploaded_photo in &photos {
182
-
if &photo == uploaded_photo {
183
-
found_photo = true;
184
-
break;
185
-
}
186
-
}
181
+
for photo in uploaded_photos {
182
+
let mut found_photo = false;
183
+
let photo = photo.as_str().unwrap().to_string();
187
184
188
-
if !found_photo {
189
-
photos_to_download.push(photo);
190
-
}
185
+
for uploaded_photo in &photos {
186
+
if &photo == uploaded_photo {
187
+
found_photo = true;
188
+
break;
189
+
}
191
190
}
192
191
193
-
photos_to_download.reverse();
192
+
if !found_photo {
193
+
photos_to_download.push(photo);
194
+
}
195
+
}
194
196
195
-
let photos_len = photos_to_download.len();
196
-
let mut photos_left = photos_to_download.len();
197
-
198
-
loop {
199
-
match photos_to_download.pop() {
200
-
Some(photo) => {
201
-
let folder_name = photo.clone().replace("VRChat_", "");
202
-
let mut folder_name = folder_name.split("-");
203
-
let folder_name = format!(
204
-
"{}-{}",
205
-
folder_name.nth(0).unwrap(),
206
-
folder_name.nth(0).unwrap()
207
-
);
197
+
photos_to_download.reverse();
208
198
209
-
let full_path = format!("{}\\{}\\{}", path.to_str().unwrap(), folder_name, photo);
199
+
let photos_len = photos_to_download.len();
200
+
let mut photos_left = photos_to_download.len();
210
201
211
-
let res = client
212
-
.get(format!(
213
-
"https://photos-cdn.phazed.xyz/api/v1/photos?token={}&photo={}",
214
-
&token, &photo
215
-
))
216
-
.timeout(Duration::from_secs(120))
217
-
.send()
218
-
.unwrap()
219
-
.bytes();
202
+
loop {
203
+
match photos_to_download.pop() {
204
+
Some(photo) => {
205
+
let folder_name = photo.clone().replace("VRChat_", "");
206
+
let mut folder_name = folder_name.split("-");
207
+
let folder_name = format!(
208
+
"{}-{}",
209
+
folder_name.nth(0).unwrap(),
210
+
folder_name.nth(0).unwrap()
211
+
);
220
212
221
-
match res {
222
-
Ok(res) => {
223
-
let folder_path = format!("{}\\{}", path.to_str().unwrap(), folder_name);
224
-
match fs::metadata(&folder_path) {
225
-
Ok(_) => {}
226
-
Err(_) => {
227
-
fs::create_dir(folder_path).unwrap();
228
-
}
229
-
}
213
+
let full_path = format!("{}\\{}\\{}", path.to_str().unwrap(), folder_name, photo);
230
214
231
-
let mut file = fs::File::create(full_path).unwrap();
232
-
file.write_all(&res).unwrap();
233
-
}
234
-
Err(err) => {
235
-
dbg!(err);
236
-
}
237
-
}
215
+
let res = client
216
+
.get(format!(
217
+
"https://photos-cdn.phazed.xyz/api/v1/photos?token={}&photo={}",
218
+
&token, &photo
219
+
))
220
+
.timeout(Duration::from_secs(120))
221
+
.send()
222
+
.unwrap()
223
+
.bytes();
238
224
239
-
photos_left -= 1;
240
-
window
241
-
.emit(
242
-
"photos-download-meta",
243
-
PhotoUploadMeta {
244
-
photos_uploading: photos_left,
245
-
photos_total: photos_len,
246
-
},
247
-
)
248
-
.unwrap();
225
+
match res {
226
+
Ok(res) => {
227
+
let folder_path = format!("{}\\{}", path.to_str().unwrap(), folder_name);
228
+
match fs::metadata(&folder_path) {
229
+
Ok(_) => {}
230
+
Err(_) => {
231
+
fs::create_dir(folder_path).unwrap();
232
+
}
249
233
}
250
-
None => {
251
-
break;
252
-
}
234
+
235
+
let mut file = fs::File::create(full_path).unwrap();
236
+
file.write_all(&res).unwrap();
237
+
}
238
+
Err(err) => {
239
+
dbg!(err);
240
+
}
253
241
}
242
+
243
+
photos_left -= 1;
244
+
window
245
+
.emit(
246
+
"photos-download-meta",
247
+
PhotoUploadMeta {
248
+
photos_uploading: photos_left,
249
+
photos_total: photos_len,
250
+
},
251
+
)
252
+
.unwrap();
253
+
}
254
+
None => {
255
+
break;
256
+
}
254
257
}
258
+
}
255
259
256
-
println!("Finished Downloading.");
260
+
println!("Finished Downloading.");
257
261
258
-
fs::remove_file(&sync_lock_path).unwrap();
259
-
window.emit("sync-finished", "h").unwrap();
262
+
fs::remove_file(&sync_lock_path).unwrap();
263
+
window.emit("sync-finished", "h").unwrap();
260
264
}
+85
-84
src-tauri/src/pngmeta.rs
+85
-84
src-tauri/src/pngmeta.rs
···
3
3
4
4
#[derive(Clone)]
5
5
pub struct PNGImage {
6
-
width: u32,
7
-
height: u32,
8
-
bit_depth: u8,
9
-
colour_type: u8,
10
-
compression_method: u8,
11
-
filter_method: u8,
12
-
interlace_method: u8,
13
-
metadata: String,
14
-
path: String,
6
+
width: u32,
7
+
height: u32,
8
+
bit_depth: u8,
9
+
colour_type: u8,
10
+
compression_method: u8,
11
+
filter_method: u8,
12
+
interlace_method: u8,
13
+
metadata: String,
14
+
path: String,
15
15
}
16
16
17
17
impl PNGImage {
18
-
pub fn new(buff: Vec<u8>, path: String) -> PNGImage {
19
-
let mut img = PNGImage {
20
-
width: 0,
21
-
height: 0,
22
-
bit_depth: 0,
23
-
colour_type: 0,
24
-
compression_method: 0,
25
-
filter_method: 0,
26
-
interlace_method: 0,
27
-
metadata: "".to_string(),
28
-
path: path,
29
-
};
18
+
pub fn new(buff: Vec<u8>, path: String) -> PNGImage {
19
+
let mut img = PNGImage {
20
+
width: 0,
21
+
height: 0,
22
+
bit_depth: 0,
23
+
colour_type: 0,
24
+
compression_method: 0,
25
+
filter_method: 0,
26
+
interlace_method: 0,
27
+
metadata: "".to_string(),
28
+
path: path,
29
+
};
30
+
31
+
if buff[0] != 0x89
32
+
|| buff[1] != 0x50
33
+
|| buff[2] != 0x4E
34
+
|| buff[3] != 0x47
35
+
|| buff[4] != 0x0D
36
+
|| buff[5] != 0x0A
37
+
|| buff[6] != 0x1A
38
+
|| buff[7] != 0x0A
39
+
{
40
+
panic!("Image is not a PNG file");
41
+
}
30
42
31
-
if buff[0] != 0x89
32
-
|| buff[1] != 0x50
33
-
|| buff[2] != 0x4E
34
-
|| buff[3] != 0x47
35
-
|| buff[4] != 0x0D
36
-
|| buff[5] != 0x0A
37
-
|| buff[6] != 0x1A
38
-
|| buff[7] != 0x0A
39
-
{
40
-
panic!("Image is not a PNG file");
41
-
}
43
+
img.read_png_chunk(8, buff);
44
+
img
45
+
}
42
46
43
-
img.read_png_chunk(8, buff);
47
+
fn read_png_chunk(&mut self, start_byte: usize, buff: Vec<u8>) {
48
+
let data_buff = buff[start_byte..].to_vec();
44
49
45
-
img
46
-
}
50
+
let length = u32::from_le_bytes([data_buff[3], data_buff[2], data_buff[1], data_buff[0]]);
51
+
let chunk_type = str::from_utf8(&data_buff[4..8]).unwrap();
47
52
48
-
fn read_png_chunk(&mut self, start_byte: usize, buff: Vec<u8>) {
49
-
let data_buff = buff[start_byte..].to_vec();
53
+
match chunk_type {
54
+
"IHDR" => {
55
+
self.width =
56
+
u32::from_le_bytes([data_buff[11], data_buff[10], data_buff[9], data_buff[8]]);
50
57
51
-
let length = u32::from_le_bytes([data_buff[3], data_buff[2], data_buff[1], data_buff[0]]);
52
-
let chunk_type = str::from_utf8(&data_buff[4..8]).unwrap();
58
+
self.height = u32::from_le_bytes([
59
+
data_buff[15],
60
+
data_buff[14],
61
+
data_buff[13],
62
+
data_buff[12],
63
+
]);
53
64
54
-
match chunk_type {
55
-
"IHDR" => {
56
-
self.width =
57
-
u32::from_le_bytes([data_buff[11], data_buff[10], data_buff[9], data_buff[8]]);
58
-
self.height = u32::from_le_bytes([
59
-
data_buff[15],
60
-
data_buff[14],
61
-
data_buff[13],
62
-
data_buff[12],
63
-
]);
64
-
self.bit_depth = data_buff[16];
65
-
self.colour_type = data_buff[17];
66
-
self.compression_method = data_buff[18];
67
-
self.filter_method = data_buff[19];
68
-
self.interlace_method = data_buff[20];
65
+
self.bit_depth = data_buff[16];
66
+
self.colour_type = data_buff[17];
67
+
self.compression_method = data_buff[18];
68
+
self.filter_method = data_buff[19];
69
+
self.interlace_method = data_buff[20];
69
70
70
-
self.read_png_chunk((length + 12) as usize, data_buff);
71
-
}
72
-
"iTXt" => {
73
-
let end_byte = (8 + length) as usize;
74
-
let d = str::from_utf8(&data_buff[8..end_byte]).unwrap();
71
+
self.read_png_chunk((length + 12) as usize, data_buff);
72
+
}
73
+
"iTXt" => {
74
+
let end_byte = (8 + length) as usize;
75
+
let d = str::from_utf8(&data_buff[8..end_byte]).unwrap();
75
76
76
-
self.metadata = d.to_string();
77
+
self.metadata = d.to_string();
77
78
78
-
self.read_png_chunk((length + 12) as usize, data_buff);
79
-
}
80
-
"IEND" => {}
81
-
"IDAT" => {}
82
-
_ => {
83
-
self.read_png_chunk((length + 12) as usize, data_buff);
84
-
}
85
-
}
79
+
self.read_png_chunk((length + 12) as usize, data_buff);
80
+
}
81
+
"IEND" => {}
82
+
"IDAT" => {}
83
+
_ => {
84
+
self.read_png_chunk((length + 12) as usize, data_buff);
85
+
}
86
86
}
87
+
}
87
88
}
88
89
89
90
impl Serialize for PNGImage {
90
-
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
91
-
where
92
-
S: Serializer,
93
-
{
94
-
let mut s = serializer.serialize_struct("PNGImage", 7)?;
95
-
s.serialize_field("width", &self.width)?;
96
-
s.serialize_field("height", &self.height)?;
97
-
s.serialize_field("bit_depth", &self.bit_depth)?;
98
-
s.serialize_field("colour_type", &self.colour_type)?;
99
-
s.serialize_field("compression_method", &self.compression_method)?;
100
-
s.serialize_field("filter_method", &self.filter_method)?;
101
-
s.serialize_field("interlace_method", &self.interlace_method)?;
102
-
s.serialize_field("metadata", &self.metadata)?;
103
-
s.serialize_field("path", &self.path)?;
104
-
s.end()
105
-
}
91
+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
92
+
where
93
+
S: Serializer,
94
+
{
95
+
let mut s = serializer.serialize_struct("PNGImage", 7)?;
96
+
s.serialize_field("width", &self.width)?;
97
+
s.serialize_field("height", &self.height)?;
98
+
s.serialize_field("bit_depth", &self.bit_depth)?;
99
+
s.serialize_field("colour_type", &self.colour_type)?;
100
+
s.serialize_field("compression_method", &self.compression_method)?;
101
+
s.serialize_field("filter_method", &self.filter_method)?;
102
+
s.serialize_field("interlace_method", &self.interlace_method)?;
103
+
s.serialize_field("metadata", &self.metadata)?;
104
+
s.serialize_field("path", &self.path)?;
105
+
s.end()
106
+
}
106
107
}
+97
-96
src-tauri/src/worldscraper.rs
+97
-96
src-tauri/src/worldscraper.rs
···
3
3
4
4
#[derive(Clone)]
5
5
pub struct World {
6
-
id: String,
7
-
name: String,
8
-
author: String,
9
-
author_id: String,
10
-
desc: String,
11
-
img: String,
12
-
max_users: u64,
13
-
visits: u64,
14
-
favourites: u64,
15
-
tags: String,
16
-
from: String,
17
-
from_site: String,
18
-
found: bool,
6
+
id: String,
7
+
name: String,
8
+
author: String,
9
+
author_id: String,
10
+
desc: String,
11
+
img: String,
12
+
max_users: u64,
13
+
visits: u64,
14
+
favourites: u64,
15
+
tags: String,
16
+
from: String,
17
+
from_site: String,
18
+
found: bool,
19
19
}
20
20
21
21
impl World {
22
-
pub fn new(world_id: String) -> World {
23
-
println!("Fetching world data for {}", &world_id);
22
+
pub fn new(world_id: String) -> World {
23
+
println!("Fetching world data for {}", &world_id);
24
24
25
-
let mut world = World {
26
-
id: world_id.clone(),
27
-
name: "".into(),
28
-
author: "".into(),
29
-
author_id: "".into(),
30
-
desc: "".into(),
31
-
img: "".into(),
32
-
max_users: 0,
33
-
visits: 0,
34
-
favourites: 0,
35
-
tags: "".into(),
36
-
from: "https://vrclist.com/worlds/".into(),
37
-
from_site: "vrclist.com".into(),
38
-
found: false,
39
-
};
25
+
let mut world = World {
26
+
id: world_id.clone(),
27
+
name: "".into(),
28
+
author: "".into(),
29
+
author_id: "".into(),
30
+
desc: "".into(),
31
+
img: "".into(),
32
+
max_users: 0,
33
+
visits: 0,
34
+
favourites: 0,
35
+
tags: "".into(),
36
+
from: "https://vrclist.com/worlds/".into(),
37
+
from_site: "vrclist.com".into(),
38
+
found: false,
39
+
};
40
40
41
-
let client = reqwest::blocking::Client::new();
41
+
let client = reqwest::blocking::Client::new();
42
42
43
-
let world_id_str = world_id.to_owned();
44
-
let fixed_id_req = client
45
-
.post("https://api.vrclist.com/worlds/id-convert")
46
-
.header("Content-Type", "application/json")
47
-
.header("User-Agent", "VRChat-Photo-Manager-Rust/0.0.1")
48
-
.body(json!({ "world_id": world_id_str }).to_string())
49
-
.send()
50
-
.unwrap()
51
-
.text()
52
-
.unwrap();
43
+
let world_id_str = world_id.to_owned();
44
+
let fixed_id_req = client
45
+
.post("https://api.vrclist.com/worlds/id-convert")
46
+
.header("Content-Type", "application/json")
47
+
.header("User-Agent", "VRChat-Photo-Manager-Rust/0.0.1")
48
+
.body(json!({ "world_id": world_id_str }).to_string())
49
+
.send()
50
+
.unwrap()
51
+
.text()
52
+
.unwrap();
53
53
54
-
if &fixed_id_req == "" {
55
-
return world;
56
-
}
54
+
if &fixed_id_req == "" {
55
+
println!("World {} not found", world_id);
56
+
return world;
57
+
}
57
58
58
-
world.found = true;
59
+
world.found = true;
59
60
60
-
let fixed_id: serde_json::Value = serde_json::from_str(&fixed_id_req).unwrap();
61
-
world.from = format!("https://vrclist.com/worlds/{}", fixed_id["id"].to_string());
61
+
let fixed_id: serde_json::Value = serde_json::from_str(&fixed_id_req).unwrap();
62
+
world.from = format!("https://vrclist.com/worlds/{}", fixed_id["id"].to_string());
62
63
63
-
let world_data = client
64
-
.post("https://api.vrclist.com/worlds/single")
65
-
.header("Content-Type", "application/json")
66
-
.header("User-Agent", "VRChat-Photo-Manager-Rust/0.0.1")
67
-
.body(json!({ "id": fixed_id["id"].to_string() }).to_string())
68
-
.send()
69
-
.unwrap()
70
-
.text()
71
-
.unwrap();
72
-
73
-
let world_data: serde_json::Value = serde_json::from_str(&world_data).unwrap();
64
+
let world_data = client
65
+
.post("https://api.vrclist.com/worlds/single")
66
+
.header("Content-Type", "application/json")
67
+
.header("User-Agent", "VRChat-Photo-Manager-Rust/0.0.1")
68
+
.body(json!({ "id": fixed_id["id"].to_string() }).to_string())
69
+
.send()
70
+
.unwrap()
71
+
.text()
72
+
.unwrap();
74
73
75
-
world.name = world_data["name"].to_string();
76
-
world.author = world_data["authorName"].to_string();
77
-
world.author_id = world_data["authorId"].to_string();
78
-
world.desc = world_data["description"].to_string();
79
-
world.img = world_data["imageUrl"].to_string();
80
-
world.tags = world_data["tags"].to_string();
74
+
let world_data: serde_json::Value = serde_json::from_str(&world_data).unwrap();
81
75
82
-
match world_data["vrchat_visits"].as_u64() {
83
-
Some(visits) => world.visits = visits,
84
-
None => {}
85
-
}
76
+
world.name = world_data["name"].to_string();
77
+
world.author = world_data["authorName"].to_string();
78
+
world.author_id = world_data["authorId"].to_string();
79
+
world.desc = world_data["description"].to_string();
80
+
world.img = world_data["imageUrl"].to_string();
81
+
world.tags = world_data["tags"].to_string();
86
82
87
-
match world_data["capacity"].as_u64() {
88
-
Some(cap) => {
89
-
world.max_users = cap;
90
-
}
91
-
None => {}
92
-
}
83
+
match world_data["vrchat_visits"].as_u64() {
84
+
Some(visits) => world.visits = visits,
85
+
None => {}
86
+
}
93
87
94
-
println!("Fetched world data for {}", &world_id);
95
-
world
88
+
match world_data["capacity"].as_u64() {
89
+
Some(cap) => {
90
+
world.max_users = cap;
91
+
}
92
+
None => {}
96
93
}
94
+
95
+
println!("Fetched world data for {}", &world_id);
96
+
world
97
+
}
97
98
}
98
99
99
100
impl Serialize for World {
100
-
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
101
-
where
102
-
S: Serializer,
103
-
{
104
-
let mut s = serializer.serialize_struct("World", 7)?;
105
-
s.serialize_field("id", &self.id)?;
106
-
s.serialize_field("name", &self.name)?;
107
-
s.serialize_field("author", &self.author)?;
108
-
s.serialize_field("authorId", &self.author_id)?;
109
-
s.serialize_field("desc", &self.desc)?;
110
-
s.serialize_field("img", &self.img)?;
111
-
s.serialize_field("maxUsers", &self.max_users)?;
112
-
s.serialize_field("visits", &self.visits)?;
113
-
s.serialize_field("favourites", &self.favourites)?;
114
-
s.serialize_field("tags", &self.tags)?;
115
-
s.serialize_field("from", &self.from)?;
116
-
s.serialize_field("fromSite", &self.from_site)?;
117
-
s.serialize_field("found", &self.found)?;
101
+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
102
+
where
103
+
S: Serializer,
104
+
{
105
+
let mut s = serializer.serialize_struct("World", 7)?;
106
+
s.serialize_field("id", &self.id)?;
107
+
s.serialize_field("name", &self.name)?;
108
+
s.serialize_field("author", &self.author)?;
109
+
s.serialize_field("authorId", &self.author_id)?;
110
+
s.serialize_field("desc", &self.desc)?;
111
+
s.serialize_field("img", &self.img)?;
112
+
s.serialize_field("maxUsers", &self.max_users)?;
113
+
s.serialize_field("visits", &self.visits)?;
114
+
s.serialize_field("favourites", &self.favourites)?;
115
+
s.serialize_field("tags", &self.tags)?;
116
+
s.serialize_field("from", &self.from)?;
117
+
s.serialize_field("fromSite", &self.from_site)?;
118
+
s.serialize_field("found", &self.found)?;
118
119
119
-
s.end()
120
-
}
120
+
s.end()
121
+
}
121
122
}
+1
-5
src-tauri/tauri.conf.json
+1
-5
src-tauri/tauri.conf.json
···
23
23
"security": {
24
24
"csp": "https://photos.phazed.xyz; connect-src ipc: http://ipc.localhost"
25
25
},
26
-
"trayIcon": {
27
-
"iconPath": "./icons/icon.ico",
28
-
"title": "VRChat Photo Manager"
29
-
},
30
26
"windows": [
31
27
{
32
28
"fullscreen": false,
33
29
"resizable": true,
34
30
"title": "VRChat Photo Manager",
35
-
"width": 1200,
31
+
"width": 1220,
36
32
"height": 580,
37
33
"minWidth": 600,
38
34
"minHeight": 400,
+27
-60
src/Components/PhotoList.tsx
+27
-60
src/Components/PhotoList.tsx
···
5
5
import anime from "animejs";
6
6
7
7
const PHOTO_HEIGHT = 200;
8
-
const MAX_IMAGE_LOAD = 3;
8
+
const MAX_IMAGE_LOAD = 10;
9
9
10
10
let months = [ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" ];
11
11
···
31
31
let imagesLoading = 0;
32
32
33
33
let photoTreeLoadingContainer: HTMLElement;
34
-
let photoMetaDataLoadingContainer: HTMLElement;
35
-
let photoMetaDataLoadingBar: HTMLElement;
36
34
37
35
let scrollToTop: HTMLElement;
38
36
let scrollToTopActive = false;
···
51
49
52
50
let quitRender: boolean = false;
53
51
let photoPath: string;
54
-
55
-
let finishedFirstLoad = false;
56
52
57
53
createEffect(() => {
58
54
if(props.requestPhotoReload()){
···
93
89
constructor( path: string ){
94
90
this.path = path;
95
91
this.dateString = this.path.split('_')[1];
96
-
97
-
invoke('load_photo_meta', { photo: this.path });
98
92
}
99
93
100
94
loadImage(){
101
-
if(this.loading || this.loaded || !this.metaLoaded || imagesLoading >= MAX_IMAGE_LOAD)return;
95
+
if(this.loading || this.loaded || imagesLoading >= MAX_IMAGE_LOAD)return;
96
+
97
+
invoke('load_photo_meta', { photo: this.path });
98
+
if(!this.metaLoaded)return;
99
+
102
100
this.loading = true;
103
101
104
102
imagesLoading++;
···
108
106
this.imageEl = document.createElement('img');
109
107
this.imageEl.crossOrigin = 'anonymous';
110
108
111
-
this.imageEl.src = "http://photo.localhost/" + photoPath + this.path;
109
+
this.imageEl.src = "http://photo.localhost/" + photoPath + this.path + "?downscale";
112
110
113
111
this.imageEl.onload = () => {
114
112
this.image!.width = this.scaledWidth!;
···
222
220
currentRowIndex += 1.4;
223
221
}
224
222
225
-
if(currentRowWidth + p.scaledWidth! + 10 < photoContainer.width - 20){
223
+
if(currentRowWidth + p.scaledWidth! + 10 < photoContainer.width - 100){
226
224
currentRowWidth += p.scaledWidth! + 10;
227
225
currentRow.push(p);
228
226
} else{
···
324
322
photo.metadata = data.metadata.split('\u0000').filter(x => x !== '')[1];
325
323
amountLoaded++;
326
324
327
-
photoMetaDataLoadingBar.style.width = (amountLoaded / photos.length) * 100 + '%';
328
325
photo.metaLoaded = true;
329
-
330
-
if(amountLoaded / photos.length === 1 && !finishedFirstLoad){
331
-
finishedFirstLoad = true;
332
-
render();
333
-
334
-
anime({
335
-
targets: photoMetaDataLoadingContainer,
336
-
height: 0,
337
-
easing: 'easeInOutQuad',
338
-
duration: 500,
339
-
opacity: 0,
340
-
complete: () => {
341
-
photoMetaDataLoadingContainer.style.display = 'none';
342
-
}
343
-
})
344
-
345
-
anime({
346
-
targets: '.reload-photos',
347
-
opacity: 1,
348
-
duration: 150,
349
-
easing: 'easeInOutQuad'
350
-
})
351
-
}
352
326
})
353
327
354
328
listen('photo_create', ( event: any ) => {
···
377
351
photoTreeLoadingContainer.style.height = '100%';
378
352
photoTreeLoadingContainer.style.display = 'flex';
379
353
380
-
photoMetaDataLoadingContainer.style.opacity = '1';
381
-
photoMetaDataLoadingContainer.style.height = '100%';
382
-
photoMetaDataLoadingContainer.style.display = 'flex';
383
-
384
-
photoMetaDataLoadingBar.style.width = '0%';
385
-
386
354
quitRender = true;
387
-
finishedFirstLoad = false;
388
355
amountLoaded = 0;
389
356
scroll = 0;
390
357
photos = [];
···
415
382
photos.push(photo);
416
383
})
417
384
418
-
if(photoPaths.length == 0){
419
-
anime.set(photoMetaDataLoadingContainer, { height: 0, opacity: 0, display: 'none' });
420
-
render();
421
-
422
-
anime({
423
-
targets: '.reload-photos',
424
-
opacity: 1,
425
-
duration: 150,
426
-
easing: 'easeInOutQuad'
427
-
})
428
-
}
429
-
430
385
anime({
431
386
targets: photoTreeLoadingContainer,
432
387
height: 0,
···
437
392
photoTreeLoadingContainer.style.display = 'none';
438
393
}
439
394
})
395
+
396
+
anime({
397
+
targets: '.reload-photos',
398
+
opacity: 1,
399
+
duration: 150,
400
+
easing: 'easeInOutQuad'
401
+
})
402
+
403
+
render();
440
404
})
441
405
}
442
406
···
488
452
return (
489
453
<div class="photo-list">
490
454
<div class="photo-tree-loading" ref={( el ) => photoTreeLoadingContainer = el}>Scanning Photo Tree...</div>
491
-
<div class="photo-tree-loading" ref={( el ) => photoMetaDataLoadingContainer = el}>
492
-
<div>
493
-
Loading MetaData...
494
-
<div class="loading-bar"><div class="loading-bar-inner" ref={( el ) => photoMetaDataLoadingBar = el}></div></div>
495
-
</div>
496
-
</div>
497
455
498
456
<div class="scroll-to-top" ref={( el ) => scrollToTop = el} onClick={() => targetScroll = 0}>
499
457
<div class="icon">
500
458
<img draggable="false" src="/icon/angle-up-solid.svg"></img>
501
459
</div>
502
460
</div>
503
-
<div class="reload-photos" onClick={() => props.setConfirmationBox("Are you sure you want to reload all photos? This can cause the application to slow down while it is loading...", reloadPhotos)}>
461
+
<div class="reload-photos" onClick={() => props.setConfirmationBox("Are you sure you want to reload all photos? This can cause the application to slow down while it is loading...", () => window.location.reload())}>
504
462
<div class="icon" style={{ width: '17px' }}>
505
463
<img draggable="false" src="/icon/arrows-rotate-solid.svg"></img>
464
+
</div>
465
+
</div>
466
+
467
+
<div class="filter-options">
468
+
<div class="icon" style={{ width: '20px', height: '5px', padding: '20px' }}>
469
+
<img draggable="false" src="/icon/sliders-solid.svg"></img>
470
+
</div>
471
+
<div class="icon" style={{ width: '20px', height: '5px', padding: '20px' }}>
472
+
<img draggable="false" src="/icon/clock-regular.svg"></img>
506
473
</div>
507
474
</div>
508
475
+129
-10
src/Components/PhotoViewer.tsx
+129
-10
src/Components/PhotoViewer.tsx
···
33
33
34
34
let worldCache: WorldCache[] = JSON.parse(localStorage.getItem('worldCache') || "[]");
35
35
36
-
// TODO: Context Menu, (Open file in explorer, Copy Image)
37
36
let PhotoViewer = ( props: PhotoViewerProps ) => {
38
37
let viewer: HTMLElement;
39
-
let imageViewer: HTMLElement;
38
+
let imageViewer: HTMLImageElement;
40
39
let isOpen = false;
41
40
let trayOpen = false;
42
41
···
49
48
let worldInfoContainer: HTMLElement;
50
49
let photoPath: string;
51
50
51
+
let viewerContextMenu: HTMLElement;
52
+
let viewerContextMenuButtons: HTMLElement[] = [];
53
+
52
54
let openTray = () => {
53
55
if(trayOpen)return;
54
56
trayOpen = true;
55
57
58
+
window.CloseAllPopups.forEach(p => p());
56
59
anime({ targets: photoTray, bottom: '0px', duration: 500 });
57
60
58
61
anime({
···
79
82
let closeTray = () => {
80
83
if(!trayOpen)return;
81
84
85
+
window.CloseAllPopups.forEach(p => p());
82
86
anime({ targets: photoTray, bottom: '-150px', duration: 500 });
83
87
84
88
anime({
···
107
111
anime.set(photoControls, { translateX: '-50%' });
108
112
anime.set(photoTrayCloseBtn, { translateX: '-50%', opacity: 0, scale: '0.75', bottom: '10px' });
109
113
114
+
let contextMenuOpen = false;
115
+
window.CloseAllPopups.push(() => {
116
+
contextMenuOpen = false;
117
+
anime.set(viewerContextMenu, { opacity: 1, rotate: '0deg' });
118
+
119
+
anime({
120
+
targets: viewerContextMenu,
121
+
opacity: 0,
122
+
easing: 'easeInOutQuad',
123
+
rotate: '30deg',
124
+
duration: 100,
125
+
complete: () => {
126
+
viewerContextMenu.style.display = 'none';
127
+
}
128
+
})
129
+
});
130
+
131
+
viewerContextMenuButtons[0].onclick = async () => {
132
+
window.CloseAllPopups.forEach(p => p());
133
+
// Context Menu -> Open file location
134
+
135
+
let path = await invoke('get_user_photos_path') + '\\' + props.currentPhotoView().path;
136
+
invoke('open_folder', { url: path });
137
+
}
138
+
139
+
viewerContextMenuButtons[1].onclick = () => {
140
+
window.CloseAllPopups.forEach(p => p());
141
+
// Context Menu -> Copy image
142
+
143
+
let canvas = document.createElement('canvas');
144
+
let ctx = canvas.getContext('2d')!;
145
+
146
+
canvas.width = props.currentPhotoView().width;
147
+
canvas.height = props.currentPhotoView().height;
148
+
149
+
ctx.drawImage(imageViewer, 0, 0);
150
+
151
+
canvas.toBlob(( blob ) => {
152
+
navigator.clipboard.write([
153
+
new ClipboardItem({
154
+
'image/png': blob!
155
+
})
156
+
]);
157
+
158
+
canvas.remove();
159
+
160
+
anime.set('.copy-notif', { translateX: '-50%', translateY: '-100px' });
161
+
anime({
162
+
targets: '.copy-notif',
163
+
opacity: 1,
164
+
translateY: '0px'
165
+
});
166
+
167
+
setTimeout(() => {
168
+
anime({
169
+
targets: '.copy-notif',
170
+
opacity: 0,
171
+
translateY: '-100px'
172
+
});
173
+
}, 2000);
174
+
});
175
+
}
176
+
177
+
imageViewer.oncontextmenu = ( e ) => {
178
+
if(contextMenuOpen){
179
+
contextMenuOpen = false;
180
+
181
+
anime.set(viewerContextMenu, { opacity: 1, rotate: '0deg' });
182
+
183
+
anime({
184
+
targets: viewerContextMenu,
185
+
opacity: 0,
186
+
rotate: '30deg',
187
+
easing: 'easeInOutQuad',
188
+
duration: 100,
189
+
complete: () => {
190
+
viewerContextMenu.style.display = 'none';
191
+
}
192
+
})
193
+
} else{
194
+
contextMenuOpen = true;
195
+
196
+
viewerContextMenu.style.top = e.clientY + 'px';
197
+
viewerContextMenu.style.left = e.clientX + 'px';
198
+
viewerContextMenu.style.display = 'block';
199
+
200
+
anime.set(viewerContextMenu, { opacity: 0, rotate: '-30deg' });
201
+
202
+
anime({
203
+
targets: viewerContextMenu,
204
+
opacity: 1,
205
+
rotate: '0deg',
206
+
easing: 'easeInOutQuad',
207
+
duration: 100
208
+
})
209
+
}
210
+
}
211
+
110
212
createEffect(() => {
111
213
let photo = props.currentPhotoView();
112
214
···
117
219
if(!photoPath)
118
220
photoPath = await invoke('get_user_photos_path') + '/';
119
221
120
-
imageViewer.style.background = 'url(\'http://photo.localhost/' + (photoPath + props.currentPhotoView().path).split('\\').join('/') +'\')';
222
+
imageViewer.src = 'http://photo.localhost/' + (photoPath + props.currentPhotoView().path).split('\\').join('/') + "?full";
223
+
imageViewer.crossOrigin = 'anonymous';
121
224
})();
122
225
123
226
anime({
···
157
260
</div> as Node
158
261
);
159
262
160
-
if(!worldData)
263
+
264
+
if(!worldData){
265
+
console.log('Fetching new world data');
266
+
161
267
invoke('find_world_by_id', { worldId: meta.world.id });
162
-
else if(worldData.expiresOn < Date.now()){
268
+
} else if(worldData.expiresOn < Date.now()){
269
+
console.log('Fetching new world data since cache has expired');
270
+
163
271
worldCache = worldCache.filter(x => x !== worldData)
164
272
invoke('find_world_by_id', { worldId: meta.world.id });
165
273
} else
···
209
317
targets: '.navbar',
210
318
top: '0px'
211
319
})
212
-
320
+
213
321
window.CloseAllPopups.forEach(p => p());
214
322
215
323
anime({ targets: '.prev-button', top: '75%', easing: 'easeInOutQuad', duration: 100 });
···
278
386
279
387
return (
280
388
<div class="photo-viewer" ref={( el ) => viewer = el}>
389
+
<div class="photo-context-menu" ref={( el ) => viewerContextMenu = el}>
390
+
<div ref={( el ) => viewerContextMenuButtons.push(el)}>Open file location</div>
391
+
<div ref={( el ) => viewerContextMenuButtons.push(el)}>Copy image</div>
392
+
</div>
393
+
281
394
<div class="viewer-close viewer-button" onClick={() => props.setCurrentPhotoView(null)}>
282
395
<div class="icon" style={{ width: '10px', margin: '0' }}>
283
396
<img draggable="false" src="/icon/x-solid.svg"></img>
284
397
</div>
285
398
</div>
286
-
<div class="image-container" ref={( el ) => imageViewer = el}></div>
399
+
<img class="image-container" ref={( el ) => imageViewer = el} />
287
400
288
-
<div class="prev-button" onClick={() => props.setPhotoNavChoice('prev')}>
401
+
<div class="prev-button" onClick={() => {
402
+
window.CloseAllPopups.forEach(p => p());
403
+
props.setPhotoNavChoice('prev');
404
+
}}>
289
405
<div class="icon" style={{ width: '15px', margin: '0' }}>
290
406
<img draggable="false" src="/icon/arrow-left-solid.svg"></img>
291
407
</div>
292
408
</div>
293
409
294
-
<div class="next-button" onClick={() => props.setPhotoNavChoice('next')}>
410
+
<div class="next-button" onClick={() => {
411
+
window.CloseAllPopups.forEach(p => p());
412
+
props.setPhotoNavChoice('next');
413
+
}}>
295
414
<div class="icon" style={{ width: '15px', margin: '0' }}>
296
415
<img draggable="false" src="/icon/arrow-right-solid.svg"></img>
297
416
</div>
···
319
438
canvas.width = props.currentPhotoView().width;
320
439
canvas.height = props.currentPhotoView().height;
321
440
322
-
ctx.drawImage(props.currentPhotoView().imageEl, 0, 0);
441
+
ctx.drawImage(imageViewer, 0, 0);
323
442
324
443
canvas.toBlob(( blob ) => {
325
444
navigator.clipboard.write([
+1
-1
src/Components/SettingsMenu.tsx
+1
-1
src/Components/SettingsMenu.tsx
···
179
179
return;
180
180
}
181
181
182
-
console.log(data.data);
182
+
console.log(data);
183
183
props.setLoggedIn({ loggedIn: true, username: data.user.username, avatar: data.user.avatar, id: data.user._id, serverVersion: data.user.serverVersion });
184
184
props.setStorageInfo({ storage: data.user.storage, used: data.user.used, sync: data.user.settings.enableSync });
185
185
})
+2
src/index.tsx
+2
src/index.tsx
+34
src/styles.css
+34
src/styles.css
···
164
164
overflow: hidden;
165
165
}
166
166
167
+
.filter-options{
168
+
position: fixed;
169
+
top: 55px;
170
+
left: 5px;
171
+
width: 40px;
172
+
height: 50px;
173
+
}
174
+
167
175
.photo-tree-loading{
168
176
width: 100%;
169
177
height: 100%;
···
220
228
backdrop-filter: blur(75px);
221
229
opacity: 0;
222
230
display: none;
231
+
}
232
+
233
+
.photo-context-menu{
234
+
position: fixed;
235
+
top: 0;
236
+
left: 0;
237
+
padding: 10px;
238
+
border-radius: 5px;
239
+
backdrop-filter: blur(5px);
240
+
background: #555a;
241
+
color: #aaa;
242
+
box-shadow: #0005 0 0 10px;
243
+
opacity: 0;
244
+
}
245
+
246
+
.photo-context-menu > div{
247
+
padding: 2px 10px;
248
+
width: calc(100% - 10px);
249
+
text-align: center;
250
+
transition: 0.1s;
251
+
}
252
+
253
+
.photo-context-menu > div:hover{
254
+
color: #fff;
255
+
cursor: pointer;
256
+
user-select: none;
223
257
}
224
258
225
259
.image-container{