Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bc70b04110 | ||
|
|
11a0ef319f | ||
|
|
bd32ce5543 | ||
|
|
c47f755155 | ||
|
|
d6cbbf3c2f | ||
|
|
8c7e2d341d | ||
|
|
039d62cbbb | ||
|
|
b6773a732b | ||
|
|
60267e674e | ||
|
|
182bfae5ad | ||
|
|
149c2a2725 | ||
|
|
69a7ae5201 | ||
|
|
462daee934 | ||
|
|
63059ea39d | ||
|
|
5a8f1db5a6 | ||
|
|
a8dc55b4f3 | ||
|
|
67a253c9c7 | ||
|
|
dcf6628aa3 | ||
|
|
dc462b3847 | ||
|
|
81bc31d1b1 | ||
|
|
500d5efc33 | ||
|
|
39254b7f17 | ||
|
|
9c40a501fe | ||
|
|
c1e6f27020 | ||
|
|
d7f14db6ab | ||
|
|
fc10ee2d6b | ||
|
|
af9b4b3922 | ||
|
|
f00a78746e | ||
|
|
eb35fb25af | ||
|
|
276df8202a | ||
|
|
0ee27097ce | ||
|
|
f1ac38dd59 | ||
|
|
66ed712952 | ||
|
|
3dddd907b0 | ||
|
|
92de27eae8 | ||
|
|
1ee1f9c775 | ||
|
|
5d20e16dc5 | ||
|
|
400220a312 | ||
|
|
6846bbf103 | ||
|
|
ef3837f246 | ||
|
|
c3a41ba9ad | ||
|
|
18c209848b | ||
|
|
e076c2be90 | ||
|
|
c80fa5da78 | ||
|
|
aaadab434c | ||
|
|
0e137f12f5 | ||
|
|
6aba344583 | ||
|
|
a0a16cc493 | ||
|
|
a1e0282df0 | ||
|
|
bb370b28b7 | ||
|
|
dd1dcba599 | ||
|
|
5b4a93ee34 | ||
|
|
f426745094 | ||
|
|
840095d73c | ||
|
|
bbd64fae81 | ||
|
|
eff1da2812 | ||
|
|
65f677b277 | ||
|
|
b6ad0ba597 | ||
|
|
9b56cbb648 | ||
|
|
e20ce9052a | ||
|
|
853be71bf5 | ||
|
|
9a908abfdd | ||
|
|
e482ac75f9 | ||
|
|
205b5f2c21 | ||
|
|
064ed16a7a | ||
|
|
8a53abef3f | ||
|
|
42c2c4738a | ||
|
|
cdf6f1b38a | ||
|
|
17536fdecb | ||
|
|
eb7356474c | ||
|
|
76d1267aa5 | ||
|
|
888c1b86f0 | ||
|
|
882d5535e6 | ||
|
|
4622a412b7 | ||
|
|
fc35f74751 | ||
|
|
f6e1f2185e | ||
|
|
2e7f803e02 | ||
|
|
9eaa79b558 | ||
|
|
c85ab276e9 | ||
|
|
7f6ad504fd | ||
|
|
130c5f8e6e | ||
|
|
d39b31f357 | ||
|
|
f36cc9a5d0 | ||
|
|
6425997508 | ||
|
|
bf06211bff | ||
|
|
506f789318 | ||
|
|
ebdd28cf1c | ||
|
|
de4c1b24ef | ||
|
|
ab664ebb2f | ||
|
|
20f527f4eb | ||
|
|
b3b24d0cc0 | ||
|
|
b15ed56008 | ||
|
|
992be378bb | ||
|
|
5156248918 | ||
|
|
adaad9367d | ||
|
|
8dc28e9286 | ||
|
|
49b2bcd20d | ||
|
|
e729c0a81f | ||
|
|
ff7aab73cc | ||
|
|
fcd214b1c0 | ||
|
|
a50f2a6eb9 | ||
|
|
2c71e4f885 | ||
|
|
f5dc9ed975 | ||
|
|
608a08eb76 | ||
|
|
cd8e4ddf00 | ||
|
|
9c8d00bb61 | ||
|
|
9015e6f160 | ||
|
|
1262653144 | ||
|
|
e9ad705ea3 | ||
|
|
b0d2cdf55a | ||
|
|
c7d8a0b7c3 | ||
|
|
286c89bf74 | ||
|
|
339e3d242f | ||
|
|
fad02eb09d | ||
|
|
8f1752a253 | ||
|
|
16a8dbc6ce | ||
|
|
1a53a0e375 | ||
|
|
52b713081d | ||
|
|
3fbcd928db | ||
|
|
fa4e3d0d2a | ||
|
|
319c358d1b | ||
|
|
bc5548ff75 | ||
|
|
46a20123c1 | ||
|
|
380d921dae | ||
|
|
6130dfe36a | ||
|
|
64b4a99aa1 | ||
|
|
9344ea23ca | ||
|
|
baeedf557d | ||
|
|
b7f369b3d9 | ||
|
|
3e76fa8e42 | ||
|
|
783fd40f63 | ||
|
|
10541502b8 | ||
|
|
95fdfd7da2 | ||
|
|
4a48a2af5a | ||
|
|
d82a6c5924 | ||
|
|
4d89b7a24e | ||
|
|
e5bca9e9cc | ||
|
|
2cbd74c716 | ||
|
|
43e4b1efad | ||
|
|
a8f791e98c | ||
|
|
b6dc18ba32 | ||
|
|
9c24597bbd | ||
|
|
ea46272b49 | ||
|
|
ac36d76eed | ||
|
|
90b9456b0b | ||
|
|
309bbbd8e7 | ||
|
|
862ba3173a | ||
|
|
76cdb689fb | ||
|
|
3bbf91ab2b | ||
|
|
c8044a92d1 | ||
|
|
799ceb432f | ||
|
|
c7c9048fe3 | ||
|
|
7e2a0144a1 | ||
|
|
3f97820094 | ||
|
|
e7adc3bcbf | ||
|
|
381913307c | ||
|
|
799ed2fdb9 | ||
|
|
a68903918c | ||
|
|
170bbb6db0 | ||
|
|
a702c06dc8 | ||
|
|
fac76ac5b8 | ||
|
|
2eea57aefa | ||
|
|
23a65bfa2a | ||
|
|
23ffc3d0a8 | ||
|
|
ff5a8c322d | ||
|
|
e320439049 | ||
|
|
8656cfeb1e | ||
|
|
6a853036a5 | ||
|
|
c5cea81be2 | ||
|
|
dde50d32d6 | ||
|
|
cdee706f46 | ||
|
|
586c20a14e | ||
|
|
1e6c4a4d88 | ||
|
|
18185cc90c | ||
|
|
67e4a759c7 | ||
|
|
68da3955c1 | ||
|
|
3fb997af84 | ||
|
|
6fb677d346 | ||
|
|
50814b2bee | ||
|
|
5a6103f5d6 | ||
|
|
da3d575295 | ||
|
|
10229ca8b5 | ||
|
|
58ad9a260c | ||
|
|
5d2ff8e07c | ||
|
|
cc6bf3cc1e | ||
|
|
0d8be87031 | ||
|
|
c65d00e95c | ||
|
|
96b1c6a540 | ||
|
|
aa58c79dbb | ||
|
|
87247ccd9d | ||
|
|
aa8addf4a3 | ||
|
|
7a92c5b538 | ||
|
|
1af6c5eadc | ||
|
|
54826da701 | ||
|
|
299e771877 | ||
|
|
ad9a4b7d71 | ||
|
|
8dff9dc67c | ||
|
|
80f034beee | ||
|
|
bf1d4b923a | ||
|
|
9064b9f7ad | ||
|
|
7dbd9fe8cd | ||
|
|
f1a5726fcb | ||
|
|
2d66dfabe8 | ||
|
|
585e71aa9d | ||
|
|
833a6cd95f | ||
|
|
31ed88364a | ||
|
|
3ee703cd6d | ||
|
|
0e278ddf3f | ||
|
|
c793eb67fc | ||
|
|
d90414a8c1 | ||
|
|
056441bb39 | ||
|
|
1eaeb2c4c1 | ||
|
|
793d3808ec | ||
|
|
9e40b7f992 | ||
|
|
cb769a7213 | ||
|
|
255a4e6849 | ||
|
|
30700cd513 | ||
|
|
bd88d89814 | ||
|
|
825725df7d | ||
|
|
78728bb6ff | ||
|
|
aead4dd068 | ||
|
|
7edc4fd76e | ||
|
|
df91e00985 | ||
|
|
6a8c15899c | ||
|
|
cbd84dfaad | ||
|
|
230eb5fd55 | ||
|
|
eb970a6b7a | ||
|
|
63111927cc | ||
|
|
4d1e1a1c28 | ||
|
|
8a94bbd602 | ||
|
|
9c7e64c502 | ||
|
|
f53c2567fc | ||
|
|
c7bdd4bcc2 | ||
|
|
cf4a2920f8 | ||
|
|
f3eeb471e7 | ||
|
|
48bb80b1d0 | ||
|
|
71b1769eee | ||
|
|
8895430d9e | ||
|
|
f363d3b0f6 | ||
|
|
3a7660108c | ||
|
|
a0d0a13030 | ||
|
|
c170b133e6 | ||
|
|
3511737bb3 | ||
|
|
d2933bdb04 | ||
|
|
00f7256e70 | ||
|
|
da20d5c221 | ||
|
|
a516ec82bc | ||
|
|
1adf6530fe | ||
|
|
0bc0a72d8a | ||
|
|
295b887cf1 | ||
|
|
d80a67bdda | ||
|
|
f469253916 | ||
|
|
06173195c5 | ||
|
|
68344ef718 | ||
|
|
6b528733fa | ||
|
|
a20e92f08a | ||
|
|
820d2548b6 | ||
|
|
b8895ce787 | ||
|
|
396d310d22 | ||
|
|
b5e9bf6ee3 | ||
|
|
1f94d9d496 | ||
|
|
19bf8e3b7a | ||
|
|
a2d27657dc | ||
|
|
4e122a9452 | ||
|
|
319875529f | ||
|
|
805bdc9d32 | ||
|
|
b10ff5e68e | ||
|
|
5e7507c8e7 | ||
|
|
4549ed20e7 | ||
|
|
d02c29426e | ||
|
|
b0257a626d | ||
|
|
e095fd4181 | ||
|
|
b9788bce09 | ||
|
|
7f92f8988f | ||
|
|
9758fe51f8 | ||
|
|
78b7027343 | ||
|
|
6c39b5b321 | ||
|
|
123e6968af | ||
|
|
a015c28e5a | ||
|
|
cbb6b2d9b0 | ||
|
|
3d014ef274 | ||
|
|
11eca2b3d9 | ||
|
|
47942ffadf | ||
|
|
62c9108a68 | ||
|
|
3ebe783684 | ||
|
|
25347a3a90 | ||
|
|
2dd9b9767e | ||
|
|
89d036158b | ||
|
|
ca8be6ab10 | ||
|
|
8d6e1cdaa1 | ||
|
|
1841fb293e | ||
|
|
e9c0956c51 | ||
|
|
06f1d52cc0 | ||
|
|
2e45624973 | ||
|
|
b784e9b842 | ||
|
|
228a41d7ea | ||
|
|
2b8e4d3e61 | ||
|
|
74b43511d1 | ||
|
|
238af3ed0e | ||
|
|
fa9e32d86a | ||
|
|
2537a04fef | ||
|
|
b9f6b62053 | ||
|
|
0d2ceba817 | ||
|
|
dff5e84ca5 | ||
|
|
2d61581eb3 | ||
|
|
4abd74ed4b | ||
|
|
591d8a3872 | ||
|
|
fa3f9c21fc | ||
|
|
323b56982b | ||
|
|
c0879e8445 | ||
|
|
e27e8e43e1 | ||
|
|
e6d4f9e3f7 | ||
|
|
73e81da356 | ||
|
|
8b87b77c15 | ||
|
|
d146bb6481 | ||
|
|
5860d35d1a | ||
|
|
a4a88959fd | ||
|
|
5a5f5eceb5 | ||
|
|
73a97fdbea | ||
|
|
d2121ed2b9 | ||
|
|
90ac7970bd | ||
|
|
bfe61ea09d | ||
|
|
454f33e0fb | ||
|
|
eaf396630c | ||
|
|
8a2c57014c | ||
|
|
c9fc9e7497 | ||
|
|
e67f91b380 | ||
|
|
09bf373e92 | ||
|
|
d7d6729241 | ||
|
|
562da32586 | ||
|
|
8ee5c6ed25 | ||
|
|
2501c15a8f | ||
|
|
fe524736ec | ||
|
|
b4aceec346 | ||
|
|
4acce447f2 | ||
|
|
c0aa90b0b3 | ||
|
|
50ebaa3ab8 | ||
|
|
7914949613 | ||
|
|
dd15a49d76 | ||
|
|
bf9f6ac13f | ||
|
|
6ea3ea189e | ||
|
|
176668049c | ||
|
|
069a23c96b | ||
|
|
c403e083cc | ||
|
|
d6cae56d74 | ||
|
|
b52dfe12c2 | ||
|
|
be5123ca21 | ||
|
|
3e620ed335 | ||
|
|
6f4ab9a55d | ||
|
|
2331b328d8 | ||
|
|
cff24581a4 | ||
|
|
285231a845 | ||
|
|
1eaf97b669 | ||
|
|
35fbf99fb1 | ||
|
|
7b8aa78e95 | ||
|
|
2d1793c5ff | ||
|
|
04a7c8dc6d | ||
|
|
3dc64f3d50 | ||
|
|
b8cbbf7454 | ||
|
|
5e7a54183a | ||
|
|
c0df0badec | ||
|
|
d115a8a79e | ||
|
|
3156568594 | ||
|
|
570eb40c7a | ||
|
|
9206e2cdff | ||
|
|
b20aaecc01 | ||
|
|
c11a00a380 | ||
|
|
f355ff01e4 | ||
|
|
cb3bd8efb5 | ||
|
|
4894361076 | ||
|
|
62d0a31a76 | ||
|
|
adda0218fa | ||
|
|
5763e289ef | ||
|
|
3df31c43f8 | ||
|
|
8d39dac145 | ||
|
|
6c3978c3ba | ||
|
|
0a49a18280 | ||
|
|
6ec93d49c2 | ||
|
|
99cb2430f7 | ||
|
|
5a4af5a983 | ||
|
|
96ad14e891 | ||
|
|
01230704ea | ||
|
|
ab0131c19c | ||
|
|
908d005737 | ||
|
|
6230d0ad9f | ||
|
|
a8cb58cb4a | ||
|
|
6a902a2b21 | ||
|
|
82182d7872 | ||
|
|
155cf3dca5 | ||
|
|
9df11256cc | ||
|
|
b578067092 | ||
|
|
e44b0a4bb8 | ||
|
|
af877913d7 | ||
|
|
86a6f5827d | ||
|
|
84aa156885 | ||
|
|
f66b3a447c | ||
|
|
d1f80b15a0 | ||
|
|
31ba12d289 | ||
|
|
9b78f35ffe | ||
|
|
602f7e7b6c | ||
|
|
9a6dda417a | ||
|
|
c0964fad5d | ||
|
|
5421c156c3 | ||
|
|
e3a1384f7f | ||
|
|
ff9cf905fa | ||
|
|
2d32e02dba | ||
|
|
ec97735e94 | ||
|
|
49fb0470ab | ||
|
|
81ecbd8ae2 | ||
|
|
41084da708 | ||
|
|
02fd2bc427 | ||
|
|
7037378947 | ||
|
|
ee463037e3 | ||
|
|
9604bc06bb | ||
|
|
39bbfe109d | ||
|
|
882043ef96 | ||
|
|
510fd11680 | ||
|
|
b2d2e071bd | ||
|
|
bc5f0c206b | ||
|
|
d1d5b37c14 | ||
|
|
e8f5b1ade3 | ||
|
|
62c2da06d4 | ||
|
|
a4b08e4f46 | ||
|
|
42995b6baf | ||
|
|
ae3a68efb6 | ||
|
|
edd9128046 | ||
|
|
cd183b569a | ||
|
|
d6d8b4da5a | ||
|
|
a1b54e0646 | ||
|
|
8cba015af1 | ||
|
|
dfb7ed1053 | ||
|
|
6fa79b65f6 | ||
|
|
e66b3b07db | ||
|
|
fa0a485c85 | ||
|
|
55be554cc7 | ||
|
|
69ae3cba04 | ||
|
|
6bda1fd2ea | ||
|
|
69423b00d4 | ||
|
|
91469df126 | ||
|
|
d648f2b5b9 | ||
|
|
ab353551d1 | ||
|
|
c5af7db2cd | ||
|
|
f317bae216 | ||
|
|
f990e3cabb | ||
|
|
77d1dbedd6 | ||
|
|
9730069ec1 | ||
|
|
0218f4e8b9 | ||
|
|
ee7cb0cb42 | ||
|
|
a352adf0d7 | ||
|
|
7de6a21331 | ||
|
|
ef5c1474cb | ||
|
|
8da2d24d4e | ||
|
|
af98febcb0 | ||
|
|
082b0d2f5d | ||
|
|
44ad0a2623 | ||
|
|
6a1b84a098 | ||
|
|
0b8909a150 | ||
|
|
5782dec45c | ||
|
|
e5f5169a39 | ||
|
|
fc1535696b | ||
|
|
5464f1851b | ||
|
|
c790b698f2 | ||
|
|
206b06f023 | ||
|
|
f84e0548a8 | ||
|
|
8206e58800 | ||
|
|
37f85cebde | ||
|
|
ca50e61762 | ||
|
|
bb80a34123 | ||
|
|
3cd9ea5366 | ||
|
|
a5e0e97df5 | ||
|
|
4e0b9c87e2 | ||
|
|
811dc46bc6 | ||
|
|
22d7ade18b | ||
|
|
ccc1d91896 | ||
|
|
2258e3468d | ||
|
|
437228d4cf | ||
|
|
03e3c2efe4 | ||
|
|
7e972c3ccd | ||
|
|
ece29b95f5 | ||
|
|
0ff08ff8d2 | ||
|
|
488882c96b | ||
|
|
04f8fc0a3a | ||
|
|
1967913db6 | ||
|
|
bdf4e60b48 | ||
|
|
283de97c90 | ||
|
|
459a949e76 | ||
|
|
6d11ab6078 | ||
|
|
4d2ca4f27f | ||
|
|
e178c7c4f4 | ||
|
|
bb61bf0a30 | ||
|
|
ca89fd93f3 | ||
|
|
ba3f2f3c02 | ||
|
|
5191b6af93 | ||
|
|
595405f2cc | ||
|
|
370db4f71b | ||
|
|
2544aeb290 | ||
|
|
893b6e041d | ||
|
|
1f2b30abfb | ||
|
|
56e68d1346 | ||
|
|
d35727b93c | ||
|
|
723b34a736 | ||
|
|
4a39c69ea7 | ||
|
|
6018200946 | ||
|
|
222fba21fb | ||
|
|
12fc952026 | ||
|
|
05975e55f0 | ||
|
|
c3033bd6e8 | ||
|
|
a413746c05 | ||
|
|
2e60459878 | ||
|
|
d1737d35fe | ||
|
|
3e75b1ac92 | ||
|
|
5896855dda | ||
|
|
3119759c92 | ||
|
|
d26d620db8 | ||
|
|
53cc0fff9d | ||
|
|
4d835757d2 | ||
|
|
9274444bde | ||
|
|
39b0fe69b5 | ||
|
|
bda16b136b | ||
|
|
c493ba79e0 | ||
|
|
ed691be7d8 | ||
|
|
c2cc08928d | ||
|
|
6c27b5455e | ||
|
|
3a1148d11a | ||
|
|
f1d818bec7 | ||
|
|
b86931a499 | ||
|
|
92043c49f3 | ||
|
|
6c58a27247 | ||
|
|
ac305e8821 | ||
|
|
27f4cf2d12 | ||
|
|
c1ec45e3a8 | ||
|
|
3d8fd6fe8d | ||
|
|
92a751b32a | ||
|
|
7e8ed1e824 | ||
|
|
589acd9e2b | ||
|
|
2b451fa06d | ||
|
|
a204af9783 | ||
|
|
4961236012 | ||
|
|
4c07ae26fc | ||
|
|
f792283e70 | ||
|
|
bc404c4ea5 | ||
|
|
5b482248ec | ||
|
|
5bb5337de8 | ||
|
|
a371cf3a7c | ||
|
|
5c13b324ec | ||
|
|
8137769edd | ||
|
|
b0ab4eff11 | ||
|
|
4f34068f51 | ||
|
|
520d5c2cb0 | ||
|
|
8856b9abb7 | ||
|
|
499ee29c3d | ||
|
|
5294d7700e | ||
|
|
68997a9c93 | ||
|
|
f88b8051f7 | ||
|
|
01589c6f94 | ||
|
|
733b7e1a45 | ||
|
|
90ded00a70 | ||
|
|
1de3999e3a | ||
|
|
97eab3b648 | ||
|
|
cb293e9e51 | ||
|
|
34275664b2 | ||
|
|
33b72c8f6a | ||
|
|
41b85510bf | ||
|
|
4b654de2e6 | ||
|
|
7bde75ee2f | ||
|
|
286781f5cc | ||
|
|
988e8db798 | ||
|
|
cc0d408775 | ||
|
|
84a71674f6 | ||
|
|
2d738fedaa | ||
|
|
fa2cfd7c20 | ||
|
|
ee0f3d3227 | ||
|
|
cfdd891991 | ||
|
|
1e2017cdd0 | ||
|
|
9b3701dd88 | ||
|
|
9ff0975f7c | ||
|
|
06bd6064ac | ||
|
|
c991473259 | ||
|
|
1f9686dc66 | ||
|
|
f4cf5d0808 | ||
|
|
7b06b4c427 | ||
|
|
c552e958ce | ||
|
|
944b2d97cd | ||
|
|
822263529c | ||
|
|
2039cc00b5 | ||
|
|
d6fa38a4b2 | ||
|
|
a6130a27c2 | ||
|
|
1ab0a8351e | ||
|
|
04ba711441 | ||
|
|
b7f65ff11d | ||
|
|
1d7d4092a5 | ||
|
|
d368ba78e0 | ||
|
|
2a5d9d5530 | ||
|
|
3c84e8df51 | ||
|
|
9142f5d870 | ||
|
|
3dff72942b | ||
|
|
4becc2eb75 | ||
|
|
61ea7ea3df | ||
|
|
0ef3247076 | ||
|
|
a4d1c614ac | ||
|
|
3b6c6e92d6 | ||
|
|
21d09f7741 | ||
|
|
1faf959e1e | ||
|
|
41ccd4f59f | ||
|
|
387ee911a9 | ||
|
|
328e0ac73a | ||
|
|
2d44a9a956 | ||
|
|
745018cf22 | ||
|
|
61c2b928c9 | ||
|
|
b24c4413e8 | ||
|
|
8f114e843c | ||
|
|
4402f4dec9 | ||
|
|
2775737260 | ||
|
|
0310b92723 | ||
|
|
a2918d251f | ||
|
|
002eac63f7 | ||
|
|
ac23f81e24 | ||
|
|
9974539946 | ||
|
|
e711bfce1d | ||
|
|
d386698e97 | ||
|
|
c9b6e02167 | ||
|
|
880669c0e5 | ||
|
|
89a5ee4a8a | ||
|
|
8f5134305e | ||
|
|
294b1bea13 | ||
|
|
800e02d85e | ||
|
|
e223eecf32 | ||
|
|
1736fb794d | ||
|
|
875c109b14 | ||
|
|
2810097b95 | ||
|
|
69f04d0958 | ||
|
|
565617d1f0 | ||
|
|
5bb52ba77e | ||
|
|
8371efd3e6 | ||
|
|
81a7b7f3eb | ||
|
|
2202c28534 | ||
|
|
708afda27d | ||
|
|
fc0fb1c3df | ||
|
|
ea271c7342 | ||
|
|
69da9acd8b | ||
|
|
8632b141ad | ||
|
|
c5963bc765 | ||
|
|
9a57593690 | ||
|
|
56fdc99990 | ||
|
|
c7db59c99b | ||
|
|
af813806f1 | ||
|
|
3d2bfcc7c1 | ||
|
|
e826d68b16 | ||
|
|
4f97133183 | ||
|
|
3d10d46e6a | ||
|
|
759c10faca | ||
|
|
a4e48e53a6 | ||
|
|
164cde50d3 | ||
|
|
05b9bc50f6 | ||
|
|
930ad26155 | ||
|
|
5d2160b0bb | ||
|
|
2571c6b1bf | ||
|
|
73cfe39e41 | ||
|
|
804b2b27c1 | ||
|
|
43a0a02328 | ||
|
|
f54f90b647 | ||
|
|
caa6f0446b | ||
|
|
21e90ffac0 | ||
|
|
bd40768c6a | ||
|
|
6296367efc | ||
|
|
a43259bddb | ||
|
|
0cd384802e | ||
|
|
9574f282e7 | ||
|
|
ac91b25219 | ||
|
|
9b5184cd72 | ||
|
|
2bbba547ee | ||
|
|
8229371f63 | ||
|
|
4c31c0dcf0 | ||
|
|
14c1959d63 | ||
|
|
d79a7cb194 | ||
|
|
8bab4cce61 | ||
|
|
1718d03884 | ||
|
|
27557c52a3 | ||
|
|
33e45a86c4 | ||
|
|
6ef7b6cf68 | ||
|
|
01a79c69c3 | ||
|
|
a7f02ec7df | ||
|
|
bfb60dcaec | ||
|
|
e9535201a4 | ||
|
|
406f185ab7 | ||
|
|
99a3a386a3 | ||
|
|
defef552ed | ||
|
|
072d94ebe6 | ||
|
|
fe549e8d67 | ||
|
|
2fb11519d2 | ||
|
|
5cdd1d38c4 | ||
|
|
e9b215ebbd | ||
|
|
4fb760e44c | ||
|
|
ddc394fcd7 | ||
|
|
84b5762017 | ||
|
|
58ad84b7df |
No files matched your search
@@ -36,16 +36,15 @@ test "" = "$(grep '^Signed-off-by: ' "$1" |
|
||||
.githooks/gitlint \
|
||||
--msg-file=$1 \
|
||||
--subject-regex="^(build|chore|ci|docs|feat|feature|fix|perf|refactor|revert|style|test)(.*)?:\s?.*" \
|
||||
--subject-maxlen=100 \
|
||||
--subject-maxlen=150 \
|
||||
--subject-minlen=10 \
|
||||
--body-regex=".*" \
|
||||
--body-maxlen=200 \
|
||||
--max-parents=1
|
||||
|
||||
if [ $? -ne 0 ]
|
||||
then
|
||||
printError "Please fix your commit message to match AppFlowy coding standards"
|
||||
printError "https://appflowy.gitbook.io/docs/essential-documentation/contribute-to-appflowy/software-contributions/submitting-code/style-guides"
|
||||
printError "https://appflowy.gitbook.io/docs/essential-documentation/contribute-to-appflowy/software-contributions/submitting-code/code-submission-guidelines#commit-message-guidelines"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: "[Bug] Untitled Bug Issue"
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Desktop (please complete the following information):**
|
||||
- OS: [e.g. iOS]
|
||||
- Browser [e.g. chrome, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Additional context**
|
||||
- Environment [e.g. flutter doctor -v or rustup show]
|
||||
Add any other context about the problem here.
|
||||
@@ -0,0 +1,49 @@
|
||||
name: Bug Report
|
||||
description: Create a bug report to help us improve
|
||||
title: "[Bug] "
|
||||
body:
|
||||
- type: textarea
|
||||
id: desc
|
||||
attributes:
|
||||
label: Bug Description
|
||||
description: A clear and concise description of what the bug is
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: reproduce
|
||||
attributes:
|
||||
label: How to Reproduce
|
||||
description: What steps can we take to reproduce this behavior?
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: expected
|
||||
attributes:
|
||||
label: Expected Behavior
|
||||
description: A clear and concise description of what you expected to happen
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: os
|
||||
attributes:
|
||||
label: Operating System
|
||||
description: What OS are you seeing this bug on?
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: AppFlowy Version(s)
|
||||
description: What version(s) of AppFlowy do you see this bug on?
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: screenshots
|
||||
attributes:
|
||||
label: Screenshots
|
||||
description: If applicable, please add screenshots to help explain your problem
|
||||
- type: textarea
|
||||
id: context
|
||||
attributes:
|
||||
label: Additional Context
|
||||
description: Add any additonal context about the problem here
|
||||
@@ -1,17 +0,0 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: "[FR] Untitled Feature Request"
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**1~3 main use cases of the proposed feature**
|
||||
Ex: As a ... , I want to set a reminder for a checkbox item so that I can be reminded by the system at a specific time.
|
||||
|
||||
**what types of users can benefit from using your proposed feature**
|
||||
Ex: busy students who tend to forget their paper deadlines
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
@@ -0,0 +1,25 @@
|
||||
name: Feature Request
|
||||
description: Suggest an idea for AppFlowy
|
||||
title: "[FR] "
|
||||
body:
|
||||
- type: textarea
|
||||
id: desc
|
||||
attributes:
|
||||
label: Description
|
||||
description: Describe your suggested feature and the main use cases
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: users
|
||||
attributes:
|
||||
label: Impact
|
||||
description: What types of users can benefit from using the suggested feature?
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: context
|
||||
attributes:
|
||||
label: Additional Context
|
||||
description: Add any additonal context about the feature here
|
||||
+5
-3
@@ -1,15 +1,15 @@
|
||||
name: FlowyEditor test
|
||||
name: AppFlowyEditor test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "main"
|
||||
paths:
|
||||
- "frontend/app_flowy/packages/appflowy_editor/**"
|
||||
- "release/*"
|
||||
|
||||
pull_request:
|
||||
branches:
|
||||
- "main"
|
||||
- "release/*"
|
||||
paths:
|
||||
- "frontend/app_flowy/packages/appflowy_editor/**"
|
||||
|
||||
@@ -37,6 +37,8 @@ jobs:
|
||||
working-directory: frontend/app_flowy/packages/appflowy_editor
|
||||
run: |
|
||||
flutter pub get
|
||||
flutter format --set-exit-if-changed .
|
||||
flutter analyze .
|
||||
flutter test --coverage
|
||||
|
||||
- uses: codecov/codecov-action@v3
|
||||
@@ -4,21 +4,25 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- "main"
|
||||
- "release/*"
|
||||
|
||||
pull_request:
|
||||
branches:
|
||||
- "main"
|
||||
- "release/*"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
flutter_profile: development-linux-x86_64
|
||||
- os: macos-latest
|
||||
flutter_profile: development-mac-x86_64
|
||||
- os: windows-latest
|
||||
flutter_profile: development-windows-x86
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
@@ -27,7 +31,7 @@ jobs:
|
||||
- id: rust_toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: "stable-2022-01-20"
|
||||
toolchain: "stable-2022-04-07"
|
||||
|
||||
- id: flutter
|
||||
uses: subosito/flutter-action@v2
|
||||
@@ -60,6 +64,8 @@ jobs:
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y dart curl build-essential libsqlite3-dev libssl-dev clang cmake ninja-build pkg-config libgtk-3-dev
|
||||
sudo apt-get install keybinder-3.0
|
||||
elif [ "$RUNNER_OS" == "Windows" ]; then
|
||||
vcpkg integrate install
|
||||
elif [ "$RUNNER_OS" == "macOS" ]; then
|
||||
echo 'do nothing'
|
||||
fi
|
||||
@@ -72,10 +78,10 @@ jobs:
|
||||
cargo install cargo-make
|
||||
cargo install duckscript_cli
|
||||
|
||||
- name: Cargo make flowy_dev
|
||||
- name: Cargo make appflowy-deps-tools
|
||||
working-directory: frontend
|
||||
run: |
|
||||
cargo make flowy_dev
|
||||
cargo make appflowy-deps-tools
|
||||
|
||||
- name: Config Flutter
|
||||
run: |
|
||||
@@ -83,6 +89,8 @@ jobs:
|
||||
flutter config --enable-linux-desktop
|
||||
elif [ "$RUNNER_OS" == "macOS" ]; then
|
||||
flutter config --enable-macos-desktop
|
||||
elif [ "$RUNNER_OS" == "Windows" ]; then
|
||||
flutter config --enable-windows-desktop
|
||||
fi
|
||||
shell: bash
|
||||
|
||||
|
||||
@@ -9,12 +9,14 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- "main"
|
||||
- "release/*"
|
||||
paths:
|
||||
- "frontend/app_flowy/**"
|
||||
|
||||
pull_request:
|
||||
branches:
|
||||
- "main"
|
||||
- "release/*"
|
||||
paths:
|
||||
- "frontend/app_flowy/**"
|
||||
|
||||
@@ -34,7 +36,7 @@ jobs:
|
||||
channel: "stable"
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: "stable-2022-01-20"
|
||||
toolchain: "stable-2022-04-07"
|
||||
|
||||
- name: Cache Cargo
|
||||
id: cache-cargo
|
||||
@@ -62,7 +64,7 @@ jobs:
|
||||
- name: Cargo make flowy dev
|
||||
working-directory: frontend
|
||||
run: |
|
||||
cargo make flowy_dev
|
||||
cargo make appflowy-deps-tools
|
||||
|
||||
- name: Flutter Deps
|
||||
run: flutter packages pub get
|
||||
@@ -71,9 +73,9 @@ jobs:
|
||||
- name: Build FlowySDK
|
||||
working-directory: frontend
|
||||
run: |
|
||||
cargo make --profile development-linux-x86_64 flowy-sdk-dev
|
||||
cargo make --profile development-linux-x86_64 appflowy-sdk-dev
|
||||
|
||||
- name: Code Generation
|
||||
- name: Flutter Code Generation
|
||||
working-directory: frontend/app_flowy
|
||||
run: |
|
||||
flutter packages pub run easy_localization:generate -f keys -o locale_keys.g.dart -S assets/translations -s en.json
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
name: Unit test(Flutter)
|
||||
name: Frontend test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "main"
|
||||
- "release/*"
|
||||
paths:
|
||||
- "frontend/app_flowy/**"
|
||||
|
||||
pull_request:
|
||||
branches:
|
||||
- "main"
|
||||
- "release/*"
|
||||
paths:
|
||||
- "frontend/app_flowy/**"
|
||||
|
||||
@@ -21,10 +23,9 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: "stable-2022-01-20"
|
||||
toolchain: "stable-2022-04-07"
|
||||
|
||||
- uses: subosito/flutter-action@v2
|
||||
with:
|
||||
@@ -53,23 +54,19 @@ jobs:
|
||||
working-directory: frontend
|
||||
run: |
|
||||
cargo install cargo-make
|
||||
|
||||
- name: Cargo make flowy dev
|
||||
working-directory: frontend
|
||||
run: |
|
||||
cargo make flowy_dev
|
||||
cargo make appflowy-deps-tools
|
||||
|
||||
- name: Flutter Deps
|
||||
working-directory: frontend/app_flowy
|
||||
run: |
|
||||
flutter config --enable-linux-desktop
|
||||
|
||||
- name: Build FlowySDK
|
||||
- name: Build Test lib
|
||||
working-directory: frontend
|
||||
run: |
|
||||
cargo make --profile development-linux-x86_64 flowy-sdk-dev
|
||||
cargo make --profile test-linux build-test-lib
|
||||
|
||||
- name: Code Generation
|
||||
- name: Flutter Code Generation
|
||||
working-directory: frontend/app_flowy
|
||||
run: |
|
||||
flutter packages pub get
|
||||
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: ${{ github.ref }}
|
||||
release_name: Release ${{ github.ref }}
|
||||
release_name: v${{ github.ref }}
|
||||
body_path: ${{ env.RELEASE_NOTES_PATH }}
|
||||
|
||||
build-linux-x86:
|
||||
@@ -37,6 +37,7 @@ jobs:
|
||||
env:
|
||||
LINUX_APP_RELEASE_PATH: frontend/app_flowy/product/${{ github.ref_name }}/linux/Release
|
||||
LINUX_ZIP_NAME: AppFlowy-linux-x86.tar.gz
|
||||
LINUX_PACKAGE_NAME: AppFlowy_${{ github.ref_name }}_linux-amd64.deb
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
@@ -44,7 +45,7 @@ jobs:
|
||||
- name: Setup environment - Rust and Cargo
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: 'stable-2022-01-20'
|
||||
toolchain: 'stable-2022-04-07'
|
||||
|
||||
- name: Setup environment - Flutter
|
||||
uses: subosito/flutter-action@v2
|
||||
@@ -62,7 +63,7 @@ jobs:
|
||||
source $HOME/.cargo/env
|
||||
cargo install --force cargo-make
|
||||
cargo install --force duckscript_cli
|
||||
cargo make flowy_dev
|
||||
cargo make appflowy-deps-tools
|
||||
|
||||
- name: Build Linux app
|
||||
working-directory: frontend
|
||||
@@ -70,6 +71,46 @@ jobs:
|
||||
flutter config --enable-linux-desktop
|
||||
cargo make --env APP_VERSION=${{ github.ref_name }} --profile production-linux-x86_64 appflowy
|
||||
|
||||
- name: Build Linux package
|
||||
working-directory: ${{ env.LINUX_APP_RELEASE_PATH }}
|
||||
run: |
|
||||
mkdir -p package/opt && mv AppFlowy package/opt/
|
||||
cd package && mkdir DEBIAN
|
||||
# Create control file
|
||||
printf 'Package: AppFlowy
|
||||
Version: %s
|
||||
Architecture: amd64
|
||||
Essential: no
|
||||
Priority: optional
|
||||
Maintainer: AppFlowy
|
||||
Description: An Open Source Alternative to Notion\n' "${{ github.ref_name }}" > DEBIAN/control
|
||||
|
||||
# postinst script for creating symlink
|
||||
printf '#!/bin/bash
|
||||
if [ -e /usr/local/bin/appflowy ]; then
|
||||
echo "Symlink already exists, skipping."
|
||||
else
|
||||
echo "Creating Symlink in /usr/local/bin/appflowy"
|
||||
ln -s /opt/AppFlowy/app_flowy /usr/local/bin/appflowy
|
||||
fi' > DEBIAN/postinst
|
||||
chmod 0755 DEBIAN/postinst
|
||||
|
||||
# postrm script for cleaning up residuals
|
||||
printf '#!/bin/bash
|
||||
if [ -e /usr/local/bin/appflowy ]; then
|
||||
rm /usr/local/bin/appflowy
|
||||
fi' > DEBIAN/postrm
|
||||
chmod 0755 DEBIAN/postrm
|
||||
|
||||
mkdir -p usr/share/applications
|
||||
# Update Exec & icon path in desktop entry
|
||||
grep -rl "\[CHANGE_THIS\]" ./opt/AppFlowy/appflowy.desktop.temp | xargs sed -i "s/\[CHANGE_THIS\]/\/opt/"
|
||||
# Add desktop entry in package
|
||||
mv ./opt/AppFlowy/appflowy.desktop.temp ./usr/share/applications/appflowy.desktop
|
||||
|
||||
# Build
|
||||
cd ../ && dpkg-deb --build --root-owner-group package ${{ env.LINUX_PACKAGE_NAME }}
|
||||
|
||||
- name: Upload Release Asset
|
||||
id: upload-release-asset
|
||||
uses: actions/upload-release-asset@v1
|
||||
@@ -81,12 +122,24 @@ jobs:
|
||||
asset_name: ${{ env.LINUX_ZIP_NAME }}
|
||||
asset_content_type: application/octet-stream
|
||||
|
||||
- name: Upload Release Asset Install Package
|
||||
id: upload-release-asset-install-package
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ needs.create-release.outputs.upload_url }}
|
||||
asset_path: ${{ env.LINUX_APP_RELEASE_PATH }}/${{ env.LINUX_PACKAGE_NAME }}
|
||||
asset_name: ${{ env.LINUX_PACKAGE_NAME }}
|
||||
asset_content_type: application/octet-stream
|
||||
|
||||
build-macos-x86_64:
|
||||
runs-on: macos-latest
|
||||
needs: create-release
|
||||
env:
|
||||
MACOS_APP_RELEASE_PATH: frontend/app_flowy/product/${{ github.ref_name }}/macos/Release
|
||||
MACOS_X86_ZIP_NAME: Appflowy-macos-x86_64.zip
|
||||
MACOS_DMG_NAME: Appflowy-macos-x86_64-installer
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
@@ -94,7 +147,7 @@ jobs:
|
||||
- name: Setup environment - Rust and Cargo
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: 'stable-2022-01-20'
|
||||
toolchain: 'stable-2022-04-07'
|
||||
|
||||
- name: Setup environment - Flutter
|
||||
uses: subosito/flutter-action@v2
|
||||
@@ -108,7 +161,7 @@ jobs:
|
||||
source $HOME/.cargo/env
|
||||
cargo install --force cargo-make
|
||||
cargo install --force duckscript_cli
|
||||
cargo make flowy_dev
|
||||
cargo make appflowy-deps-tools
|
||||
|
||||
- name: Build macOS app for x86_64
|
||||
working-directory: frontend
|
||||
@@ -116,9 +169,24 @@ jobs:
|
||||
flutter config --enable-macos-desktop
|
||||
cargo make --env APP_VERSION=${{ github.ref_name }} --profile production-mac-x86_64 appflowy
|
||||
|
||||
- name: Create MacOS dmg
|
||||
working-directory: frontend
|
||||
run: |
|
||||
brew install create-dmg
|
||||
create-dmg \
|
||||
--volname ${{ env.MACOS_DMG_NAME }} \
|
||||
--hide-extension "AppFlowy.app" \
|
||||
--background scripts/dmg_assets/AppFlowyInstallerBackground.jpg \
|
||||
--window-size 600 450 \
|
||||
--icon-size 94 \
|
||||
--icon "AppFlowy.app" 141 249 \
|
||||
--app-drop-link 458 249 \
|
||||
"${{ env.MACOS_APP_RELEASE_PATH }}/${{ env.MACOS_DMG_NAME }}.dmg" \
|
||||
"${{ env.MACOS_APP_RELEASE_PATH }}/AppFlowy.app"
|
||||
|
||||
- name: Archive macOS app
|
||||
working-directory: ${{ env.MACOS_APP_RELEASE_PATH }}
|
||||
run: zip -qr ${{ env.MACOS_X86_ZIP_NAME }} AppFlowy.app
|
||||
run: zip --symlinks -qr ${{ env.MACOS_X86_ZIP_NAME }} AppFlowy.app
|
||||
|
||||
- name: Upload Release Asset
|
||||
uses: actions/upload-release-asset@v1
|
||||
@@ -129,3 +197,85 @@ jobs:
|
||||
asset_path: ${{ env.MACOS_APP_RELEASE_PATH }}/${{ env.MACOS_X86_ZIP_NAME }}
|
||||
asset_name: ${{ env.MACOS_X86_ZIP_NAME }}
|
||||
asset_content_type: application/octet-stream
|
||||
- name: Upload DMG Release Asset
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ needs.create-release.outputs.upload_url }}
|
||||
asset_path: ${{ env.MACOS_APP_RELEASE_PATH }}/${{ env.MACOS_DMG_NAME }}.dmg
|
||||
asset_name: ${{ env.MACOS_DMG_NAME }}.dmg
|
||||
asset_content_type: application/octet-stream
|
||||
|
||||
build-windows-x86_64:
|
||||
runs-on: windows-latest
|
||||
needs: create-release
|
||||
env:
|
||||
WINDOWS_APP_RELEASE_PATH: frontend\app_flowy\product\${{ github.ref_name }}\windows
|
||||
WINDOWS_ZIP_NAME: AppFlowy-windows-x86_64.zip
|
||||
WINDOWS_INSTALLER_NAME: AppFlowy-${{ github.ref_name }}-windows-x86_64
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup environment - Rust and Cargo
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: 'stable-2022-04-07'
|
||||
|
||||
- name: Setup environment - Flutter
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: 'stable'
|
||||
flutter-version: '3.0.5'
|
||||
|
||||
- name: Pre build
|
||||
working-directory: frontend
|
||||
run: |
|
||||
vcpkg integrate install
|
||||
cargo install --force cargo-make
|
||||
cargo install --force duckscript_cli
|
||||
cargo make appflowy-deps-tools
|
||||
|
||||
- name: Build Windows app
|
||||
working-directory: frontend
|
||||
run: |
|
||||
flutter config --enable-windows-desktop
|
||||
cargo make --env APP_VERSION=${{ github.ref_name }} --profile production-windows-x86 appflowy
|
||||
|
||||
- uses: vimtor/action-zip@v1
|
||||
with:
|
||||
files: ${{ env.WINDOWS_APP_RELEASE_PATH }}\
|
||||
dest: ${{ env.WINDOWS_APP_RELEASE_PATH }}\${{ env.WINDOWS_ZIP_NAME }}
|
||||
|
||||
- name: Copy installer config & icon file
|
||||
working-directory: frontend
|
||||
run: |
|
||||
cp scripts/windows_installer/* ../${{ env.WINDOWS_APP_RELEASE_PATH }}
|
||||
|
||||
- name: Build installer executable
|
||||
working-directory: ${{ env.WINDOWS_APP_RELEASE_PATH }}
|
||||
run: |
|
||||
iscc /F${{ env.WINDOWS_INSTALLER_NAME }} inno_setup_config.iss /DAppVersion=${{ github.ref_name }}
|
||||
|
||||
- name: Upload Release Asset
|
||||
id: upload-release-asset
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ needs.create-release.outputs.upload_url }}
|
||||
asset_path: ${{ env.WINDOWS_APP_RELEASE_PATH }}\${{ env.WINDOWS_ZIP_NAME }}
|
||||
asset_name: ${{ env.WINDOWS_ZIP_NAME }}
|
||||
asset_content_type: application/octet-stream
|
||||
|
||||
- name: Upload Installer Asset
|
||||
id: upload-installer-asset
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ needs.create-release.outputs.upload_url }}
|
||||
asset_path: ${{ env.WINDOWS_APP_RELEASE_PATH }}\Output\${{ env.WINDOWS_INSTALLER_NAME }}.exe
|
||||
asset_name: ${{ env.WINDOWS_INSTALLER_NAME }}.exe
|
||||
asset_content_type: application/octet-stream
|
||||
@@ -0,0 +1,73 @@
|
||||
name: Rust coverage tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "main"
|
||||
- "release/*"
|
||||
paths:
|
||||
- "frontend/rust-lib/**"
|
||||
- "shared-lib/**"
|
||||
|
||||
pull_request:
|
||||
branches:
|
||||
- "main"
|
||||
- "release/*"
|
||||
paths:
|
||||
- "frontend/rust-lib/**"
|
||||
- "shared-lib/**"
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
|
||||
jobs:
|
||||
test-coverage:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- id: rust_toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: 'stable-2022-04-07'
|
||||
|
||||
- name: Cache Cargo
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.cargo
|
||||
key: ${{ runner.os }}-cargo-${{ steps.rust_toolchain.outputs.rustc_hash }}-${{ hashFiles('./frontend/rust-lib/Cargo.toml') }}
|
||||
|
||||
- name: Cache Rust
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
frontend/rust-lib/target
|
||||
shared-lib/target
|
||||
key: ${{ runner.os }}-rust-rust-lib-share-lib-${{ steps.rust_toolchain.outputs.rustc_hash }}-${{ hashFiles('./frontend/rust-lib/Cargo.toml') }}
|
||||
|
||||
- name: Setup Environment
|
||||
run: |
|
||||
if [ "$RUNNER_OS" == "Linux" ]; then
|
||||
sudo wget -qO /etc/apt/trusted.gpg.d/dart_linux_signing_key.asc https://dl-ssl.google.com/linux/linux_signing_key.pub
|
||||
sudo wget -qO /etc/apt/sources.list.d/dart_stable.list https://storage.googleapis.com/download.dartlang.org/linux/debian/dart_stable.list
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y dart curl build-essential libsqlite3-dev libssl-dev clang cmake ninja-build pkg-config libgtk-3-dev
|
||||
sudo apt-get install keybinder-3.0
|
||||
elif [ "$RUNNER_OS" == "macOS" ]; then
|
||||
echo 'do nothing'
|
||||
fi
|
||||
shell: bash
|
||||
|
||||
- name: Install cargo-make, grcov and llvm-tools-preview
|
||||
working-directory: frontend
|
||||
run: |
|
||||
cargo install cargo-make
|
||||
cargo install grcov
|
||||
rustup component add llvm-tools-preview
|
||||
|
||||
- name: Run Coverage tests and generate LCOV report
|
||||
working-directory: frontend
|
||||
run: cargo make get_ci_test_coverage
|
||||
@@ -4,6 +4,7 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- "main"
|
||||
- "release/*"
|
||||
paths:
|
||||
- "frontend/rust-lib/**"
|
||||
- "shared-lib/**"
|
||||
@@ -11,6 +12,7 @@ on:
|
||||
pull_request:
|
||||
branches:
|
||||
- "main"
|
||||
- "release/*"
|
||||
paths:
|
||||
- "frontend/rust-lib/**"
|
||||
- "shared-lib/**"
|
||||
@@ -26,7 +28,7 @@ jobs:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: 'stable-2022-01-20'
|
||||
toolchain: 'stable-2022-04-07'
|
||||
override: true
|
||||
- uses: subosito/flutter-action@v1
|
||||
with:
|
||||
@@ -37,12 +39,12 @@ jobs:
|
||||
working-directory: frontend
|
||||
run: |
|
||||
cargo install cargo-make
|
||||
cargo make flowy_dev
|
||||
cargo make appflowy-deps-tools
|
||||
|
||||
- name: Build FlowySDK
|
||||
working-directory: frontend
|
||||
run: |
|
||||
cargo make --profile development-linux-x86_64 flowy-sdk-dev
|
||||
cargo make --profile development-linux-x86_64 appflowy-sdk-dev
|
||||
|
||||
- run: rustup component add rustfmt
|
||||
working-directory: frontend/rust-lib
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
name: Unit test(Rust)
|
||||
name: Backend test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "main"
|
||||
- "release/*"
|
||||
paths:
|
||||
- "frontend/rust-lib/**"
|
||||
- "shared-lib/**"
|
||||
@@ -11,6 +12,7 @@ on:
|
||||
pull_request:
|
||||
branches:
|
||||
- "main"
|
||||
- "release/*"
|
||||
paths:
|
||||
- "frontend/rust-lib/**"
|
||||
- "shared-lib/**"
|
||||
@@ -28,7 +30,7 @@ jobs:
|
||||
- id: rust_toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: 'stable-2022-01-20'
|
||||
toolchain: 'stable-2022-04-07'
|
||||
|
||||
- name: Cache Cargo
|
||||
uses: actions/cache@v2
|
||||
@@ -55,4 +57,4 @@ jobs:
|
||||
|
||||
- name: Run shared-lib tests
|
||||
working-directory: shared-lib
|
||||
run: RUST_LOG=info cargo test --no-default-features
|
||||
run: RUST_LOG=info cargo test --no-default-features
|
||||
+72
-6
@@ -1,8 +1,74 @@
|
||||
# Release Notes
|
||||
## Version 0.0.7 - 11/27/2022
|
||||
### New features
|
||||
- Support adding filters by the text/checkbox/single-select property in Grid
|
||||
|
||||
## Version 0.0.6.2 - 10/30/2022
|
||||
- Fix some bugs
|
||||
|
||||
## Version 0.0.6.1 - 10/26/2022
|
||||
### New features
|
||||
- Optimize appflowy_editor dark mode style
|
||||
|
||||
### Bug Fixes
|
||||
- Unable to copy the text with checkbox or link style
|
||||
|
||||
## Version 0.0.6 - 10/23/2022
|
||||
|
||||
### New features
|
||||
- Integrate **appflowy_editor**
|
||||
|
||||
|
||||
## Version 0.0.5.3 - 09/26/2022
|
||||
|
||||
### New features
|
||||
- Open the next page automatically after deleting the current page
|
||||
- Refresh the Kanban board after altering a property type
|
||||
|
||||
### Bug Fixes
|
||||
- Fix switch board bug
|
||||
- Fix delete the Kanban board's row error
|
||||
- Remove duplicate time format
|
||||
- Fix can't delete field in property edit panel
|
||||
- Adjust some display UI issues
|
||||
|
||||
|
||||
## Version 0.0.5.2 - 09/16/2022
|
||||
|
||||
### New features
|
||||
- Enable adding a new card to the "No Status" group
|
||||
- Fix some bugs
|
||||
|
||||
### Bug Fixes
|
||||
- Fix cannot open AppFlowy error
|
||||
- Fix delete the Kanban board's row error
|
||||
|
||||
|
||||
## Version 0.0.5.1 - 09/14/2022
|
||||
|
||||
### New features
|
||||
- Enable deleting a field in board
|
||||
- Fix some bugs
|
||||
|
||||
|
||||
## Version 0.0.5 - 09/08/2022
|
||||
### New features - Kanban Board like Notion and Trello beta
|
||||
Boards are the best way to manage projects & tasks. Use them to group your databases by select, multiselect, and checkbox.
|
||||
|
||||
<p align="left"><img src="https://user-images.githubusercontent.com/12026239/190055984-6efa2d7a-ee38-4551-859e-ee56388e1859.gif" width="1000px" /></p>
|
||||
|
||||
- Set up columns that represent a specific phase of the project cycle and use cards to represent each project/task
|
||||
- Drag and drop a card from one phase/column to another phase/column
|
||||
- Update database properties in the Board view by clicking on a property and making edits on the card
|
||||
|
||||
### Other Features & Improvements
|
||||
- Settings allow users to change avatars
|
||||
- Click and drag the right edge to resize your sidebar
|
||||
- And many user interface improvements (link)
|
||||
|
||||
## Version 0.0.5 - beta.2 - beta.1 - 09/01/2022
|
||||
|
||||
New features
|
||||
### New features
|
||||
- Board-view database
|
||||
- Support start editing after creating a new card
|
||||
- Support editing the card directly by clicking the edit button
|
||||
@@ -14,7 +80,7 @@ New features
|
||||
|
||||
## Version 0.0.5 - beta.1 - 08/25/2022
|
||||
|
||||
New features
|
||||
### New features
|
||||
- Board-view database
|
||||
- Group by single select
|
||||
- drag and drop cards
|
||||
@@ -37,7 +103,7 @@ New features
|
||||
|
||||
## Version 0.0.4 - beta.3 - 05/02/2022
|
||||
- Drag to reorder app/ view/ field
|
||||
- Row record open as a page
|
||||
- Row record opens as a page
|
||||
- Auto resize the height of the row in the grid
|
||||
- Support more number formats
|
||||
- Search column options, supporting Single-select, Multi-select, and number format
|
||||
@@ -61,7 +127,7 @@ New features
|
||||
## Version 0.0.4 - beta.1 - 04/08/2022
|
||||
v0.0.4 - beta.1 is pre-release
|
||||
|
||||
New features
|
||||
### New features
|
||||
- Table-view database
|
||||
- supported column types: Text, Checkbox, Single-select, Multi-select, Numbers
|
||||
- hide / delete columns
|
||||
@@ -70,12 +136,12 @@ New features
|
||||
## Version 0.0.3 - 02/23/2022
|
||||
v0.0.3 is production ready, available on Linux, macOS, and Windows
|
||||
|
||||
New features
|
||||
### New features
|
||||
- Dark Mode
|
||||
- Support new languages: French, Italian, Russian, Simplified Chinese, Spanish
|
||||
- Add Settings: Toggle on Dark Mode; Select a language
|
||||
- Show device info
|
||||
- Add tooltip on toolbar icons
|
||||
- Add tooltip on the toolbar icons
|
||||
|
||||
Bug fixes and improvements
|
||||
- Increased height of action
|
||||
|
||||
@@ -23,12 +23,15 @@ You are in charge of your data and customizations.
|
||||
<a href="https://twitter.com/appflowy"><b>Twitter</b></a>
|
||||
</p>
|
||||
|
||||
<p align="center"><img src="https://user-images.githubusercontent.com/12026239/174754661-682980e4-e386-4685-bb6f-2da357390b61.png" alt="The Open Source Alternative To Notion." width="1000px" /></p>
|
||||
<p align="center"><img src="https://user-images.githubusercontent.com/12026239/200787830-96be260b-d0a0-4152-864e-6730b19095cd.png" alt="The Open Source Alternative To Notion." width="1000px" /></p>
|
||||
<p align="center"><img src="https://user-images.githubusercontent.com/12026239/174753177-98e4c899-2356-4137-bb42-374bba2b127b.png" alt="The Open Source Alternative To Notion." width="1000px" /></p>
|
||||
<p align="center"><img src="https://user-images.githubusercontent.com/12026239/190650183-a940f1e0-a2c5-4797-ab3a-56758f6f696c.png" alt="The Open Source Alternative To Notion." width="1000px" /></p>
|
||||
|
||||
## User Installation
|
||||
|
||||
Please view the [documentation](https://appflowy.gitbook.io/docs/essential-documentation/install-appflowy/installation-methods) for OS specific installation instructions.
|
||||
* [Windows/Mac/Linux](https://appflowy.gitbook.io/docs/essential-documentation/install-appflowy/installation-methods/mac-windows-linux-packages)
|
||||
* [Docker](https://appflowy.gitbook.io/docs/essential-documentation/install-appflowy/installation-methods/installing-with-docker)
|
||||
* [Source](https://appflowy.gitbook.io/docs/essential-documentation/install-appflowy/installation-methods/from-source)
|
||||
|
||||
## Built With
|
||||
|
||||
@@ -48,7 +51,9 @@ Please view the [documentation](https://appflowy.gitbook.io/docs/essential-docum
|
||||
- [AppFlowy Roadmap ReadMe](https://appflowy.gitbook.io/docs/essential-documentation/roadmap)
|
||||
- [AppFlowy Public Roadmap](https://github.com/orgs/AppFlowy-IO/projects/5/views/12)
|
||||
|
||||
If you'd like to propose a feature, submit an issue [here](https://github.com/AppFlowy-IO/appflowy/issues).
|
||||
|
||||
If you'd like to propose a feature, submit a feature request [here](https://github.com/AppFlowy-IO/AppFlowy/issues/new?assignees=&labels=&template=feature_request.yaml&title=%5BFR%5D+) <br/>
|
||||
If you'd like to report a bug, submit bug report [here](https://github.com/AppFlowy-IO/AppFlowy/issues/new?assignees=&labels=&template=bug_report.yaml&title=%5BBug%5D+)
|
||||
|
||||
## **Releases**
|
||||
|
||||
@@ -61,7 +66,11 @@ Contributions are what make the open source community such an amazing place to b
|
||||
If your Pull Request is accepted as it fixes a bug, adds functionality, or makes AppFlowy's codebase significantly easier to use or understand, congratulations! If your administrative and managerial work behind the scenes that sustains the community as a whole, congratulations! You are now an official contributor to AppFlowy. Get in touch with us ([link](https://tally.so/r/mKP5z3)) to receive the very special Contributor T-shirt!
|
||||
Proudly wear your T-shirt and show it to us by tagging [@appflowy](https://twitter.com/appflowy) on Twitter.
|
||||
|
||||

|
||||
|
||||
## Join the community to build AppFlowy together!
|
||||
<a href="https://github.com/AppFlowy-IO/AppFlowy/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=AppFlowy-IO/AppFlowy" />
|
||||
</a>
|
||||
|
||||
## Why Are We Building This?
|
||||
|
||||
@@ -86,7 +95,7 @@ To be honest, we do not claim to outperform Notion in terms of functionality and
|
||||
|
||||
## License
|
||||
|
||||
Distributed under the AGPLv3 License. See `LICENSE.md` for more information.
|
||||
Distributed under the AGPLv3 License. See [`LICENSE.md`](https://github.com/AppFlowy-IO/AppFlowy/blob/main/LICENSE) for more information.
|
||||
|
||||
## Acknowledgements
|
||||
|
||||
@@ -94,3 +103,4 @@ Special thanks to these amazing projects which help power AppFlowy.IO:
|
||||
|
||||
- [flutter-quill](https://github.com/singerdmx/flutter-quill)
|
||||
- [cargo-make](https://github.com/sagiegurari/cargo-make)
|
||||
- [contrib.rocks](https://contrib.rocks)
|
||||
Vendored
+1
-1
@@ -48,7 +48,7 @@
|
||||
{
|
||||
"label": "AF: build_flowy_sdk_for_android",
|
||||
"type": "shell",
|
||||
"command": "cargo make --profile development-android flowy-sdk-dev-android",
|
||||
"command": "cargo make --profile development-android appflowy-sdk-dev-android",
|
||||
"group": "build",
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}"
|
||||
|
||||
+71
-19
@@ -14,7 +14,7 @@ extend = [
|
||||
on_error_task = "catch"
|
||||
|
||||
[tasks.catch]
|
||||
run_task = {name = ["restore-crate-type"]}
|
||||
run_task = { name = ["restore-crate-type"] }
|
||||
|
||||
[env]
|
||||
RUST_LOG = "info"
|
||||
@@ -22,7 +22,7 @@ CARGO_MAKE_EXTEND_WORKSPACE_MAKEFILE = true
|
||||
CARGO_MAKE_CRATE_FS_NAME = "dart_ffi"
|
||||
CARGO_MAKE_CRATE_NAME = "dart-ffi"
|
||||
LIB_NAME = "dart_ffi"
|
||||
CURRENT_APP_VERSION = "0.0.5"
|
||||
CURRENT_APP_VERSION = "0.0.7"
|
||||
FEATURES = "flutter"
|
||||
PRODUCT_NAME = "AppFlowy"
|
||||
# CRATE_TYPE: https://doc.rust-lang.org/reference/linkage.html
|
||||
@@ -40,10 +40,16 @@ PRODUCT_NAME = "AppFlowy"
|
||||
# for cdylib:
|
||||
# if (Platform.isMacOS) return DynamicLibrary.open('${prefix}/libdart_ffi.dylib');
|
||||
CRATE_TYPE = "staticlib"
|
||||
SDK_EXT = "a"
|
||||
LIB_EXT = "a"
|
||||
APP_ENVIRONMENT = "local"
|
||||
FLUTTER_FLOWY_SDK_PATH="app_flowy/packages/flowy_sdk"
|
||||
PROTOBUF_DERIVE_CACHE="../shared-lib/flowy-derive/src/derive_cache/derive_cache.rs"
|
||||
FLUTTER_FLOWY_SDK_PATH = "app_flowy/packages/flowy_sdk"
|
||||
PROTOBUF_DERIVE_CACHE = "../shared-lib/flowy-derive/src/derive_cache/derive_cache.rs"
|
||||
# Test default config
|
||||
TEST_CRATE_TYPE = "cdylib"
|
||||
TEST_LIB_EXT = "dylib"
|
||||
TEST_RUST_LOG = "info"
|
||||
TEST_BUILD_FLAG = "debug"
|
||||
TEST_COMPILE_TARGET = "x86_64-apple-darwin"
|
||||
|
||||
[env.development-mac-arm64]
|
||||
RUST_LOG = "info"
|
||||
@@ -88,7 +94,7 @@ BUILD_FLAG = "debug"
|
||||
FLUTTER_OUTPUT_DIR = "Debug"
|
||||
PRODUCT_EXT = "exe"
|
||||
CRATE_TYPE = "cdylib"
|
||||
SDK_EXT = "dll"
|
||||
LIB_EXT = "dll"
|
||||
|
||||
[env.production-windows-x86]
|
||||
BUILD_FLAG = "release"
|
||||
@@ -97,7 +103,7 @@ RUST_COMPILE_TARGET = "x86_64-pc-windows-msvc"
|
||||
FLUTTER_OUTPUT_DIR = "Release"
|
||||
PRODUCT_EXT = "exe"
|
||||
CRATE_TYPE = "cdylib"
|
||||
SDK_EXT = "dll"
|
||||
LIB_EXT = "dll"
|
||||
APP_ENVIRONMENT = "production"
|
||||
|
||||
[env.development-linux-x86_64]
|
||||
@@ -106,7 +112,7 @@ RUST_COMPILE_TARGET = "x86_64-unknown-linux-gnu"
|
||||
BUILD_FLAG = "debug"
|
||||
CRATE_TYPE = "cdylib"
|
||||
FLUTTER_OUTPUT_DIR = "Debug"
|
||||
SDK_EXT = "so"
|
||||
LIB_EXT = "so"
|
||||
LINUX_ARCH = "x64"
|
||||
|
||||
[env.production-linux-x86_64]
|
||||
@@ -115,7 +121,7 @@ TARGET_OS = "linux"
|
||||
RUST_COMPILE_TARGET = "x86_64-unknown-linux-gnu"
|
||||
CRATE_TYPE = "cdylib"
|
||||
FLUTTER_OUTPUT_DIR = "Release"
|
||||
SDK_EXT = "so"
|
||||
LIB_EXT = "so"
|
||||
LINUX_ARCH = "x64"
|
||||
APP_ENVIRONMENT = "production"
|
||||
|
||||
@@ -125,7 +131,7 @@ RUST_COMPILE_TARGET = "aarch64-unknown-linux-gnu"
|
||||
BUILD_FLAG = "debug"
|
||||
CRATE_TYPE = "cdylib"
|
||||
FLUTTER_OUTPUT_DIR = "Debug"
|
||||
SDK_EXT = "so"
|
||||
LIB_EXT = "so"
|
||||
LINUX_ARCH = "arm64"
|
||||
|
||||
[env.production-linux-aarch64]
|
||||
@@ -134,13 +140,12 @@ TARGET_OS = "linux"
|
||||
RUST_COMPILE_TARGET = "aarch64-unknown-linux-gnu"
|
||||
CRATE_TYPE = "cdylib"
|
||||
FLUTTER_OUTPUT_DIR = "Release"
|
||||
SDK_EXT = "so"
|
||||
LIB_EXT = "so"
|
||||
LINUX_ARCH = "arm64"
|
||||
APP_ENVIRONMENT = "production"
|
||||
|
||||
[tasks.echo_env]
|
||||
script = [
|
||||
'''
|
||||
script = ['''
|
||||
echo "-------- Env Parameters --------"
|
||||
echo CRATE_TYPE: ${CRATE_TYPE}
|
||||
echo BUILD_FLAG: ${BUILD_FLAG}
|
||||
@@ -151,8 +156,7 @@ script = [
|
||||
echo APP_ENVIRONMENT: ${APP_ENVIRONMENT}
|
||||
echo ${platforms}
|
||||
echo ${BUILD_ARCHS}
|
||||
'''
|
||||
]
|
||||
''']
|
||||
script_runner = "@shell"
|
||||
|
||||
[env.production-ios]
|
||||
@@ -166,6 +170,14 @@ BUILD_FLAG = "debug"
|
||||
TARGET_OS = "android"
|
||||
CRATE_TYPE = "cdylib"
|
||||
FLUTTER_OUTPUT_DIR = "Debug"
|
||||
FEATURES = "flutter,openssl_vendored"
|
||||
|
||||
[env.production-android]
|
||||
BUILD_FLAG = "release"
|
||||
TARGET_OS = "android"
|
||||
CRATE_TYPE = "cdylib"
|
||||
FLUTTER_OUTPUT_DIR = "Release"
|
||||
FEATURES = "flutter,openssl_vendored"
|
||||
|
||||
[tasks.setup-crate-type]
|
||||
private = true
|
||||
@@ -191,12 +203,52 @@ script = [
|
||||
]
|
||||
script_runner = "@duckscript"
|
||||
|
||||
[tasks.test-build]
|
||||
condition = { env_set = [ "FLUTTER_FLOWY_SDK_PATH"] }
|
||||
[env.test-macos]
|
||||
TEST_CRATE_TYPE = "cdylib"
|
||||
TEST_LIB_EXT = "dylib"
|
||||
# For the moment, the DynamicLibrary only supports open x86_64 architectures binary.
|
||||
TEST_COMPILE_TARGET = "x86_64-apple-darwin"
|
||||
|
||||
[env.test-linux]
|
||||
TEST_CRATE_TYPE = "cdylib"
|
||||
TEST_LIB_EXT = "so"
|
||||
TEST_COMPILE_TARGET = "x86_64-unknown-linux-gnu"
|
||||
|
||||
[env.test-windows]
|
||||
TEST_CRATE_TYPE = "cdylib"
|
||||
TEST_LIB_EXT = "dll"
|
||||
TEST_COMPILE_TARGET = "x86_64-pc-windows-msvc"
|
||||
|
||||
[tasks.setup-test-crate-type]
|
||||
private = true
|
||||
script = [
|
||||
"""
|
||||
cd ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/rust-lib/flowy-net
|
||||
cargo build -vv --features=dart
|
||||
toml = readfile ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/rust-lib/${CARGO_MAKE_CRATE_NAME}/Cargo.toml
|
||||
val = replace ${toml} "staticlib" ${TEST_CRATE_TYPE}
|
||||
result = writefile ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/rust-lib/${CARGO_MAKE_CRATE_NAME}/Cargo.toml ${val}
|
||||
assert ${result}
|
||||
""",
|
||||
]
|
||||
script_runner = "@duckscript"
|
||||
|
||||
[tasks.restore-test-crate-type]
|
||||
private = true
|
||||
script = [
|
||||
"""
|
||||
toml = readfile ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/rust-lib/${CARGO_MAKE_CRATE_NAME}/Cargo.toml
|
||||
val = replace ${toml} ${TEST_CRATE_TYPE} "staticlib"
|
||||
result = writefile ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/rust-lib/${CARGO_MAKE_CRATE_NAME}/Cargo.toml ${val}
|
||||
assert ${result}
|
||||
""",
|
||||
]
|
||||
script_runner = "@duckscript"
|
||||
|
||||
[tasks.test-build]
|
||||
condition = { env_set = ["FLUTTER_FLOWY_SDK_PATH"] }
|
||||
script = ["""
|
||||
cd ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/rust-lib/flowy-net
|
||||
cargo build -vv --features=dart
|
||||
"""]
|
||||
script_runner = "@shell"
|
||||
|
||||
|
||||
@@ -12,8 +12,8 @@ When compiling for android we need the following pre-requisites:
|
||||
- [Download](https://developer.android.com/ndk/downloads/) Android NDK version 24.
|
||||
- When downloading Android NDK you can get the compressed version as a standalone from the site.
|
||||
Or you can download it through [Android Studio](https://developer.android.com/studio).
|
||||
- After downloading the two you need to set the environment variables. For Windows that's a seperate process.
|
||||
On MacOs and Linux the process is similar.
|
||||
- After downloading the two you need to set the environment variables. For Windows that's a separate process.
|
||||
On macOS and Linux the process is similar.
|
||||
- The variables needed are '$ANDROID_NDK_HOME', this will point to where the NDK is located.
|
||||
---
|
||||
|
||||
@@ -48,9 +48,9 @@ linker = "/home/user/Android/Sdk/ndk/24.0.8215888/toolchains/llvm/prebuilt/linux
|
||||
|
||||
**Folder path: 'Android/Sdk/ndk/24.0.8215888/toolchains/llvm/prebuilt/linux-x86_64/lib64/clang/14.0.1/lib/linux'.**
|
||||
After that you have to copy this file into three different folders namely aarch64, arm, i386 and x86_64.
|
||||
We have to do this so we Android NDK can find clang on our system, if we used NDK 22 we wouldnt have to do this process.
|
||||
Though using NDK v22 will not give us alot of features to work with.
|
||||
This github [issue](https://github.com/fzyzcjy/flutter_rust_bridge/issues/419) explains the reason why we are doing this.
|
||||
We have to do this so we Android NDK can find clang on our system, if we used NDK 22 we wouldn't have to do this process.
|
||||
Though using NDK v22 will not give us a lot of features to work with.
|
||||
This GitHub [issue](https://github.com/fzyzcjy/flutter_rust_bridge/issues/419) explains the reason why we are doing this.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
Copyright 2020 The Poppins Project Authors (https://github.com/itfoundry/Poppins)
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -161,7 +161,47 @@
|
||||
"filter": "Filter",
|
||||
"sortBy": "Sort by",
|
||||
"Properties": "Properties",
|
||||
"group": "Group"
|
||||
"group": "Group",
|
||||
"addFilter": "Add Filter",
|
||||
"deleteFilter": "Delete filter",
|
||||
"filterBy": "Filter by...",
|
||||
"typeAValue": "Type a value..."
|
||||
},
|
||||
"textFilter": {
|
||||
"contains": "Contains",
|
||||
"doesNotContain": "Does not contain",
|
||||
"endsWith": "Ends with",
|
||||
"startWith": "Starts with",
|
||||
"is": "Is",
|
||||
"isNot": "Is not",
|
||||
"isEmpty": "Is empty",
|
||||
"isNotEmpty": "Is not empty",
|
||||
"choicechipPrefix": {
|
||||
"isNot": "Not",
|
||||
"startWith": "Starts with",
|
||||
"endWith": "Ends with",
|
||||
"isEmpty": "is empty",
|
||||
"isNotEmpty": "is not empty"
|
||||
}
|
||||
},
|
||||
"checkboxFilter": {
|
||||
"isChecked": "Checked",
|
||||
"isUnchecked": "Unchecked",
|
||||
"choicechipPrefix": {
|
||||
"is": "is"
|
||||
}
|
||||
},
|
||||
"singleSelectOptionFilter": {
|
||||
"is": "Is",
|
||||
"isNot": "Is not",
|
||||
"isEmpty": "Is empty",
|
||||
"isNotEmpty": "Is not empty"
|
||||
},
|
||||
"multiSelectOptionFilter": {
|
||||
"contains": "Contains",
|
||||
"doesNotContain": "Does not contain",
|
||||
"isEmpty": "Is empty",
|
||||
"isNotEmpty": "Is not empty"
|
||||
},
|
||||
"field": {
|
||||
"hide": "Hide",
|
||||
@@ -181,7 +221,7 @@
|
||||
"includeTime": " Include time",
|
||||
"dateFormatFriendly": "Month Day,Year",
|
||||
"dateFormatISO": "Year-Month-Day",
|
||||
"dateFormatLocal": "Year/Month/Day",
|
||||
"dateFormatLocal": "Month/Day/Year",
|
||||
"dateFormatUS": "Year/Month/Day",
|
||||
"timeFormat": " Time format",
|
||||
"invalidTimeFormat": "Invalid format",
|
||||
@@ -191,13 +231,16 @@
|
||||
"optionTitle": "Options",
|
||||
"addOption": "Add option",
|
||||
"editProperty": "Edit property",
|
||||
"newColumn": "New column"
|
||||
"newColumn": "New column",
|
||||
"deleteFieldPromptMessage": "Are you sure? This property will be deleted"
|
||||
},
|
||||
"row": {
|
||||
"duplicate": "Duplicate",
|
||||
"delete": "Delete",
|
||||
"textPlaceholder": "Empty",
|
||||
"copyProperty": "Copied property to clipboard"
|
||||
"copyProperty": "Copied property to clipboard",
|
||||
"count": "Count",
|
||||
"newRow": "New row"
|
||||
},
|
||||
"selectOption": {
|
||||
"create": "Create",
|
||||
@@ -211,17 +254,17 @@
|
||||
"aquaColor": "Aqua",
|
||||
"blueColor": "Blue",
|
||||
"deleteTag": "Delete tag",
|
||||
"colorPannelTitle": "Colors",
|
||||
"pannelTitle": "Select an option or create one",
|
||||
"colorPanelTitle": "Colors",
|
||||
"panelTitle": "Select an option or create one",
|
||||
"searchOption": "Search for an option"
|
||||
},
|
||||
"menuName": "Grid"
|
||||
},
|
||||
"document": {
|
||||
"menuName": "Doc",
|
||||
"menuName": "Document",
|
||||
"date": {
|
||||
"timeHintTextInTwelveHour": "12:00 AM",
|
||||
"timeHintTextInTwentyFourHour": "12:00"
|
||||
"timeHintTextInTwelveHour": "01:00 PM",
|
||||
"timeHintTextInTwentyFourHour": "13:00"
|
||||
}
|
||||
},
|
||||
"board": {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"letsGoButtonText": "Vamos",
|
||||
"title": "Título",
|
||||
"signUp": {
|
||||
"buttonText": "Registar",
|
||||
"buttonText": "Registrar",
|
||||
"title": "Registrar en @:appName",
|
||||
"getStartedText": "Empezar",
|
||||
"emptyPasswordError": "La contraseña no puede estar en blanco",
|
||||
@@ -16,7 +16,7 @@
|
||||
"alreadyHaveAnAccount": "¿Posee credenciales?",
|
||||
"emailHint": "Correo",
|
||||
"passwordHint": "Contraseña",
|
||||
"repeatPasswordHint": "Repite la contraseña"
|
||||
"repeatPasswordHint": "Repetir contraseña"
|
||||
},
|
||||
"signIn": {
|
||||
"loginTitle": "Ingresa a @:appName",
|
||||
@@ -58,7 +58,7 @@
|
||||
}
|
||||
},
|
||||
"deletePagePrompt": {
|
||||
"text": "Esta paágina esta en la Papelera",
|
||||
"text": "Esta página está en la Papelera",
|
||||
"restore": "Recuperar página",
|
||||
"deletePermanent": "Eliminar permanentemente"
|
||||
},
|
||||
@@ -69,7 +69,7 @@
|
||||
"debug": {
|
||||
"name": "Información de depuración",
|
||||
"success": "¡Información copiada!",
|
||||
"fail": "No fué posible copiar la información"
|
||||
"fail": "No fue posible copiar la información"
|
||||
}
|
||||
},
|
||||
"menuAppHeader": {
|
||||
@@ -167,15 +167,15 @@
|
||||
"singleSelectFieldName": "Seleccionar",
|
||||
"multiSelectFieldName": "Selección múltiple",
|
||||
"urlFieldName": "URL",
|
||||
"numberFormat": " Formato numérico",
|
||||
"dateFormat": " Formato de fecha",
|
||||
"includeTime": " Incluir tiempo",
|
||||
"numberFormat": "Formato numérico",
|
||||
"dateFormat": "Formato de fecha",
|
||||
"includeTime": "Incluir tiempo",
|
||||
"dateFormatFriendly": "Mes Día, Año",
|
||||
"dateFormatISO": "Año-Mes-Día",
|
||||
"dateFormatLocal": "Año/Mes/Día",
|
||||
"dateFormatLocal": "Mes/Día/Año",
|
||||
"dateFormatUS": "Año/Mes/Día",
|
||||
"timeFormat": " Time format",
|
||||
"invalidTimeFormat": "Formato de tiempo",
|
||||
"timeFormat": "Formato de tiempo",
|
||||
"invalidTimeFormat": "Formato de tiempo inválido",
|
||||
"timeFormatTwelveHour": "12 horas",
|
||||
"timeFormatTwentyFourHour": "24 horas",
|
||||
"addSelectOption": "Añadir una opción",
|
||||
@@ -201,21 +201,26 @@
|
||||
"aquaColor": "Agua",
|
||||
"blueColor": "Azul",
|
||||
"deleteTag": "Borrar etiqueta",
|
||||
"colorPannelTitle": "Colores",
|
||||
"pannelTitle": "Selecciona una opción o crea una",
|
||||
"colorPanelTitle": "Colores",
|
||||
"panelTitle": "Selecciona una opción o crea una",
|
||||
"searchOption": "Buscar una opción"
|
||||
},
|
||||
"menuName": "Grid"
|
||||
"menuName": "Cuadrícula"
|
||||
},
|
||||
"document": {
|
||||
"menuName": "Doc",
|
||||
"menuName": "Documento",
|
||||
"date": {
|
||||
"timeHintTextInTwelveHour": "12:00 AM",
|
||||
"timeHintTextInTwentyFourHour": "12:00"
|
||||
"timeHintTextInTwelveHour": "01:00 PM",
|
||||
"timeHintTextInTwentyFourHour": "13:00"
|
||||
}
|
||||
},
|
||||
"sideBar": {
|
||||
"openSidebar": "Open sidebar",
|
||||
"closeSidebar": "Close sidebar"
|
||||
"openSidebar": "Abrir panel lateral",
|
||||
"closeSidebar": "Cerrar panel lateral"
|
||||
},
|
||||
"board": {
|
||||
"column": {
|
||||
"create_new_card": "Nuevo"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -94,7 +94,20 @@
|
||||
},
|
||||
"tooltip": {
|
||||
"lightMode": "Passer en mode clair",
|
||||
"darkMode": "Passer en mode sombre"
|
||||
"darkMode": "Passer en mode sombre",
|
||||
"openAsPage": "Ouvrir en tant que page",
|
||||
"addNewRow": "Ajouter une ligne",
|
||||
"openMenu": "Cliquer pour ouvrir le menu"
|
||||
},
|
||||
"sideBar": {
|
||||
"closeSidebar": "Fermer le menu latéral",
|
||||
"openSidebar": "Ouvrir le menu latéral"
|
||||
},
|
||||
"notifications": {
|
||||
"export": {
|
||||
"markdown": "Note exportée en Markdown",
|
||||
"path": "Documents/flowy"
|
||||
}
|
||||
},
|
||||
"contactsPage": {
|
||||
"title": "Contacts",
|
||||
@@ -123,7 +136,7 @@
|
||||
"failedMsg": "Assurez-vous d'avoir terminé le processus de connexion dans votre navigateur."
|
||||
},
|
||||
"google": {
|
||||
"title": "CONNEXION GOOGLE",
|
||||
"title": "CONNEXION VIA GOOGLE",
|
||||
"instruction1": "Pour importer vos contacts Google, vous devez autoriser cette application à l'aide de votre navigateur web.",
|
||||
"instruction2": "Copiez ce code dans votre presse-papiers en cliquant sur l'icône ou en sélectionnant le texte:",
|
||||
"instruction3": "Accédez au lien suivant dans votre navigateur web et saisissez le code ci-dessus:",
|
||||
@@ -135,6 +148,7 @@
|
||||
"menu": {
|
||||
"appearance": "Apparence",
|
||||
"language": "Langue",
|
||||
"user": "Utilisateur",
|
||||
"open": "Ouvrir les paramètres"
|
||||
},
|
||||
"appearance": {
|
||||
@@ -142,15 +156,12 @@
|
||||
"darkLabel": "Mode sombre"
|
||||
}
|
||||
},
|
||||
"sideBar": {
|
||||
"openSidebar": "Open sidebar",
|
||||
"closeSidebar": "Close sidebar"
|
||||
},
|
||||
"grid": {
|
||||
"settings": {
|
||||
"filter": "Filtrer",
|
||||
"sortBy": "Trier par",
|
||||
"Properties": "Propriétés"
|
||||
"sortBy": "Filtrer par",
|
||||
"Properties": "Propriétés",
|
||||
"group": "Groupe"
|
||||
},
|
||||
"field": {
|
||||
"hide": "Cacher",
|
||||
@@ -170,7 +181,7 @@
|
||||
"includeTime": " Inclure l'heure",
|
||||
"dateFormatFriendly": "Mois Jour, Année",
|
||||
"dateFormatISO": "Année-Mois-Jour",
|
||||
"dateFormatLocal": "Année/Mois/Jour",
|
||||
"dateFormatLocal": "Mois/Jour/Année",
|
||||
"dateFormatUS": "Année/Mois/Jour",
|
||||
"timeFormat": " Format du temps",
|
||||
"invalidTimeFormat": "Format invalide",
|
||||
@@ -179,13 +190,17 @@
|
||||
"addSelectOption": "Ajouter une option",
|
||||
"optionTitle": "Options",
|
||||
"addOption": "Ajouter une option",
|
||||
"editProperty": "Modifier la propriété"
|
||||
"editProperty": "Modifier la propriété",
|
||||
"newColumn": "Nouvelle colonne",
|
||||
"deleteFieldPromptMessage": "Vous voulez supprimer cette propriété ?"
|
||||
},
|
||||
"row": {
|
||||
"duplicate": "Dupliquer",
|
||||
"delete": "Supprimer",
|
||||
"textPlaceholder": "Vide",
|
||||
"copyProperty": "Copie de la propriété dans le presse-papiers"
|
||||
"copyProperty": "Copie de la propriété dans le presse-papiers",
|
||||
"count": "Nombre",
|
||||
"newRow": "Nouvelle ligne"
|
||||
},
|
||||
"selectOption": {
|
||||
"create": "Créer",
|
||||
@@ -199,17 +214,22 @@
|
||||
"aquaColor": "Aqua",
|
||||
"blueColor": "Bleu",
|
||||
"deleteTag": "Supprimer l'étiquette",
|
||||
"colorPannelTitle": "Couleurs",
|
||||
"pannelTitle": "Sélectionnez une option ou créez-en une",
|
||||
"colorPanelTitle": "Couleurs",
|
||||
"panelTitle": "Sélectionnez une option ou créez-en une",
|
||||
"searchOption": "Rechercher une option"
|
||||
},
|
||||
"menuName": "Grille"
|
||||
},
|
||||
"document": {
|
||||
"menuName": "Doc",
|
||||
"menuName": "Document",
|
||||
"date": {
|
||||
"timeHintTextInTwelveHour": "12:00 AM",
|
||||
"timeHintTextInTwentyFourHour": "12:00"
|
||||
"timeHintTextInTwelveHour": "01:00 PM",
|
||||
"timeHintTextInTwentyFourHour": "13:00"
|
||||
}
|
||||
},
|
||||
"board": {
|
||||
"column": {
|
||||
"create_new_card": "Nouveau"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -173,7 +173,7 @@
|
||||
"includeTime": " Sertakan waktu",
|
||||
"dateFormatFriendly": "Bulan Hari,Tahun",
|
||||
"dateFormatISO": "Tahun-Bulan-Hari",
|
||||
"dateFormatLocal": "Tahun/Bulan/Hari",
|
||||
"dateFormatLocal": "Bulan/Hari/Tahun",
|
||||
"dateFormatUS": "Tahun/Bulan/Hari",
|
||||
"timeFormat": " Format waktu",
|
||||
"invalidTimeFormat": "Format yang tidak valid",
|
||||
@@ -202,17 +202,17 @@
|
||||
"aquaColor": "Air",
|
||||
"blueColor": "Biru",
|
||||
"deleteTag": "Hapus tag",
|
||||
"colorPannelTitle": "Warna",
|
||||
"pannelTitle": "Pilih opsi atau buat baru",
|
||||
"colorPanelTitle": "Warna",
|
||||
"panelTitle": "Pilih opsi atau buat baru",
|
||||
"searchOption": "Cari opsi"
|
||||
},
|
||||
"menuName": "Grid"
|
||||
},
|
||||
"document": {
|
||||
"menuName": "Doc",
|
||||
"menuName": "Dokter",
|
||||
"date": {
|
||||
"timeHintTextInTwelveHour": "12:00 AM",
|
||||
"timeHintTextInTwentyFourHour": "12:00"
|
||||
"timeHintTextInTwelveHour": "01:00 PM",
|
||||
"timeHintTextInTwentyFourHour": "13:00"
|
||||
}
|
||||
},
|
||||
"sideBar": {
|
||||
|
||||
@@ -165,7 +165,7 @@
|
||||
"includeTime": " 時刻を含める",
|
||||
"dateFormatFriendly": "月 日,年",
|
||||
"dateFormatISO": "年-月-日",
|
||||
"dateFormatLocal": "年/月/日",
|
||||
"dateFormatLocal": "月/日/年",
|
||||
"dateFormatUS": "年/月/日",
|
||||
"timeFormat": " 時刻書式",
|
||||
"timeFormatTwelveHour": "12 時間表記",
|
||||
@@ -191,8 +191,8 @@
|
||||
"aquaColor": "水色",
|
||||
"blueColor": "青",
|
||||
"deleteTag": "選択候補を削除",
|
||||
"colorPannelTitle": "色",
|
||||
"pannelTitle": "選択候補を検索 または 作成する",
|
||||
"colorPanelTitle": "色",
|
||||
"panelTitle": "選択候補を検索 または 作成する",
|
||||
"searchOption": "選択候補を検索"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -0,0 +1,236 @@
|
||||
{
|
||||
"appName": "AppFlowy",
|
||||
"defaultUsername": "Me",
|
||||
"welcomeText": "@:appName 에 오신것을 환영합니다",
|
||||
"githubStarText": "Star on GitHub",
|
||||
"subscribeNewsletterText": "뉴스레터 구독",
|
||||
"letsGoButtonText": "Let's Go",
|
||||
"title": "제목",
|
||||
"signUp": {
|
||||
"buttonText": "회원가입",
|
||||
"title": "@:appName 에 회원가입",
|
||||
"getStartedText": "시작하기",
|
||||
"emptyPasswordError": "비밀번호는 공백일 수 없습니다",
|
||||
"repeatPasswordEmptyError": "비밀번호 재입력란은 공백일 수 없습니다",
|
||||
"unmatchedPasswordError": "재입력 하신 비밀번호가 같지 않습니다",
|
||||
"alreadyHaveAnAccount": "이미 계정이 있으신가요?",
|
||||
"emailHint": "이메일",
|
||||
"passwordHint": "비밀번호",
|
||||
"repeatPasswordHint": "비밀번호 재입력"
|
||||
},
|
||||
"signIn": {
|
||||
"loginTitle": "@:appName 에 로그인",
|
||||
"loginButtonText": "로그인",
|
||||
"buttonText": "로그인",
|
||||
"forgotPassword": "비밀번호를 잊으셨나요?",
|
||||
"emailHint": "이메일",
|
||||
"passwordHint": "비밀번호",
|
||||
"dontHaveAnAccount": "계정이 없으신가요?",
|
||||
"repeatPasswordEmptyError": "비밀번호 재입력란은 공백일 수 없습니다",
|
||||
"unmatchedPasswordError": "재입력 하신 비밀번호가 같지 않습니다"
|
||||
},
|
||||
"workspace": {
|
||||
"create": "워크스페이스 생성",
|
||||
"hint": "워크스페이스",
|
||||
"notFoundError": "워크스페이스를 찾을 수 없습니다"
|
||||
},
|
||||
"shareAction": {
|
||||
"buttonText": "공유",
|
||||
"workInProgress": "Coming soon",
|
||||
"markdown": "마크다운",
|
||||
"copyLink": "링크 복사"
|
||||
},
|
||||
"disclosureAction": {
|
||||
"rename": "이름변경",
|
||||
"delete": "삭제",
|
||||
"duplicate": "복제"
|
||||
},
|
||||
"blankPageTitle": "빈 페이지",
|
||||
"newPageText": "새로운 페이지",
|
||||
"trash": {
|
||||
"text": "휴지통",
|
||||
"restoreAll": "모두 복구",
|
||||
"deleteAll": "모두 삭제",
|
||||
"pageHeader": {
|
||||
"fileName": "파일 이름",
|
||||
"lastModified": "수정날짜",
|
||||
"created": "생성날짜"
|
||||
}
|
||||
},
|
||||
"deletePagePrompt": {
|
||||
"text": "현재 페이지는 휴지통에 있습니다",
|
||||
"restore": "페이지 복구",
|
||||
"deletePermanent": "영구 삭제"
|
||||
},
|
||||
"dialogCreatePageNameHint": "페이지 이름",
|
||||
"questionBubble": {
|
||||
"whatsNew": "새로운 소식",
|
||||
"help": "도움 및 지원",
|
||||
"debug": {
|
||||
"name": "디버그 정보",
|
||||
"success": "디버그 정보를 클립보드로 복사했습니다.",
|
||||
"fail": "디버그 정보를 클립보드로 복사할 수 없습니다."
|
||||
}
|
||||
},
|
||||
"menuAppHeader": {
|
||||
"addPageTooltip": "하위에 페이지 추가",
|
||||
"defaultNewPageName": "제목없음",
|
||||
"renameDialog": "이름변경"
|
||||
},
|
||||
"toolbar": {
|
||||
"undo": "실행취소",
|
||||
"redo": "재실행",
|
||||
"bold": "굵게",
|
||||
"italic": "기울임꼴",
|
||||
"underline": "밑줄",
|
||||
"strike": "취소선",
|
||||
"numList": "번호 매기기 목록",
|
||||
"bulletList": "글머리 기호 목록",
|
||||
"checkList": "작업 목록",
|
||||
"inlineCode": "인라인 코드",
|
||||
"quote": "인용구 블록",
|
||||
"header": "헤더",
|
||||
"highlight": "하이라이트"
|
||||
},
|
||||
"tooltip": {
|
||||
"lightMode": "라이트 모드로 변경",
|
||||
"darkMode": "다크 모드로 변경",
|
||||
"openAsPage": "페이지로 열기",
|
||||
"addNewRow": "열 추가",
|
||||
"openMenu": "메뉴를 여시려면 클릭하세요"
|
||||
},
|
||||
"sideBar": {
|
||||
"closeSidebar": "사이드바 닫기",
|
||||
"openSidebar": "사이드바 열기"
|
||||
},
|
||||
"notifications": {
|
||||
"export": {
|
||||
"markdown": "마크다운으로 노트를 내보냄",
|
||||
"path": "Documents/flowy"
|
||||
}
|
||||
},
|
||||
"contactsPage": {
|
||||
"title": "연락처",
|
||||
"whatsHappening": "이번주에는 무슨 일이 있나요?",
|
||||
"addContact": "연락처 추가",
|
||||
"editContact": "연락처 편집"
|
||||
},
|
||||
"button": {
|
||||
"OK": "확인",
|
||||
"Cancel": "취소",
|
||||
"signIn": "로그인",
|
||||
"signOut": "로그아웃",
|
||||
"complete": "완료",
|
||||
"save": "저장"
|
||||
},
|
||||
"label": {
|
||||
"welcome": "환영합니다!",
|
||||
"firstName": "이름",
|
||||
"middleName": "중간 이름",
|
||||
"lastName": "성",
|
||||
"stepX": "{X} 단계"
|
||||
},
|
||||
"oAuth": {
|
||||
"err": {
|
||||
"failedTitle": "계정에 연결을 할 수 없습니다.",
|
||||
"failedMsg": "브라우저에서 회원가입이 완료되었는지 확인해주세요."
|
||||
},
|
||||
"google": {
|
||||
"title": "GOOGLE SIGN-IN",
|
||||
"instruction1": "구글 연락처를 가져오기 위해서 웹브라우저로 앱을 승인 해야 합니다.",
|
||||
"instruction2": "아이콘을 클릭 또는 텍스트를 선택해서 이 코드를 클립보드로 복사하세요:",
|
||||
"instruction3": "웹브라우저로 다음 링크로 가셔서 위 코드를 입력해주세요:",
|
||||
"instruction4": "가입 완료 후 아래 버튼을 눌러주세요:"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"title": "설정",
|
||||
"menu": {
|
||||
"appearance": "화면",
|
||||
"language": "언어",
|
||||
"user": "사용자",
|
||||
"open": "설정 열기"
|
||||
},
|
||||
"appearance": {
|
||||
"lightLabel": "라이트 모드",
|
||||
"darkLabel": "다크 모드"
|
||||
}
|
||||
},
|
||||
"grid": {
|
||||
"settings": {
|
||||
"filter": "필터",
|
||||
"sortBy": "정렬 기준",
|
||||
"Properties": "속성",
|
||||
"group": "그룹"
|
||||
},
|
||||
"field": {
|
||||
"hide": "숨기기",
|
||||
"insertLeft": "왼쪽 삽입",
|
||||
"insertRight": "오른쪽 삽입",
|
||||
"duplicate": "복제",
|
||||
"delete": "삭제",
|
||||
"textFieldName": "텍스트",
|
||||
"checkboxFieldName": "체크박스",
|
||||
"dateFieldName": "날짜",
|
||||
"numberFieldName": "숫자",
|
||||
"singleSelectFieldName": "선택",
|
||||
"multiSelectFieldName": "다중선택",
|
||||
"urlFieldName": "링크",
|
||||
"numberFormat": " 숫자 형식",
|
||||
"dateFormat": " 날짜 형식",
|
||||
"includeTime": " 시간 표시",
|
||||
"dateFormatFriendly": "월 일,년",
|
||||
"dateFormatISO": "년-월-일",
|
||||
"dateFormatLocal": "월/일/년",
|
||||
"dateFormatUS": "년/월/일",
|
||||
"timeFormat": " 시간 형식",
|
||||
"invalidTimeFormat": "잘못된 형식",
|
||||
"timeFormatTwelveHour": "12 시간",
|
||||
"timeFormatTwentyFourHour": "24 시간",
|
||||
"addSelectOption": "옵션 추가",
|
||||
"optionTitle": "옵션",
|
||||
"addOption": "옵션 추가",
|
||||
"editProperty": "속성 편집",
|
||||
"newColumn": "열 추가",
|
||||
"deleteFieldPromptMessage": "해당 속성을 삭제 하시겠습니까?"
|
||||
},
|
||||
"row": {
|
||||
"duplicate": "복제",
|
||||
"delete": "삭제",
|
||||
"textPlaceholder": "비어있음",
|
||||
"copyProperty": "속성이 클립보드로 복사됨",
|
||||
"count": "개수",
|
||||
"newRow": "행 추가"
|
||||
},
|
||||
"selectOption": {
|
||||
"create": "생성",
|
||||
"purpleColor": "보라색",
|
||||
"pinkColor": "핑크색",
|
||||
"lightPinkColor": "연한 핑크색",
|
||||
"orangeColor": "오렌지색",
|
||||
"yellowColor": "노랑색",
|
||||
"limeColor": "라임색",
|
||||
"greenColor": "초록색",
|
||||
"aquaColor": "아쿠아색",
|
||||
"blueColor": "파랑색",
|
||||
"deleteTag": "태그 삭제",
|
||||
"colorPanelTitle": "색상",
|
||||
"panelTitle": "옵션 선택 또는 생성",
|
||||
"searchOption": "옵션 검색"
|
||||
},
|
||||
"menuName": "그리드"
|
||||
},
|
||||
"document": {
|
||||
"menuName": "도큐먼트",
|
||||
"date": {
|
||||
"timeHintTextInTwelveHour": "01:00 PM",
|
||||
"timeHintTextInTwentyFourHour": "13:00"
|
||||
}
|
||||
},
|
||||
"board": {
|
||||
"column": {
|
||||
"create_new_card": "추가"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,151 +1,234 @@
|
||||
{
|
||||
"appName": "AppFlowy",
|
||||
"defaultUsername": "Me",
|
||||
"welcomeText": "Bem vindo @:appName",
|
||||
"githubStarText": "Star on GitHub",
|
||||
"subscribeNewsletterText": "Se inscreva na Newsletter",
|
||||
"letsGoButtonText": "Vamos lá",
|
||||
"title": "Título",
|
||||
"signUp": {
|
||||
"buttonText": "Se inscreva",
|
||||
"title": "Se inscreva no @:appName",
|
||||
"getStartedText": "Começar",
|
||||
"emptyPasswordError": "Senha não pode estar em branco.",
|
||||
"repeatPasswordEmptyError": "Confirmar a senha não pode estar em branco.",
|
||||
"unmatchedPasswordError": "As senhas não conferem.",
|
||||
"alreadyHaveAnAccount": "Já possui uma conta?",
|
||||
"emailHint": "Email",
|
||||
"passwordHint": "Senha",
|
||||
"repeatPasswordHint": "Confirme a senha"
|
||||
"appName": "AppFlowy",
|
||||
"defaultUsername": "Eu",
|
||||
"welcomeText": "Bem-vindo @:appName",
|
||||
"githubStarText": "Dar uma estrela no Github",
|
||||
"subscribeNewsletterText": "Inscreva-se para receber novidades",
|
||||
"letsGoButtonText": "Vamos lá",
|
||||
"title": "Título",
|
||||
"signUp": {
|
||||
"buttonText": "Inscreva-se",
|
||||
"title": "Inscreva-se no @:appName",
|
||||
"getStartedText": "Começar",
|
||||
"emptyPasswordError": "Senha não pode estar em branco.",
|
||||
"repeatPasswordEmptyError": "Senha não estar em branco.",
|
||||
"unmatchedPasswordError": "As senhas não conferem.",
|
||||
"alreadyHaveAnAccount": "Já possui uma conta?",
|
||||
"emailHint": "E-mail",
|
||||
"passwordHint": "Dica de senha",
|
||||
"repeatPasswordHint": "Confirme a senha"
|
||||
},
|
||||
"signIn": {
|
||||
"loginTitle": "Conectar-se ao @:appName",
|
||||
"loginButtonText": "Conectar-se",
|
||||
"buttonText": "Entre",
|
||||
"forgotPassword": "Esqueceu sua senha?",
|
||||
"emailHint": "E-mail",
|
||||
"passwordHint": "Senha",
|
||||
"dontHaveAnAccount": "Não possui uma conta?",
|
||||
"repeatPasswordEmptyError": "Senha não pode estar em branco.",
|
||||
"unmatchedPasswordError": "As senhas não conferem."
|
||||
},
|
||||
"workspace": {
|
||||
"create": "Crie uma área de trabalho",
|
||||
"hint": "Espaço de trabalho",
|
||||
"notFoundError": "Espaço de trabalho não encontrada"
|
||||
},
|
||||
"shareAction": {
|
||||
"buttonText": "Compartilhar",
|
||||
"workInProgress": "Em breve",
|
||||
"markdown": "Marcador",
|
||||
"copyLink": "Copiar o link"
|
||||
},
|
||||
"disclosureAction": {
|
||||
"rename": "Renomear",
|
||||
"delete": "Apagar",
|
||||
"duplicate": "Duplicar"
|
||||
},
|
||||
"blankPageTitle": "Página em branco",
|
||||
"newPageText": "Nova página",
|
||||
"trash": {
|
||||
"text": "Lixeira",
|
||||
"restoreAll": "Restaurar tudo",
|
||||
"deleteAll": "Apagar tudo",
|
||||
"pageHeader": {
|
||||
"fileName": "Nome do arquivo",
|
||||
"lastModified": "Última modificação",
|
||||
"created": "Criado"
|
||||
}
|
||||
},
|
||||
"deletePagePrompt": {
|
||||
"text": "Está página está na lixeira",
|
||||
"restore": "Restaurar a página",
|
||||
"deletePermanent": "Apagar permanentemente"
|
||||
},
|
||||
"dialogCreatePageNameHint": "Nome da página",
|
||||
"questionBubble": {
|
||||
"whatsNew": "O que há de novo?",
|
||||
"help": "Ajuda e Suporte",
|
||||
"debug": {
|
||||
"name": "Informação de depuração",
|
||||
"success": "Informação de depuração copiada para a área de transferência!",
|
||||
"fail": "Falha ao copiar a informação de depuração para a área de transferência"
|
||||
}
|
||||
},
|
||||
"menuAppHeader": {
|
||||
"addPageTooltip": "Adicionar uma nova página.",
|
||||
"defaultNewPageName": "Sem título",
|
||||
"renameDialog": "Renomear"
|
||||
},
|
||||
"toolbar": {
|
||||
"undo": "Desfazer",
|
||||
"redo": "Refazer",
|
||||
"bold": "Negrito",
|
||||
"italic": "Itálico",
|
||||
"underline": "Sublinhado",
|
||||
"strike": "Tachado",
|
||||
"numList": "Lista numerada",
|
||||
"bulletList": "Lista com marcadores",
|
||||
"checkList": "Check list",
|
||||
"inlineCode": "Embutir código",
|
||||
"quote": "Citação em bloco",
|
||||
"header": "Cabeçalho",
|
||||
"highlight": "Realçar"
|
||||
},
|
||||
"tooltip": {
|
||||
"lightMode": "Mudar para o modo claro",
|
||||
"darkMode": "Mudar para o modo escuro",
|
||||
"openAsPage": "Abrir como uma página",
|
||||
"addNewRow": "Adicionar uma nova linha",
|
||||
"openMenu": "Clique para abrir o menu"
|
||||
},
|
||||
"sideBar": {
|
||||
"openSidebar": "Abrir barra lateral",
|
||||
"closeSidebar": "Fechar barra lateral"
|
||||
},
|
||||
"notifications": {
|
||||
"export": {
|
||||
"markdown": "Nota exportada como um marcador",
|
||||
"path": "Documentos/flowy"
|
||||
}
|
||||
},
|
||||
"contactsPage": {
|
||||
"title": "Contatos",
|
||||
"whatsHappening": "O que está acontecendo essa semana?",
|
||||
"addContact": "Adicionar um contato",
|
||||
"editContact": "Editar um contato"
|
||||
},
|
||||
"button": {
|
||||
"OK": "Ok",
|
||||
"Cancel": "Cancelar",
|
||||
"signIn": "Conectar",
|
||||
"signOut": "Desconectar",
|
||||
"complete": "Completar",
|
||||
"save": "Salvar"
|
||||
},
|
||||
"label": {
|
||||
"welcome": "Welcome!",
|
||||
"firstName": "Nome",
|
||||
"middleName": "Sobrenome",
|
||||
"lastName": "Último nome",
|
||||
"stepX": "Passo {X}"
|
||||
},
|
||||
"oAuth": {
|
||||
"err": {
|
||||
"failedTitle": "Erro ao conectar a sua conta.",
|
||||
"failedMsg": "Verifique se você concluiu o processo de conexão em seu navegador."
|
||||
},
|
||||
"signIn": {
|
||||
"loginTitle": "Entre no @:appName",
|
||||
"loginButtonText": "Login",
|
||||
"buttonText": "Entre",
|
||||
"forgotPassword": "Esqueceu a senha?",
|
||||
"emailHint": "Email",
|
||||
"passwordHint": "Senha",
|
||||
"dontHaveAnAccount": "Não possui uma conta?",
|
||||
"repeatPasswordEmptyError": "Confirmar a senha não pode estar em branco.",
|
||||
"unmatchedPasswordError": "As senhas não conferem."
|
||||
},
|
||||
"workspace": {
|
||||
"create": "Crie uma área de trabalho",
|
||||
"hint": "área de trabalho",
|
||||
"notFoundError": "Área de trabalho não encontrada"
|
||||
},
|
||||
"shareAction": {
|
||||
"buttonText": "Compartilhar",
|
||||
"workInProgress": "Em breve",
|
||||
"markdown": "Markdown",
|
||||
"copyLink": "Copiar o link"
|
||||
},
|
||||
"disclosureAction": {
|
||||
"rename": "Renomear",
|
||||
"delete": "Deletar",
|
||||
"duplicate": "Duplicar"
|
||||
},
|
||||
"blankPageTitle": "Página em branco",
|
||||
"newPageText": "Nova página",
|
||||
"trash": {
|
||||
"text": "Lixeira",
|
||||
"restoreAll": "Restaurar todos",
|
||||
"deleteAll": "Deletar todos",
|
||||
"pageHeader": {
|
||||
"fileName": "Nome do arquivo",
|
||||
"lastModified": "Última modificação",
|
||||
"created": "Criado"
|
||||
}
|
||||
},
|
||||
"deletePagePrompt": {
|
||||
"text": "Está página está na lixeira",
|
||||
"restore": "Restaurar a oágina",
|
||||
"deletePermanent": "Deletar permanentemente"
|
||||
},
|
||||
"dialogCreatePageNameHint": "Nome da página",
|
||||
"questionBubble": {
|
||||
"whatsNew": "O que há de novo?",
|
||||
"help": "Ajuda & Suporte",
|
||||
"debug": {
|
||||
"name": "Informação de depuração",
|
||||
"success": "Copiar informação de debug para o clipboard!",
|
||||
"fail": "Falha em copiar a informação de debug para o clipboard"
|
||||
}
|
||||
},
|
||||
"menuAppHeader": {
|
||||
"addPageTooltip": "Adicione uma nova página.",
|
||||
"defaultNewPageName": "Sem título",
|
||||
"renameDialog": "Renomear"
|
||||
},
|
||||
"toolbar": {
|
||||
"undo": "Desfazer",
|
||||
"redo": "Refazer",
|
||||
"bold": "Negrito",
|
||||
"italic": "Itálico",
|
||||
"underline": "Sublinhado",
|
||||
"strike": "Tachado",
|
||||
"numList": "Lista numerada",
|
||||
"bulletList": "Lista com marcadores",
|
||||
"checkList": "Check List",
|
||||
"inlineCode": "Embutir código",
|
||||
"quote": "Citação em bloco",
|
||||
"header": "Cabeçalho",
|
||||
"highlight": "Realçar"
|
||||
},
|
||||
"tooltip": {
|
||||
"lightMode": "Mudar para o modo Claro.",
|
||||
"darkMode": "Mudar para o modo Escuro."
|
||||
},
|
||||
"contactsPage": {
|
||||
"title": "Contatos",
|
||||
"whatsHappening": "O que está acontecendo essa semana?",
|
||||
"addContact": "Adicionar um contato",
|
||||
"editContact": "Editar um contato"
|
||||
},
|
||||
"button": {
|
||||
"OK": "OK",
|
||||
"Cancel": "Cancelar",
|
||||
"signIn": "Entrar",
|
||||
"signOut": "Sair",
|
||||
"complete": "Completar",
|
||||
"save": "Salvar"
|
||||
},
|
||||
"label": {
|
||||
"welcome": "Bem vindo!",
|
||||
"firstName": "Primeiro Nome",
|
||||
"middleName": "Nome do Meio",
|
||||
"lastName": "Último Nome",
|
||||
"stepX": "Passo {X}"
|
||||
},
|
||||
"oAuth": {
|
||||
"err": {
|
||||
"failedTitle": "Erro ao conectar a sua conta.",
|
||||
"failedMsg": "Verifique se você concluiu o processo de login em seu navegador."
|
||||
},
|
||||
"google": {
|
||||
"title": "GOOGLE SIGN-IN",
|
||||
"instruction1": "Para importar seus Contatos do Google, você precisará autorizar este aplicativo usando seu navegador web.",
|
||||
"instruction2": "Copie este código para sua área de transferência clicando no ícone ou selecionando o texto:",
|
||||
"instruction3": "Navegue até o link a seguir em seu navegador e digite o código acima:",
|
||||
"instruction4": "Pressione o botão abaixo ao concluir a inscrição:"
|
||||
}
|
||||
"google": {
|
||||
"title": "Conexão com o GOOGLE",
|
||||
"instruction1": "Para importar seus Contatos do Google, você precisará autorizar este aplicativo usando seu navegador.",
|
||||
"instruction2": "Copie este código para sua área de transferência clicando no ícone ou selecionando o texto:",
|
||||
"instruction3": "Navegue até o link a seguir em seu navegador e digite o código acima:",
|
||||
"instruction4": "Pressione o botão abaixo ao concluir a inscrição:"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"title": "Configurações",
|
||||
"menu": {
|
||||
"appearance": "Aparência",
|
||||
"language": "Idioma",
|
||||
"user": "Usuário",
|
||||
"open": "Abrir as Configurações"
|
||||
},
|
||||
"appearance": {
|
||||
"lightLabel": "Modo Claro",
|
||||
"darkLabel": "Modo Escuro"
|
||||
}
|
||||
},
|
||||
"grid": {
|
||||
"settings": {
|
||||
"title": "Configurações",
|
||||
"menu": {
|
||||
"appearance": "Aparência",
|
||||
"language": "Idioma",
|
||||
"open": "Abrir as Configurações"
|
||||
},
|
||||
"appearance": {
|
||||
"lightLabel": "Modo Claro",
|
||||
"darkLabel": "Modo Escuro"
|
||||
}
|
||||
"filter": "Filtro",
|
||||
"sortBy": "Ordenar por",
|
||||
"Properties": "Propriedades",
|
||||
"group": "Grupo"
|
||||
},
|
||||
"sideBar": {
|
||||
"openSidebar": "Open sidebar",
|
||||
"closeSidebar": "Close sidebar"
|
||||
"field": {
|
||||
"hide": "Esconder",
|
||||
"insertLeft": "Inserir à esquerda",
|
||||
"insertRight": "Inserir à direita",
|
||||
"duplicate": "Duplicar",
|
||||
"delete": "Apagar",
|
||||
"textFieldName": "Texto",
|
||||
"checkboxFieldName": "Caixa de seleção",
|
||||
"dateFieldName": "Data",
|
||||
"numberFieldName": "Números",
|
||||
"singleSelectFieldName": "Selecionar",
|
||||
"multiSelectFieldName": "Seleção múltipla",
|
||||
"urlFieldName": "URL",
|
||||
"numberFormat": "Formato numérico",
|
||||
"dateFormat": "Formato de data",
|
||||
"includeTime": "Incluir horário",
|
||||
"dateFormatFriendly": "Mês/Dia/Ano",
|
||||
"dateFormatISO": "Ano/Mês/Dia",
|
||||
"dateFormatLocal": "Mês/Dia/Ano",
|
||||
"dateFormatUS": "Ano/Mês/Dia",
|
||||
"timeFormat": "Formato de hora",
|
||||
"invalidTimeFormat": "Formato Inválido",
|
||||
"timeFormatTwelveHour": "12 horas",
|
||||
"timeFormatTwentyFourHour": "24 horas",
|
||||
"addSelectOption": "Adicionar uma opção",
|
||||
"optionTitle": "Opções",
|
||||
"addOption": "Adicionar opção",
|
||||
"editProperty": "Editar propriedade",
|
||||
"newColumn": "Nova coluna",
|
||||
"deleteFieldPromptMessage": "Tem certeza? Esta propriedade será excluída"
|
||||
},
|
||||
"row": {
|
||||
"duplicate": "Duplicar",
|
||||
"delete": "Apagar",
|
||||
"textPlaceholder": "Vazio",
|
||||
"copyProperty": "Propriedade copiada para a área de transferência",
|
||||
"count": "Contagem"
|
||||
},
|
||||
"selectOption": {
|
||||
"create": "Criar",
|
||||
"purpleColor": "Roxo",
|
||||
"pinkColor": "Rosa",
|
||||
"lightPinkColor": "Rosa claro",
|
||||
"orangeColor": "Laranja",
|
||||
"yellowColor": "Amarelo",
|
||||
"limeColor": "Verde limão",
|
||||
"greenColor": "Verde",
|
||||
"aquaColor": "Áqua",
|
||||
"blueColor": "Azul",
|
||||
"deleteTag": "Apagar etiqueta",
|
||||
"colorPannelTitle": "Cores",
|
||||
"pannelTitle": "Escolha uma opção ou crie uma",
|
||||
"searchOption": "Procure uma opção"
|
||||
},
|
||||
"menuName": "Grade"
|
||||
},
|
||||
"document": {
|
||||
"menuName": "Documento",
|
||||
"date": {
|
||||
"timeHintTextInTwelveHour": "01:00 PM",
|
||||
"timeHintTextInTwentyFourHour": "13:00"
|
||||
}
|
||||
},
|
||||
"board": {
|
||||
"column": {
|
||||
"create_new_card": "Novo"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,212 +1,233 @@
|
||||
{
|
||||
"appName": "AppFlowy",
|
||||
"defaultUsername": "Я",
|
||||
"welcomeText": "Добро пожаловать в @:appName",
|
||||
"githubStarText": "Поставить звезду на GitHub",
|
||||
"subscribeNewsletterText": "Подписаться на рассылку",
|
||||
"letsGoButtonText": "Начнём",
|
||||
"title": "Заголовок",
|
||||
"signUp": {
|
||||
"buttonText": "Зарегистрироваться",
|
||||
"title": "Регистрация в @:appName",
|
||||
"getStartedText": "Начать",
|
||||
"emptyPasswordError": "Пароль не может быть пустым",
|
||||
"repeatPasswordEmptyError": "Повтор пароля не может быть пустым",
|
||||
"unmatchedPasswordError": "Пароли не совпадают",
|
||||
"alreadyHaveAnAccount": "Уже есть аккаунт?",
|
||||
"emailHint": "Электронная почта",
|
||||
"passwordHint": "Пароль",
|
||||
"repeatPasswordHint": "Повторите пароль"
|
||||
"appName": "AppFlowy",
|
||||
"defaultUsername": "Я",
|
||||
"welcomeText": "Добро пожаловать в @:appName",
|
||||
"githubStarText": "Поставить звезду на GitHub",
|
||||
"subscribeNewsletterText": "Подписаться на рассылку",
|
||||
"letsGoButtonText": "Начнём",
|
||||
"title": "Заголовок",
|
||||
"signUp": {
|
||||
"buttonText": "Зарегистрироваться",
|
||||
"title": "Регистрация в @:appName",
|
||||
"getStartedText": "Начать",
|
||||
"emptyPasswordError": "Пароль не может быть пустым",
|
||||
"repeatPasswordEmptyError": "Повтор пароля не может быть пустым",
|
||||
"unmatchedPasswordError": "Пароли не совпадают",
|
||||
"alreadyHaveAnAccount": "Уже есть аккаунт?",
|
||||
"emailHint": "Электронная почта",
|
||||
"passwordHint": "Пароль",
|
||||
"repeatPasswordHint": "Повторите пароль"
|
||||
},
|
||||
"signIn": {
|
||||
"loginTitle": "Войти в @:appName",
|
||||
"loginButtonText": "Войти",
|
||||
"buttonText": "Авторизация",
|
||||
"forgotPassword": "Забыли пароль?",
|
||||
"emailHint": "Электронная почта",
|
||||
"passwordHint": "Пароль",
|
||||
"dontHaveAnAccount": "Нет аккаунта?",
|
||||
"repeatPasswordEmptyError": "Повтор пароля не может быть пустым",
|
||||
"unmatchedPasswordError": "Пароли не совпадают"
|
||||
},
|
||||
"workspace": {
|
||||
"create": "Создать рабочее пространство",
|
||||
"hint": "рабочее пространство",
|
||||
"notFoundError": "Нет такого рабочего пространства"
|
||||
},
|
||||
"shareAction": {
|
||||
"buttonText": "Поделиться",
|
||||
"workInProgress": "В разработке",
|
||||
"markdown": "Markdown",
|
||||
"copyLink": "Скопировать ссылку"
|
||||
},
|
||||
"disclosureAction": {
|
||||
"rename": "Переименовать",
|
||||
"delete": "Удалить",
|
||||
"duplicate": "Дублировать"
|
||||
},
|
||||
"blankPageTitle": "Пустая страница",
|
||||
"newPageText": "Новая страница",
|
||||
"trash": {
|
||||
"text": "Корзина",
|
||||
"restoreAll": "Восстановить всё",
|
||||
"deleteAll": "Очистить",
|
||||
"pageHeader": {
|
||||
"fileName": "Имя",
|
||||
"lastModified": "Последнее изменение",
|
||||
"created": "Создан"
|
||||
}
|
||||
},
|
||||
"deletePagePrompt": {
|
||||
"text": "Эта страница в Корзине",
|
||||
"restore": "Восстановить страницу",
|
||||
"deletePermanent": "Удалить навсегда"
|
||||
},
|
||||
"dialogCreatePageNameHint": "Имя",
|
||||
"questionBubble": {
|
||||
"whatsNew": "Что нового?",
|
||||
"help": "Помощь",
|
||||
"debug": {
|
||||
"name": "Отладочная информация",
|
||||
"success": "Скопировано в буфер обмена!",
|
||||
"fail": "Не получилось скопировать"
|
||||
}
|
||||
},
|
||||
"menuAppHeader": {
|
||||
"addPageTooltip": "Быстро добавить новую страницу",
|
||||
"defaultNewPageName": "Без заголовка",
|
||||
"renameDialog": "Переименовать"
|
||||
},
|
||||
"toolbar": {
|
||||
"undo": "Отменить",
|
||||
"redo": "Повторить",
|
||||
"bold": "Жирный",
|
||||
"italic": "Курсив",
|
||||
"underline": "Подчёркнутый",
|
||||
"strike": "Зачёркнутый",
|
||||
"numList": "Нумерованный список",
|
||||
"bulletList": "Маркированный список",
|
||||
"checkList": "Список To-Do",
|
||||
"inlineCode": "Код",
|
||||
"quote": "Цитата",
|
||||
"header": "Заголовок",
|
||||
"highlight": "Выделение"
|
||||
},
|
||||
"tooltip": {
|
||||
"darkMode": "Переключиться в тёмную тему",
|
||||
"openAsPage": "Открыть как страницу",
|
||||
"addNewRow": "Добавить новую строку",
|
||||
"openMenu": "Открыть меню"
|
||||
},
|
||||
"sideBar": {
|
||||
"closeSidebar": "Закрыть боковое меню",
|
||||
"openSidebar": "Открыть боковое меню"
|
||||
},
|
||||
"notifications": {
|
||||
"export": {
|
||||
"markdown": "Заметка экспортирована в Markdown",
|
||||
"path": "Документы/flowy"
|
||||
}
|
||||
},
|
||||
"contactsPage": {
|
||||
"title": "Контакты",
|
||||
"whatsHappening": "Что происходит на этой неделе?",
|
||||
"addContact": "Новый контакт",
|
||||
"editContact": "Редактировать"
|
||||
},
|
||||
"button": {
|
||||
"OK": "OK",
|
||||
"Cancel": "Отмена",
|
||||
"signIn": "Войти",
|
||||
"signOut": "Выйти",
|
||||
"complete": "Завершить",
|
||||
"save": "Сохранить"
|
||||
},
|
||||
"label": {
|
||||
"welcome": "Добро пожаловать!",
|
||||
"firstName": "Имя",
|
||||
"middleName": "Отчество",
|
||||
"lastName": "Фамилия",
|
||||
"stepX": "Этап {X}"
|
||||
},
|
||||
"oAuth": {
|
||||
"err": {
|
||||
"failedTitle": "Ошибка подключения к аккаунту.",
|
||||
"failedMsg": "Убедитесь, что вы завершили вход в своём браузере."
|
||||
},
|
||||
"signIn": {
|
||||
"loginTitle": "Войти в @:appName",
|
||||
"loginButtonText": "Войти",
|
||||
"buttonText": "Авторизация",
|
||||
"forgotPassword": "Забыли пароль?",
|
||||
"emailHint": "Электронная почта",
|
||||
"passwordHint": "Пароль",
|
||||
"dontHaveAnAccount": "Нет аккаунта?",
|
||||
"repeatPasswordEmptyError": "Повтор пароля не может быть пустым",
|
||||
"unmatchedPasswordError": "Пароли не совпадают"
|
||||
},
|
||||
"workspace": {
|
||||
"create": "Создать рабочее пространство",
|
||||
"hint": "рабочее пространство",
|
||||
"notFoundError": "Нет такого рабочего пространства"
|
||||
},
|
||||
"shareAction": {
|
||||
"buttonText": "Поделиться",
|
||||
"workInProgress": "В разработке",
|
||||
"markdown": "Markdown",
|
||||
"copyLink": "Скопировать ссылку"
|
||||
},
|
||||
"disclosureAction": {
|
||||
"rename": "Переименовать",
|
||||
"delete": "Удалить",
|
||||
"duplicate": "Дублировать"
|
||||
},
|
||||
"blankPageTitle": "Пустая страница",
|
||||
"newPageText": "Новая страница",
|
||||
"trash": {
|
||||
"text": "Корзина",
|
||||
"restoreAll": "Восстановить всё",
|
||||
"deleteAll": "Очистить",
|
||||
"pageHeader": {
|
||||
"fileName": "Имя",
|
||||
"lastModified": "Последнее изменение",
|
||||
"created": "Создан"
|
||||
}
|
||||
},
|
||||
"deletePagePrompt": {
|
||||
"text": "Эта страница в Корзине",
|
||||
"restore": "Восстановить страницу",
|
||||
"deletePermanent": "Удалить навсегда"
|
||||
},
|
||||
"dialogCreatePageNameHint": "Имя",
|
||||
"questionBubble": {
|
||||
"whatsNew": "Что нового?",
|
||||
"help": "Помощь",
|
||||
"debug": {
|
||||
"name": "Отладочная информация",
|
||||
"success": "Скопировано в буфер обмена!",
|
||||
"fail": "Не получилось скопировать"
|
||||
}
|
||||
},
|
||||
"menuAppHeader": {
|
||||
"addPageTooltip": "Быстро добавить новую страницу",
|
||||
"defaultNewPageName": "Без заголовка",
|
||||
"renameDialog": "Переименовать"
|
||||
},
|
||||
"toolbar": {
|
||||
"undo": "Отменить",
|
||||
"redo": "Повторить",
|
||||
"bold": "Жирный",
|
||||
"italic": "Курсив",
|
||||
"underline": "Подчёркнутый",
|
||||
"strike": "Зачёркнутый",
|
||||
"numList": "Нумерованный список",
|
||||
"bulletList": "Маркированный список",
|
||||
"checkList": "Список To-Do",
|
||||
"inlineCode": "Код",
|
||||
"quote": "Цитата",
|
||||
"header": "Заголовок",
|
||||
"highlight": "Выделение"
|
||||
},
|
||||
"tooltip": {
|
||||
"lightMode": "Переключиться в светлую тему",
|
||||
"darkMode": "Переключиться в тёмную тему"
|
||||
},
|
||||
"contactsPage": {
|
||||
"title": "Контакты",
|
||||
"whatsHappening": "Что происходит на этой неделе?",
|
||||
"addContact": "Новый контакт",
|
||||
"editContact": "Редактировать"
|
||||
},
|
||||
"button": {
|
||||
"OK": "OK",
|
||||
"Cancel": "Отмена",
|
||||
"signIn": "Войти",
|
||||
"signOut": "Выйти",
|
||||
"complete": "Завершить",
|
||||
"save": "Сохранить"
|
||||
},
|
||||
"label": {
|
||||
"welcome": "Добро пожаловать!",
|
||||
"firstName": "Имя",
|
||||
"middleName": "Отчество",
|
||||
"lastName": "Фамилия",
|
||||
"stepX": "Этап {X}"
|
||||
},
|
||||
"oAuth": {
|
||||
"err": {
|
||||
"failedTitle": "Ошибка подключения к аккаунту.",
|
||||
"failedMsg": "Убедитесь, что вы завершили вход в своём браузере."
|
||||
},
|
||||
"google": {
|
||||
"title": "Вход через Google",
|
||||
"instruction1": "Чтобы импортировать ваши Google Контакты, вам нужно будет авторизовать приложение через браузер.",
|
||||
"instruction2": "Скопируйте этот код в буфер обмена (нажав кнопку или выделив текст):",
|
||||
"instruction3": "Пройдите по ссылке и введите этот код:",
|
||||
"instruction4": "Нажмите на кнопку, когда завершите вход:"
|
||||
}
|
||||
"google": {
|
||||
"title": "Вход через Google",
|
||||
"instruction1": "Чтобы импортировать ваши Google Контакты, вам нужно будет авторизовать приложение через браузер.",
|
||||
"instruction2": "Скопируйте этот код в буфер обмена (нажав кнопку или выделив текст):",
|
||||
"instruction3": "Пройдите по ссылке и введите этот код:",
|
||||
"instruction4": "Нажмите на кнопку ниже, когда завершите вход:"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"title": "Настройки",
|
||||
"menu": {
|
||||
"appearance": "Внешний вид",
|
||||
"language": "Язык",
|
||||
"user": "Пользователь",
|
||||
"open": "Открыть настройки"
|
||||
},
|
||||
"appearance": {
|
||||
"lightLabel": "Светлая",
|
||||
"darkLabel": "Тёмная"
|
||||
}
|
||||
},
|
||||
"grid": {
|
||||
"settings": {
|
||||
"title": "Настройки",
|
||||
"menu": {
|
||||
"appearance": "Внешний вид",
|
||||
"language": "Язык",
|
||||
"open": "Открыть настройки"
|
||||
},
|
||||
"appearance": {
|
||||
"lightLabel": "Светлая тема",
|
||||
"darkLabel": "Тёмная тема"
|
||||
}
|
||||
"filter": "Фильтр",
|
||||
"sortBy": "Сортировать",
|
||||
"Properties": "Свойства",
|
||||
"group": "Группировать"
|
||||
},
|
||||
"grid": {
|
||||
"settings": {
|
||||
"filter": "Фильтр",
|
||||
"sortBy": "Сортировать",
|
||||
"Properties": "Свойства"
|
||||
},
|
||||
"field": {
|
||||
"hide": "Скрыть",
|
||||
"insertLeft": "Вставить слева",
|
||||
"insertRight": "Вставить справа",
|
||||
"duplicate": "Дублировать",
|
||||
"delete": "Удалить",
|
||||
"textFieldName": "Текст",
|
||||
"checkboxFieldName": "Checkbox",
|
||||
"dateFieldName": "Дата",
|
||||
"numberFieldName": "Число",
|
||||
"singleSelectFieldName": "Выбор",
|
||||
"multiSelectFieldName": "Выбор многих",
|
||||
"urlFieldName": "URL",
|
||||
"numberFormat": " Формат числа",
|
||||
"dateFormat": " Формат даты",
|
||||
"includeTime": " Время",
|
||||
"dateFormatFriendly": "День Месяц, Год",
|
||||
"dateFormatISO": "Год-Месяц-День",
|
||||
"dateFormatLocal": "Год/Месяц/День",
|
||||
"dateFormatUS": "Год/Месяц/День",
|
||||
"timeFormat": " Форматировать время",
|
||||
"invalidTimeFormat": "Неверный формат",
|
||||
"timeFormatTwelveHour": "12 часов",
|
||||
"timeFormatTwentyFourHour": "24 часа",
|
||||
"addSelectOption": "Добавить вариант",
|
||||
"optionTitle": "Варианты",
|
||||
"addOption": "Добавить",
|
||||
"editProperty": "Редактировать свойство"
|
||||
},
|
||||
"row": {
|
||||
"duplicate": "Дублировать",
|
||||
"delete": "Удалить",
|
||||
"textPlaceholder": "Пусто",
|
||||
"copyProperty": "Свойство скопировано"
|
||||
},
|
||||
"selectOption": {
|
||||
"create": "Создать",
|
||||
"purpleColor": "Фиолетовый",
|
||||
"pinkColor": "Розовый",
|
||||
"lightPinkColor": "Светло-розовый",
|
||||
"orangeColor": "Оранжевый",
|
||||
"yellowColor": "Желтый",
|
||||
"limeColor": "Ярко-зелёный",
|
||||
"greenColor": "Зелёный",
|
||||
"aquaColor": "Морской волны",
|
||||
"blueColor": "Синий",
|
||||
"deleteTag": "Удалить вариант",
|
||||
"colorPannelTitle": "Цвета",
|
||||
"pannelTitle": "Выберите или создайте вариант",
|
||||
"searchOption": "Поиск"
|
||||
},
|
||||
"date": {
|
||||
"timeHintTextInTwelveHour": "12:00 AM",
|
||||
"timeHintTextInTwentyFourHour": "12:00"
|
||||
}
|
||||
"field": {
|
||||
"hide": "Скрыть",
|
||||
"insertLeft": "Вставить слева",
|
||||
"insertRight": "Вставить справа",
|
||||
"duplicate": "Дублировать",
|
||||
"delete": "Удалить",
|
||||
"textFieldName": "Текст",
|
||||
"checkboxFieldName": "Чекбокс",
|
||||
"dateFieldName": "Дата",
|
||||
"numberFieldName": "Число",
|
||||
"singleSelectFieldName": "Выбор",
|
||||
"multiSelectFieldName": "Выбор нескольких",
|
||||
"urlFieldName": "URL",
|
||||
"numberFormat": " Формат числа",
|
||||
"dateFormat": " Формат даты",
|
||||
"includeTime": " Время",
|
||||
"dateFormatFriendly": "День Месяц, Год",
|
||||
"dateFormatISO": "Год-Месяц-День",
|
||||
"dateFormatLocal": "Месяц/День/Год",
|
||||
"dateFormatUS": "Год/Месяц/День",
|
||||
"timeFormat": " Форматировать время",
|
||||
"invalidTimeFormat": "Неверный формат",
|
||||
"timeFormatTwelveHour": "12 часов",
|
||||
"timeFormatTwentyFourHour": "24 часа",
|
||||
"addSelectOption": "Добавить вариант",
|
||||
"optionTitle": "Варианты",
|
||||
"addOption": "Добавить",
|
||||
"editProperty": "Редактировать свойство",
|
||||
"newColumn": "Добавить колонку",
|
||||
"deleteFieldPromptMessage": "Вы уверены? Свойство будет удалено"
|
||||
},
|
||||
"sideBar": {
|
||||
"openSidebar": "Open sidebar",
|
||||
"closeSidebar": "Close sidebar"
|
||||
"row": {
|
||||
"duplicate": "Дублировать",
|
||||
"delete": "Удалить",
|
||||
"textPlaceholder": "Пусто",
|
||||
"copyProperty": "Свойство скопировано",
|
||||
"count": "Количество"
|
||||
},
|
||||
"selectOption": {
|
||||
"create": "Создать",
|
||||
"purpleColor": "Фиолетовый",
|
||||
"pinkColor": "Розовый",
|
||||
"lightPinkColor": "Светло-розовый",
|
||||
"orangeColor": "Оранжевый",
|
||||
"yellowColor": "Желтый",
|
||||
"limeColor": "Ярко-зелёный",
|
||||
"greenColor": "Зелёный",
|
||||
"aquaColor": "Бирюзовый",
|
||||
"blueColor": "Синий",
|
||||
"deleteTag": "Удалить вариант",
|
||||
"colorPanelTitle": "Цвета",
|
||||
"panelTitle": "Выберите или создайте вариант",
|
||||
"searchOption": "Поиск"
|
||||
},
|
||||
"menuName": "Сетка"
|
||||
},
|
||||
"document": {
|
||||
"menuName": "Документ",
|
||||
"date": {
|
||||
"timeHintTextInTwelveHour": "01:00 PM",
|
||||
"timeHintTextInTwentyFourHour": "13:00"
|
||||
}
|
||||
},
|
||||
"board": {
|
||||
"column": {
|
||||
"create_new_card": "Создать"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
{
|
||||
"appName": "AppFlowy",
|
||||
"defaultUsername": "Jag",
|
||||
"welcomeText": "Välkommen till @:appName",
|
||||
"githubStarText": "Stärnmärk på GitHub",
|
||||
"subscribeNewsletterText": "Prenumerera på nyhetsbrev",
|
||||
"letsGoButtonText": "Kör igång",
|
||||
"title": "Namn",
|
||||
"signUp": {
|
||||
"buttonText": "Registrera dig",
|
||||
"title": "Registrera dig på @:appName",
|
||||
"getStartedText": "Sätt igång",
|
||||
"emptyPasswordError": "Lösenordet kan inte vara tomt",
|
||||
"repeatPasswordEmptyError": "Upprepat lösenord kan inte vara tomt",
|
||||
"unmatchedPasswordError": "Upprepat lösenord är inte samma som det första",
|
||||
"alreadyHaveAnAccount": "Har du redan ett konto?",
|
||||
"emailHint": "E-post",
|
||||
"passwordHint": "Lösenord",
|
||||
"repeatPasswordHint": "Uprepa lösenordet"
|
||||
},
|
||||
"signIn": {
|
||||
"loginTitle": "Logga in till @:appName",
|
||||
"loginButtonText": "Logga in",
|
||||
"buttonText": "Registrering",
|
||||
"forgotPassword": "Glömt lösenordet?",
|
||||
"emailHint": "E-post",
|
||||
"passwordHint": "Lösenord",
|
||||
"dontHaveAnAccount": "Har du inget konto?",
|
||||
"repeatPasswordEmptyError": "Upprepat lösenord kan inte vara tomt",
|
||||
"unmatchedPasswordError": "Upprepat lösenord är inte samma som det första"
|
||||
},
|
||||
"workspace": {
|
||||
"create": "Skapa arbetsyta",
|
||||
"hint": "Arbetsyta",
|
||||
"notFoundError": "Hittade ingen arbetsyta"
|
||||
},
|
||||
"shareAction": {
|
||||
"buttonText": "Dela",
|
||||
"workInProgress": "Kommer snart",
|
||||
"markdown": "Markdown",
|
||||
"copyLink": "Kopiera länk"
|
||||
},
|
||||
"disclosureAction": {
|
||||
"rename": "Byt namn",
|
||||
"delete": "Ta bort",
|
||||
"duplicate": "Klona"
|
||||
},
|
||||
"blankPageTitle": "Tom sida",
|
||||
"newPageText": "Ny sida",
|
||||
"trash": {
|
||||
"text": "Skräp",
|
||||
"restoreAll": "Återställ alla",
|
||||
"deleteAll": "Ta bort alla",
|
||||
"pageHeader": {
|
||||
"fileName": "Filnamn",
|
||||
"lastModified": "Ändrad",
|
||||
"created": "Skapad"
|
||||
}
|
||||
},
|
||||
"deletePagePrompt": {
|
||||
"text": "Denna sida är i skräpmappen",
|
||||
"restore": "Återställ sida",
|
||||
"deletePermanent": "Radera permanent"
|
||||
},
|
||||
"dialogCreatePageNameHint": "Sidnamn",
|
||||
"questionBubble": {
|
||||
"whatsNew": "Vad nytt?",
|
||||
"help": "Hjälp & Support",
|
||||
"debug": {
|
||||
"name": "Felsökningsinfo",
|
||||
"success": "Kopierade felsökningsinfo till urklipp!",
|
||||
"fail": "Kunde inte kopiera felsökningsinfo till urklipp"
|
||||
}
|
||||
},
|
||||
"menuAppHeader": {
|
||||
"addPageTooltip": "Lägg snabbt till en sida inuti",
|
||||
"defaultNewPageName": "Namnlös",
|
||||
"renameDialog": "Byt namn"
|
||||
},
|
||||
"toolbar": {
|
||||
"undo": "Ångra",
|
||||
"redo": "Upprepa",
|
||||
"bold": "Fet",
|
||||
"italic": "Kursiv",
|
||||
"underline": "Understruken",
|
||||
"strike": "Genomstruken",
|
||||
"numList": "Numrerad lista",
|
||||
"bulletList": "Punktlista",
|
||||
"checkList": "Checklista",
|
||||
"inlineCode": "Infogad kod",
|
||||
"quote": "Citatblock",
|
||||
"header": "Rubrik",
|
||||
"highlight": "Färgmarkera"
|
||||
},
|
||||
"tooltip": {
|
||||
"lightMode": "Växla till ljust läge",
|
||||
"darkMode": "Växla till mörkt läge",
|
||||
"openAsPage": "Öppna som sida",
|
||||
"addNewRow": "Lägg till ny rad",
|
||||
"openMenu": "Klicka för att öppna meny"
|
||||
},
|
||||
"sideBar": {
|
||||
"closeSidebar": "Stäng sidofältet",
|
||||
"openSidebar": "Öppna sidofältet"
|
||||
},
|
||||
"notifications": {
|
||||
"export": {
|
||||
"markdown": "Exporterade anteckning till Markdown",
|
||||
"path": "Dokument/flowy"
|
||||
}
|
||||
},
|
||||
"contactsPage": {
|
||||
"title": "Kontakter",
|
||||
"whatsHappening": "Vad händer denna vecka?",
|
||||
"addContact": "Lägg till kontakt",
|
||||
"editContact": "Redigera kontakt"
|
||||
},
|
||||
"button": {
|
||||
"OK": "OK",
|
||||
"Cancel": "Avbryt",
|
||||
"signIn": "Logga in",
|
||||
"signOut": "Logga ut",
|
||||
"complete": "Slutfört",
|
||||
"save": "Spara"
|
||||
},
|
||||
"label": {
|
||||
"welcome": "Välkommen!",
|
||||
"firstName": "Förnamn",
|
||||
"middleName": "Mellannamn",
|
||||
"lastName": "Efternamn",
|
||||
"stepX": "Steg {X}"
|
||||
},
|
||||
"oAuth": {
|
||||
"err": {
|
||||
"failedTitle": "Kan inte ansluta till ditt konto.",
|
||||
"failedMsg": "Tillse att du har slutfört registreringsprocessen i din webbläsare."
|
||||
},
|
||||
"google": {
|
||||
"title": "GOOGLE-inloggning",
|
||||
"instruction1": "För att kunna importera dina Google-kontakter, måste du auktorisera detta program med hjälp av din webbläsare.",
|
||||
"instruction2": "Kopiera den här koden till urklipp genom att klicka på ikonen eller genom att markera texten:",
|
||||
"instruction3": "Gå till följande länk i din webbläsare, och ange ovanstående kod:",
|
||||
"instruction4": "Tryck på nedanstående knapp när du slutfört registreringen:"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"title": "Inställningar",
|
||||
"menu": {
|
||||
"appearance": "Utseende",
|
||||
"language": "Språk",
|
||||
"user": "Användare",
|
||||
"open": "Öppna inställningarna"
|
||||
},
|
||||
"appearance": {
|
||||
"lightLabel": "Ljust läge",
|
||||
"darkLabel": "Mörkt läge"
|
||||
}
|
||||
},
|
||||
"grid": {
|
||||
"settings": {
|
||||
"filter": "Filter",
|
||||
"sortBy": "Sortera efter",
|
||||
"Properties": "Egenskaper",
|
||||
"group": "Grupp"
|
||||
},
|
||||
"field": {
|
||||
"hide": "Dölj",
|
||||
"insertLeft": "Infoga till vänster",
|
||||
"insertRight": "Infoga till höger",
|
||||
"duplicate": "Klona",
|
||||
"delete": "Ta bort",
|
||||
"textFieldName": "Text",
|
||||
"checkboxFieldName": "Checkruta",
|
||||
"dateFieldName": "Datum",
|
||||
"numberFieldName": "Siffror",
|
||||
"singleSelectFieldName": "Välj",
|
||||
"multiSelectFieldName": "Välj flera",
|
||||
"urlFieldName": "URL",
|
||||
"numberFormat": " Sifferformat",
|
||||
"dateFormat": " Datumformat",
|
||||
"includeTime": " Inkludera tid",
|
||||
"dateFormatFriendly": "Månad Dag,År",
|
||||
"dateFormatISO": "År-Månad-Dag",
|
||||
"dateFormatLocal": "Månad/Dag/År",
|
||||
"dateFormatUS": "År/Månad/Dag",
|
||||
"timeFormat": " Tidsformat",
|
||||
"invalidTimeFormat": "Ogiltigt format",
|
||||
"timeFormatTwelveHour": "12-timmars",
|
||||
"timeFormatTwentyFourHour": "24-timmars",
|
||||
"addSelectOption": "Lägg till ett alternativ",
|
||||
"optionTitle": "Alternativ",
|
||||
"addOption": "Lägg till alternativ",
|
||||
"editProperty": "Redigera egenskap",
|
||||
"newColumn": "Ny kolumn",
|
||||
"deleteFieldPromptMessage": "Är du säker? Denna egenskap kommer att raderas."
|
||||
},
|
||||
"row": {
|
||||
"duplicate": "Klona",
|
||||
"delete": "Ta bort",
|
||||
"textPlaceholder": "Tom",
|
||||
"copyProperty": "Kopierade egenskap till urklipp",
|
||||
"count": "Antal",
|
||||
"newRow": "Ny rad"
|
||||
},
|
||||
"selectOption": {
|
||||
"create": "Skapa",
|
||||
"purpleColor": "Purpur",
|
||||
"pinkColor": "Rosa",
|
||||
"lightPinkColor": "Ljusrosa",
|
||||
"orangeColor": "Orange",
|
||||
"yellowColor": "Gul",
|
||||
"limeColor": "Lime",
|
||||
"greenColor": "Grön",
|
||||
"aquaColor": "Vatten",
|
||||
"blueColor": "Blå",
|
||||
"deleteTag": "Ta bort tagg",
|
||||
"colorPanelTitle": "Färger",
|
||||
"panelTitle": "Välj ett alternativ eller skapa ett",
|
||||
"searchOption": "Sök efter ett alternativ"
|
||||
},
|
||||
"menuName": "Tabell"
|
||||
},
|
||||
"document": {
|
||||
"menuName": "Dokument",
|
||||
"date": {
|
||||
"timeHintTextInTwelveHour": "01:00 PM",
|
||||
"timeHintTextInTwentyFourHour": "13:00"
|
||||
}
|
||||
},
|
||||
"board": {
|
||||
"column": {
|
||||
"create_new_card": "Nytt"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -94,7 +94,14 @@
|
||||
},
|
||||
"tooltip": {
|
||||
"lightMode": "切换到亮色模式",
|
||||
"darkMode": "切换到暗色模式"
|
||||
"darkMode": "切换到暗色模式",
|
||||
"openAsPage": "作为页面打开",
|
||||
"addNewRow": "增加一行",
|
||||
"openMenu": "点击打开菜单"
|
||||
},
|
||||
"sideBar": {
|
||||
"openSidebar": "打开侧边栏",
|
||||
"closeSidebar": "关闭侧边栏"
|
||||
},
|
||||
"notifications": {
|
||||
"export": {
|
||||
@@ -149,15 +156,12 @@
|
||||
"darkLabel": "夜间模式"
|
||||
}
|
||||
},
|
||||
"sideBar": {
|
||||
"openSidebar": "打开侧边栏",
|
||||
"closeSidebar": "关闭侧边栏"
|
||||
},
|
||||
"grid": {
|
||||
"settings": {
|
||||
"filter": "过滤器",
|
||||
"sortBy": "排序",
|
||||
"Properties": "属性"
|
||||
"Properties": "属性",
|
||||
"group": "组"
|
||||
},
|
||||
"field": {
|
||||
"hide": "隐藏",
|
||||
@@ -177,7 +181,7 @@
|
||||
"includeTime": " 包含时间",
|
||||
"dateFormatFriendly": "月 日,年",
|
||||
"dateFormatISO": "年-月-日",
|
||||
"dateFormatLocal": "年/月/日",
|
||||
"dateFormatLocal": "月/日/年",
|
||||
"dateFormatUS": "年/月/日",
|
||||
"timeFormat": " 时间格式",
|
||||
"invalidTimeFormat": "时间格式错误",
|
||||
@@ -186,13 +190,17 @@
|
||||
"addSelectOption": "添加一个标签",
|
||||
"optionTitle": "标签",
|
||||
"addOption": "添加标签",
|
||||
"editProperty": "编辑列属性"
|
||||
"editProperty": "编辑列属性",
|
||||
"newColumn": "增加一列",
|
||||
"deleteFieldPromptMessage": "确定要删除这个属性吗? "
|
||||
},
|
||||
"row": {
|
||||
"duplicate": "复制",
|
||||
"delete": "删除",
|
||||
"textPlaceholder": "空",
|
||||
"copyProperty": "复制列"
|
||||
"copyProperty": "复制列",
|
||||
"count": "数量",
|
||||
"newRow": "添加一行"
|
||||
},
|
||||
"selectOption": {
|
||||
"create": "新建",
|
||||
@@ -206,8 +214,8 @@
|
||||
"aquaColor": "水蓝色",
|
||||
"blueColor": "蓝色",
|
||||
"deleteTag": "删除标签",
|
||||
"colorPannelTitle": "颜色",
|
||||
"pannelTitle": "选择或新建一个标签",
|
||||
"colorPanelTitle": "颜色",
|
||||
"panelTitle": "选择或新建一个标签",
|
||||
"searchOption": "搜索标签"
|
||||
},
|
||||
"menuName": "网格"
|
||||
@@ -215,8 +223,14 @@
|
||||
"document": {
|
||||
"menuName": "文档",
|
||||
"date": {
|
||||
"timeHintTextInTwelveHour": "12:00 AM",
|
||||
"timeHintTextInTwentyFourHour": "12:00"
|
||||
"timeHintTextInTwelveHour": "01:00 PM",
|
||||
"timeHintTextInTwentyFourHour": "13:00"
|
||||
}
|
||||
},
|
||||
"board": {
|
||||
"column": {
|
||||
"create_new_card": "新建"
|
||||
},
|
||||
"menuName": "看板"
|
||||
}
|
||||
}
|
||||
@@ -173,7 +173,7 @@
|
||||
"includeTime": " 包含時間",
|
||||
"dateFormatFriendly": "月 日,年",
|
||||
"dateFormatISO": "年-月-日",
|
||||
"dateFormatLocal": "年/月/日",
|
||||
"dateFormatLocal": "月/日/年",
|
||||
"dateFormatUS": "年/月/日",
|
||||
"timeFormat": " 時間格式",
|
||||
"invalidTimeFormat": "格式無效",
|
||||
@@ -202,8 +202,8 @@
|
||||
"aquaColor": "水藍色",
|
||||
"blueColor": "藍色",
|
||||
"deleteTag": "刪除標籤",
|
||||
"colorPannelTitle": "顏色",
|
||||
"pannelTitle": "搜尋或建立選項",
|
||||
"colorPanelTitle": "顏色",
|
||||
"panelTitle": "搜尋或建立選項",
|
||||
"searchOption": "搜尋選項"
|
||||
},
|
||||
"menuName": "網格"
|
||||
@@ -211,8 +211,8 @@
|
||||
"document": {
|
||||
"menuName": "檔案",
|
||||
"date": {
|
||||
"timeHintTextInTwelveHour": "12:00 AM",
|
||||
"timeHintTextInTwentyFourHour": "12:00"
|
||||
"timeHintTextInTwelveHour": "01:00 PM",
|
||||
"timeHintTextInTwentyFourHour": "13:00"
|
||||
}
|
||||
},
|
||||
"sideBar": {
|
||||
|
||||
@@ -45,5 +45,7 @@
|
||||
<array>
|
||||
<string>en</string>
|
||||
</array>
|
||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -9,31 +9,38 @@ import 'package:flowy_sdk/rust_stream.dart';
|
||||
import 'notification_helper.dart';
|
||||
|
||||
// GridPB
|
||||
typedef GridNotificationCallback = void Function(GridNotification, Either<Uint8List, FlowyError>);
|
||||
typedef GridNotificationCallback = void Function(
|
||||
GridDartNotification, Either<Uint8List, FlowyError>);
|
||||
|
||||
class GridNotificationParser extends NotificationParser<GridNotification, FlowyError> {
|
||||
GridNotificationParser({String? id, required GridNotificationCallback callback})
|
||||
class GridNotificationParser
|
||||
extends NotificationParser<GridDartNotification, FlowyError> {
|
||||
GridNotificationParser(
|
||||
{String? id, required GridNotificationCallback callback})
|
||||
: super(
|
||||
id: id,
|
||||
callback: callback,
|
||||
tyParser: (ty) => GridNotification.valueOf(ty),
|
||||
tyParser: (ty) => GridDartNotification.valueOf(ty),
|
||||
errorParser: (bytes) => FlowyError.fromBuffer(bytes),
|
||||
);
|
||||
}
|
||||
|
||||
typedef GridNotificationHandler = Function(GridNotification ty, Either<Uint8List, FlowyError> result);
|
||||
typedef GridNotificationHandler = Function(
|
||||
GridDartNotification ty, Either<Uint8List, FlowyError> result);
|
||||
|
||||
class GridNotificationListener {
|
||||
StreamSubscription<SubscribeObject>? _subscription;
|
||||
GridNotificationParser? _parser;
|
||||
|
||||
GridNotificationListener({required String objectId, required GridNotificationHandler handler})
|
||||
GridNotificationListener(
|
||||
{required String objectId, required GridNotificationHandler handler})
|
||||
: _parser = GridNotificationParser(id: objectId, callback: handler) {
|
||||
_subscription = RustStreamReceiver.listen((observable) => _parser?.parse(observable));
|
||||
_subscription =
|
||||
RustStreamReceiver.listen((observable) => _parser?.parse(observable));
|
||||
}
|
||||
|
||||
Future<void> stop() async {
|
||||
_parser = null;
|
||||
await _subscription?.cancel();
|
||||
_subscription = null;
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,8 @@ class NetworkListener {
|
||||
late StreamSubscription<ConnectivityResult> _connectivitySubscription;
|
||||
|
||||
NetworkListener() {
|
||||
_connectivitySubscription = _connectivity.onConnectivityChanged.listen(_updateConnectionStatus);
|
||||
_connectivitySubscription =
|
||||
_connectivity.onConnectivityChanged.listen(_updateConnectionStatus);
|
||||
}
|
||||
|
||||
Future<void> start() async {
|
||||
@@ -39,9 +40,11 @@ class NetworkListener {
|
||||
return NetworkType.Ethernet;
|
||||
case ConnectivityResult.mobile:
|
||||
return NetworkType.Cell;
|
||||
case ConnectivityResult.none:
|
||||
return NetworkType.UnknownNetworkType;
|
||||
case ConnectivityResult.bluetooth:
|
||||
return NetworkType.Bluetooth;
|
||||
case ConnectivityResult.vpn:
|
||||
return NetworkType.VPN;
|
||||
case ConnectivityResult.none:
|
||||
return NetworkType.UnknownNetworkType;
|
||||
}
|
||||
}();
|
||||
|
||||
@@ -9,12 +9,15 @@ import 'package:app_flowy/startup/plugin/plugin.dart';
|
||||
class BlankPluginBuilder extends PluginBuilder {
|
||||
@override
|
||||
Plugin build(dynamic data) {
|
||||
return BlankPagePlugin(pluginType: pluginType);
|
||||
return BlankPagePlugin();
|
||||
}
|
||||
|
||||
@override
|
||||
String get menuName => "Blank";
|
||||
|
||||
@override
|
||||
String get menuIcon => "";
|
||||
|
||||
@override
|
||||
PluginType get pluginType => PluginType.blank;
|
||||
}
|
||||
@@ -25,11 +28,6 @@ class BlankPluginConfig implements PluginConfig {
|
||||
}
|
||||
|
||||
class BlankPagePlugin extends Plugin {
|
||||
final PluginType _pluginType;
|
||||
BlankPagePlugin({
|
||||
required PluginType pluginType,
|
||||
}) : _pluginType = pluginType;
|
||||
|
||||
@override
|
||||
PluginDisplay get display => BlankPagePluginDisplay();
|
||||
|
||||
@@ -37,16 +35,15 @@ class BlankPagePlugin extends Plugin {
|
||||
PluginId get id => "BlankStack";
|
||||
|
||||
@override
|
||||
PluginType get ty => _pluginType;
|
||||
PluginType get ty => PluginType.blank;
|
||||
}
|
||||
|
||||
class BlankPagePluginDisplay extends PluginDisplay with NavigationItem {
|
||||
@override
|
||||
Widget get leftBarItem =>
|
||||
FlowyText.medium(LocaleKeys.blankPageTitle.tr(), fontSize: 12);
|
||||
Widget get leftBarItem => FlowyText.medium(LocaleKeys.blankPageTitle.tr());
|
||||
|
||||
@override
|
||||
Widget buildWidget() => const BlankPage();
|
||||
Widget buildWidget(PluginContext context) => const BlankPage();
|
||||
|
||||
@override
|
||||
List<NavigationItem> get navigationItems => [this];
|
||||
|
||||
@@ -24,7 +24,8 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
||||
final BoardDataController _gridDataController;
|
||||
late final AppFlowyBoardController boardController;
|
||||
final MoveRowFFIService _rowService;
|
||||
LinkedHashMap<String, GroupController> groupControllers = LinkedHashMap();
|
||||
final LinkedHashMap<String, GroupController> groupControllers =
|
||||
LinkedHashMap();
|
||||
|
||||
GridFieldController get fieldController =>
|
||||
_gridDataController.fieldController;
|
||||
@@ -36,21 +37,21 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
||||
super(BoardState.initial(view.id)) {
|
||||
boardController = AppFlowyBoardController(
|
||||
onMoveGroup: (
|
||||
fromColumnId,
|
||||
fromGroupId,
|
||||
fromIndex,
|
||||
toColumnId,
|
||||
toGroupId,
|
||||
toIndex,
|
||||
) {
|
||||
_moveGroup(fromColumnId, toColumnId);
|
||||
_moveGroup(fromGroupId, toGroupId);
|
||||
},
|
||||
onMoveGroupItem: (
|
||||
columnId,
|
||||
groupId,
|
||||
fromIndex,
|
||||
toIndex,
|
||||
) {
|
||||
final fromRow = groupControllers[columnId]?.rowAtIndex(fromIndex);
|
||||
final toRow = groupControllers[columnId]?.rowAtIndex(toIndex);
|
||||
_moveRow(fromRow, columnId, toRow);
|
||||
final fromRow = groupControllers[groupId]?.rowAtIndex(fromIndex);
|
||||
final toRow = groupControllers[groupId]?.rowAtIndex(toIndex);
|
||||
_moveRow(fromRow, groupId, toRow);
|
||||
},
|
||||
onMoveGroupItemToGroup: (
|
||||
fromGroupId,
|
||||
@@ -69,7 +70,7 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
||||
await event.when(
|
||||
initial: () async {
|
||||
_startListening();
|
||||
await _loadGrid(emit);
|
||||
await _openGrid(emit);
|
||||
},
|
||||
createBottomRow: (groupId) async {
|
||||
final startRowId = groupControllers[groupId]?.lastRow()?.id;
|
||||
@@ -89,18 +90,30 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
},
|
||||
didCreateRow: (String groupId, RowPB row, int? index) {
|
||||
didCreateRow: (group, row, int? index) {
|
||||
emit(state.copyWith(
|
||||
editingRow: Some(BoardEditingRow(
|
||||
columnId: groupId,
|
||||
group: group,
|
||||
row: row,
|
||||
index: index,
|
||||
)),
|
||||
));
|
||||
_groupItemStartEditing(group, row, true);
|
||||
},
|
||||
endEditRow: (rowId) {
|
||||
startEditingRow: (group, row) {
|
||||
emit(state.copyWith(
|
||||
editingRow: Some(BoardEditingRow(
|
||||
group: group,
|
||||
row: row,
|
||||
index: null,
|
||||
)),
|
||||
));
|
||||
_groupItemStartEditing(group, row, true);
|
||||
},
|
||||
endEditingRow: (rowId) {
|
||||
state.editingRow.fold(() => null, (editingRow) {
|
||||
assert(editingRow.row.id == rowId);
|
||||
_groupItemStartEditing(editingRow.group, editingRow.row, false);
|
||||
emit(state.copyWith(editingRow: none()));
|
||||
});
|
||||
},
|
||||
@@ -122,6 +135,24 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
||||
);
|
||||
}
|
||||
|
||||
void _groupItemStartEditing(GroupPB group, RowPB row, bool isEdit) {
|
||||
final fieldInfo = fieldController.getField(group.fieldId);
|
||||
if (fieldInfo == null) {
|
||||
Log.warn("fieldInfo should not be null");
|
||||
return;
|
||||
}
|
||||
|
||||
boardController.enableGroupDragging(!isEdit);
|
||||
// boardController.updateGroupItem(
|
||||
// group.groupId,
|
||||
// GroupItem(
|
||||
// row: row,
|
||||
// fieldInfo: fieldInfo,
|
||||
// isDraggable: !isEdit,
|
||||
// ),
|
||||
// );
|
||||
}
|
||||
|
||||
void _moveRow(RowPB? fromRow, String columnId, RowPB? toRow) {
|
||||
if (fromRow != null) {
|
||||
_rowService
|
||||
@@ -136,11 +167,11 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
||||
}
|
||||
}
|
||||
|
||||
void _moveGroup(String fromColumnId, String toColumnId) {
|
||||
void _moveGroup(String fromGroupId, String toGroupId) {
|
||||
_rowService
|
||||
.moveGroup(
|
||||
fromGroupId: fromColumnId,
|
||||
toGroupId: toColumnId,
|
||||
fromGroupId: fromGroupId,
|
||||
toGroupId: toGroupId,
|
||||
)
|
||||
.then((result) {
|
||||
result.fold((l) => null, (r) => add(BoardEvent.didReceiveError(r)));
|
||||
@@ -156,7 +187,7 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
||||
return super.close();
|
||||
}
|
||||
|
||||
void initializeGroups(List<GroupPB> groups) {
|
||||
void initializeGroups(List<GroupPB> groupsData) {
|
||||
for (var controller in groupControllers.values) {
|
||||
controller.dispose();
|
||||
}
|
||||
@@ -164,27 +195,27 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
||||
boardController.clear();
|
||||
|
||||
//
|
||||
List<AppFlowyGroupData> columns = groups
|
||||
List<AppFlowyGroupData> groups = groupsData
|
||||
.where((group) => fieldController.getField(group.fieldId) != null)
|
||||
.map((group) {
|
||||
return AppFlowyGroupData(
|
||||
id: group.groupId,
|
||||
name: group.desc,
|
||||
items: _buildRows(group),
|
||||
customData: BoardCustomData(
|
||||
items: _buildGroupItems(group),
|
||||
customData: GroupData(
|
||||
group: group,
|
||||
fieldContext: fieldController.getField(group.fieldId)!,
|
||||
fieldInfo: fieldController.getField(group.fieldId)!,
|
||||
),
|
||||
);
|
||||
}).toList();
|
||||
boardController.addGroups(columns);
|
||||
boardController.addGroups(groups);
|
||||
|
||||
for (final group in groups) {
|
||||
for (final group in groupsData) {
|
||||
final delegate = GroupControllerDelegateImpl(
|
||||
controller: boardController,
|
||||
fieldController: fieldController,
|
||||
onNewColumnItem: (groupId, row, index) {
|
||||
add(BoardEvent.didCreateRow(groupId, row, index));
|
||||
add(BoardEvent.didCreateRow(group, row, index));
|
||||
},
|
||||
);
|
||||
final controller = GroupController(
|
||||
@@ -242,17 +273,20 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
||||
);
|
||||
}
|
||||
|
||||
List<AppFlowyGroupItem> _buildRows(GroupPB group) {
|
||||
List<AppFlowyGroupItem> _buildGroupItems(GroupPB group) {
|
||||
final items = group.rows.map((row) {
|
||||
final fieldContext = fieldController.getField(group.fieldId);
|
||||
return BoardColumnItem(row: row, fieldContext: fieldContext!);
|
||||
final fieldInfo = fieldController.getField(group.fieldId);
|
||||
return GroupItem(
|
||||
row: row,
|
||||
fieldInfo: fieldInfo!,
|
||||
);
|
||||
}).toList();
|
||||
|
||||
return <AppFlowyGroupItem>[...items];
|
||||
}
|
||||
|
||||
Future<void> _loadGrid(Emitter<BoardState> emit) async {
|
||||
final result = await _gridDataController.loadData();
|
||||
Future<void> _openGrid(Emitter<BoardState> emit) async {
|
||||
final result = await _gridDataController.openGrid();
|
||||
result.fold(
|
||||
(grid) => emit(
|
||||
state.copyWith(loadingState: GridLoadingState.finish(left(unit))),
|
||||
@@ -270,11 +304,15 @@ class BoardEvent with _$BoardEvent {
|
||||
const factory BoardEvent.createBottomRow(String groupId) = _CreateBottomRow;
|
||||
const factory BoardEvent.createHeaderRow(String groupId) = _CreateHeaderRow;
|
||||
const factory BoardEvent.didCreateRow(
|
||||
String groupId,
|
||||
GroupPB group,
|
||||
RowPB row,
|
||||
int? index,
|
||||
) = _DidCreateRow;
|
||||
const factory BoardEvent.endEditRow(String rowId) = _EndEditRow;
|
||||
const factory BoardEvent.startEditingRow(
|
||||
GroupPB group,
|
||||
RowPB row,
|
||||
) = _StartEditRow;
|
||||
const factory BoardEvent.endEditingRow(String rowId) = _EndEditRow;
|
||||
const factory BoardEvent.didReceiveError(FlowyError error) = _DidReceiveError;
|
||||
const factory BoardEvent.didReceiveGridUpdate(
|
||||
GridPB grid,
|
||||
@@ -334,14 +372,17 @@ class GridFieldEquatable extends Equatable {
|
||||
UnmodifiableListView<FieldPB> get value => UnmodifiableListView(_fields);
|
||||
}
|
||||
|
||||
class BoardColumnItem extends AppFlowyGroupItem {
|
||||
class GroupItem extends AppFlowyGroupItem {
|
||||
final RowPB row;
|
||||
final GridFieldContext fieldContext;
|
||||
final FieldInfo fieldInfo;
|
||||
|
||||
BoardColumnItem({
|
||||
GroupItem({
|
||||
required this.row,
|
||||
required this.fieldContext,
|
||||
});
|
||||
required this.fieldInfo,
|
||||
bool draggable = true,
|
||||
}) {
|
||||
super.draggable = draggable;
|
||||
}
|
||||
|
||||
@override
|
||||
String get id => row.id;
|
||||
@@ -360,17 +401,23 @@ class GroupControllerDelegateImpl extends GroupControllerDelegate {
|
||||
|
||||
@override
|
||||
void insertRow(GroupPB group, RowPB row, int? index) {
|
||||
final fieldContext = fieldController.getField(group.fieldId);
|
||||
if (fieldContext == null) {
|
||||
Log.warn("FieldContext should not be null");
|
||||
final fieldInfo = fieldController.getField(group.fieldId);
|
||||
if (fieldInfo == null) {
|
||||
Log.warn("fieldInfo should not be null");
|
||||
return;
|
||||
}
|
||||
|
||||
if (index != null) {
|
||||
final item = BoardColumnItem(row: row, fieldContext: fieldContext);
|
||||
final item = GroupItem(
|
||||
row: row,
|
||||
fieldInfo: fieldInfo,
|
||||
);
|
||||
controller.insertGroupItem(group.groupId, index, item);
|
||||
} else {
|
||||
final item = BoardColumnItem(row: row, fieldContext: fieldContext);
|
||||
final item = GroupItem(
|
||||
row: row,
|
||||
fieldInfo: fieldInfo,
|
||||
);
|
||||
controller.addGroupItem(group.groupId, item);
|
||||
}
|
||||
}
|
||||
@@ -382,25 +429,32 @@ class GroupControllerDelegateImpl extends GroupControllerDelegate {
|
||||
|
||||
@override
|
||||
void updateRow(GroupPB group, RowPB row) {
|
||||
final fieldContext = fieldController.getField(group.fieldId);
|
||||
if (fieldContext == null) {
|
||||
Log.warn("FieldContext should not be null");
|
||||
final fieldInfo = fieldController.getField(group.fieldId);
|
||||
if (fieldInfo == null) {
|
||||
Log.warn("fieldInfo should not be null");
|
||||
return;
|
||||
}
|
||||
controller.updateGroupItem(
|
||||
group.groupId,
|
||||
BoardColumnItem(row: row, fieldContext: fieldContext),
|
||||
GroupItem(
|
||||
row: row,
|
||||
fieldInfo: fieldInfo,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void addNewRow(GroupPB group, RowPB row, int? index) {
|
||||
final fieldContext = fieldController.getField(group.fieldId);
|
||||
if (fieldContext == null) {
|
||||
Log.warn("FieldContext should not be null");
|
||||
final fieldInfo = fieldController.getField(group.fieldId);
|
||||
if (fieldInfo == null) {
|
||||
Log.warn("fieldInfo should not be null");
|
||||
return;
|
||||
}
|
||||
final item = BoardColumnItem(row: row, fieldContext: fieldContext);
|
||||
final item = GroupItem(
|
||||
row: row,
|
||||
fieldInfo: fieldInfo,
|
||||
draggable: false,
|
||||
);
|
||||
|
||||
if (index != null) {
|
||||
controller.insertGroupItem(group.groupId, index, item);
|
||||
@@ -412,23 +466,23 @@ class GroupControllerDelegateImpl extends GroupControllerDelegate {
|
||||
}
|
||||
|
||||
class BoardEditingRow {
|
||||
String columnId;
|
||||
GroupPB group;
|
||||
RowPB row;
|
||||
int? index;
|
||||
|
||||
BoardEditingRow({
|
||||
required this.columnId,
|
||||
required this.group,
|
||||
required this.row,
|
||||
required this.index,
|
||||
});
|
||||
}
|
||||
|
||||
class BoardCustomData {
|
||||
class GroupData {
|
||||
final GroupPB group;
|
||||
final GridFieldContext fieldContext;
|
||||
BoardCustomData({
|
||||
final FieldInfo fieldInfo;
|
||||
GroupData({
|
||||
required this.group,
|
||||
required this.fieldContext,
|
||||
required this.fieldInfo,
|
||||
});
|
||||
|
||||
CheckboxGroup? asCheckboxGroup() {
|
||||
@@ -436,7 +490,7 @@ class BoardCustomData {
|
||||
return CheckboxGroup(group);
|
||||
}
|
||||
|
||||
FieldType get fieldType => fieldContext.fieldType;
|
||||
FieldType get fieldType => fieldInfo.fieldType;
|
||||
}
|
||||
|
||||
class CheckboxGroup {
|
||||
|
||||
@@ -12,7 +12,7 @@ import 'package:flowy_sdk/protobuf/flowy-grid/protobuf.dart';
|
||||
|
||||
import 'board_listener.dart';
|
||||
|
||||
typedef OnFieldsChanged = void Function(UnmodifiableListView<GridFieldContext>);
|
||||
typedef OnFieldsChanged = void Function(UnmodifiableListView<FieldInfo>);
|
||||
typedef OnGridChanged = void Function(GridPB);
|
||||
typedef DidLoadGroups = void Function(List<GroupPB>);
|
||||
typedef OnUpdatedGroup = void Function(List<GroupPB>);
|
||||
@@ -34,7 +34,8 @@ class BoardDataController {
|
||||
|
||||
// key: the block id
|
||||
final LinkedHashMap<String, GridBlockCache> _blocks;
|
||||
LinkedHashMap<String, GridBlockCache> get blocks => _blocks;
|
||||
UnmodifiableMapView<String, GridBlockCache> get blocks =>
|
||||
UnmodifiableMapView(_blocks);
|
||||
|
||||
OnFieldsChanged? _onFieldsChanged;
|
||||
OnGridChanged? _onGridChanged;
|
||||
@@ -87,13 +88,13 @@ class BoardDataController {
|
||||
onUpdatedGroup.call(changeset.updateGroups);
|
||||
}
|
||||
|
||||
if (changeset.insertedGroups.isNotEmpty) {
|
||||
onInsertedGroup.call(changeset.insertedGroups);
|
||||
}
|
||||
|
||||
if (changeset.deletedGroups.isNotEmpty) {
|
||||
onDeletedGroup.call(changeset.deletedGroups);
|
||||
}
|
||||
|
||||
if (changeset.insertedGroups.isNotEmpty) {
|
||||
onInsertedGroup.call(changeset.insertedGroups);
|
||||
}
|
||||
},
|
||||
(e) => _onError?.call(e),
|
||||
);
|
||||
@@ -107,21 +108,22 @@ class BoardDataController {
|
||||
);
|
||||
}
|
||||
|
||||
Future<Either<Unit, FlowyError>> loadData() async {
|
||||
final result = await _gridFFIService.loadGrid();
|
||||
Future<Either<Unit, FlowyError>> openGrid() async {
|
||||
final result = await _gridFFIService.openGrid();
|
||||
return Future(
|
||||
() => result.fold(
|
||||
(grid) async {
|
||||
_onGridChanged?.call(grid);
|
||||
return await fieldController.loadFields(fieldIds: grid.fields).then(
|
||||
(result) => result.fold(
|
||||
(l) {
|
||||
_loadGroups(grid.blocks);
|
||||
return left(l);
|
||||
},
|
||||
(err) => right(err),
|
||||
),
|
||||
);
|
||||
final result = await fieldController.loadFields(
|
||||
fieldIds: grid.fields,
|
||||
);
|
||||
return result.fold(
|
||||
(l) {
|
||||
_loadGroups(grid.blocks);
|
||||
return left(l);
|
||||
},
|
||||
(err) => right(err),
|
||||
);
|
||||
},
|
||||
(err) => right(err),
|
||||
),
|
||||
|
||||
@@ -32,18 +32,18 @@ class BoardListener {
|
||||
}
|
||||
|
||||
void _handler(
|
||||
GridNotification ty,
|
||||
GridDartNotification ty,
|
||||
Either<Uint8List, FlowyError> result,
|
||||
) {
|
||||
switch (ty) {
|
||||
case GridNotification.DidUpdateGroupView:
|
||||
case GridDartNotification.DidUpdateGroupView:
|
||||
result.fold(
|
||||
(payload) => _groupUpdateNotifier?.value =
|
||||
left(GroupViewChangesetPB.fromBuffer(payload)),
|
||||
(error) => _groupUpdateNotifier?.value = right(error),
|
||||
);
|
||||
break;
|
||||
case GridNotification.DidGroupByNewField:
|
||||
case GridDartNotification.DidGroupByNewField:
|
||||
result.fold(
|
||||
(payload) => _groupByNewFieldNotifier?.value =
|
||||
left(GroupViewChangesetPB.fromBuffer(payload).newGroups),
|
||||
|
||||
@@ -21,6 +21,9 @@ class BoardCheckboxCellBloc
|
||||
didReceiveCellUpdate: (cellData) {
|
||||
emit(state.copyWith(isSelected: _isSelected(cellData)));
|
||||
},
|
||||
select: () async {
|
||||
cellController.saveCellData(!state.isSelected ? "Yes" : "No");
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
@@ -32,7 +35,7 @@ class BoardCheckboxCellBloc
|
||||
cellController.removeListener(_onCellChangedFn!);
|
||||
_onCellChangedFn = null;
|
||||
}
|
||||
cellController.dispose();
|
||||
await cellController.dispose();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
@@ -50,6 +53,7 @@ class BoardCheckboxCellBloc
|
||||
@freezed
|
||||
class BoardCheckboxCellEvent with _$BoardCheckboxCellEvent {
|
||||
const factory BoardCheckboxCellEvent.initial() = _InitialCell;
|
||||
const factory BoardCheckboxCellEvent.select() = _Selected;
|
||||
const factory BoardCheckboxCellEvent.didReceiveCellUpdate(
|
||||
String cellContent) = _DidReceiveCellUpdate;
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ class BoardDateCellBloc extends Bloc<BoardDateCellEvent, BoardDateCellState> {
|
||||
cellController.removeListener(_onCellChangedFn!);
|
||||
_onCellChangedFn = null;
|
||||
}
|
||||
cellController.dispose();
|
||||
await cellController.dispose();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
@@ -58,14 +58,14 @@ class BoardDateCellState with _$BoardDateCellState {
|
||||
const factory BoardDateCellState({
|
||||
required DateCellDataPB? data,
|
||||
required String dateStr,
|
||||
required GridFieldContext fieldContext,
|
||||
required FieldInfo fieldInfo,
|
||||
}) = _BoardDateCellState;
|
||||
|
||||
factory BoardDateCellState.initial(GridDateCellController context) {
|
||||
final cellData = context.getCellData();
|
||||
|
||||
return BoardDateCellState(
|
||||
fieldContext: context.fieldContext,
|
||||
fieldInfo: context.fieldInfo,
|
||||
data: cellData,
|
||||
dateStr: _dateStrFromCellData(cellData),
|
||||
);
|
||||
|
||||
@@ -32,7 +32,7 @@ class BoardNumberCellBloc
|
||||
cellController.removeListener(_onCellChangedFn!);
|
||||
_onCellChangedFn = null;
|
||||
}
|
||||
cellController.dispose();
|
||||
await cellController.dispose();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
import 'dart:async';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/select_option.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/select_type_option.pb.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
|
||||
@@ -34,7 +34,7 @@ class BoardSelectOptionCellBloc
|
||||
cellController.removeListener(_onCellChangedFn!);
|
||||
_onCellChangedFn = null;
|
||||
}
|
||||
cellController.dispose();
|
||||
await cellController.dispose();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ class BoardTextCellBloc extends Bloc<BoardTextCellEvent, BoardTextCellState> {
|
||||
cellController.removeListener(_onCellChangedFn!);
|
||||
_onCellChangedFn = null;
|
||||
}
|
||||
cellController.dispose();
|
||||
await cellController.dispose();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ class BoardURLCellBloc extends Bloc<BoardURLCellEvent, BoardURLCellState> {
|
||||
cellController.removeListener(_onCellChangedFn!);
|
||||
_onCellChangedFn = null;
|
||||
}
|
||||
cellController.dispose();
|
||||
await cellController.dispose();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ class BoardCardBloc extends Bloc<BoardCardEvent, BoardCardState> {
|
||||
return RowInfo(
|
||||
gridId: _rowService.gridId,
|
||||
fields: UnmodifiableListView(
|
||||
state.cells.map((cell) => cell.identifier.fieldContext).toList(),
|
||||
state.cells.map((cell) => cell.identifier.fieldInfo).toList(),
|
||||
),
|
||||
rowPB: state.rowPB,
|
||||
);
|
||||
@@ -132,10 +132,10 @@ class BoardCellEquatable extends Equatable {
|
||||
@override
|
||||
List<Object?> get props {
|
||||
return [
|
||||
identifier.fieldContext.id,
|
||||
identifier.fieldContext.fieldType,
|
||||
identifier.fieldContext.visibility,
|
||||
identifier.fieldContext.width,
|
||||
identifier.fieldInfo.id,
|
||||
identifier.fieldInfo.fieldType,
|
||||
identifier.fieldInfo.visibility,
|
||||
identifier.fieldInfo.width,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,7 @@ class GroupController {
|
||||
void startListening() {
|
||||
_listener.start(onGroupChanged: (result) {
|
||||
result.fold(
|
||||
(GroupChangesetPB changeset) {
|
||||
(GroupRowsNotificationPB changeset) {
|
||||
for (final deletedRow in changeset.deletedRows) {
|
||||
group.rows.removeWhere((rowPB) => rowPB.id == deletedRow);
|
||||
delegate.removeRow(group, deletedRow);
|
||||
|
||||
@@ -8,7 +8,7 @@ import 'package:flowy_sdk/protobuf/flowy-grid/group.pb.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/group_changeset.pb.dart';
|
||||
|
||||
typedef UpdateGroupNotifiedValue = Either<GroupChangesetPB, FlowyError>;
|
||||
typedef UpdateGroupNotifiedValue = Either<GroupRowsNotificationPB, FlowyError>;
|
||||
|
||||
class GroupListener {
|
||||
final GroupPB group;
|
||||
@@ -27,14 +27,14 @@ class GroupListener {
|
||||
}
|
||||
|
||||
void _handler(
|
||||
GridNotification ty,
|
||||
GridDartNotification ty,
|
||||
Either<Uint8List, FlowyError> result,
|
||||
) {
|
||||
switch (ty) {
|
||||
case GridNotification.DidUpdateGroup:
|
||||
case GridDartNotification.DidUpdateGroup:
|
||||
result.fold(
|
||||
(payload) => _groupNotifier?.value =
|
||||
left(GroupChangesetPB.fromBuffer(payload)),
|
||||
left(GroupRowsNotificationPB.fromBuffer(payload)),
|
||||
(error) => _groupNotifier?.value = right(error),
|
||||
);
|
||||
break;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:app_flowy/plugins/util.dart';
|
||||
import 'package:app_flowy/workspace/presentation/home/home_stack.dart';
|
||||
import 'package:app_flowy/workspace/presentation/widgets/left_bar_item.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
|
||||
@@ -19,14 +20,17 @@ class BoardPluginBuilder implements PluginBuilder {
|
||||
@override
|
||||
String get menuName => "Board";
|
||||
|
||||
@override
|
||||
String get menuIcon => "editor/board";
|
||||
|
||||
@override
|
||||
PluginType get pluginType => PluginType.board;
|
||||
|
||||
@override
|
||||
ViewDataTypePB get dataType => ViewDataTypePB.Database;
|
||||
ViewDataFormatPB get dataFormatType => ViewDataFormatPB.DatabaseFormat;
|
||||
|
||||
@override
|
||||
ViewLayoutTypePB? get subDataType => ViewLayoutTypePB.Board;
|
||||
ViewLayoutTypePB? get layoutType => ViewLayoutTypePB.Board;
|
||||
}
|
||||
|
||||
class BoardPluginConfig implements PluginConfig {
|
||||
@@ -35,34 +39,47 @@ class BoardPluginConfig implements PluginConfig {
|
||||
}
|
||||
|
||||
class BoardPlugin extends Plugin {
|
||||
final ViewPB _view;
|
||||
@override
|
||||
final ViewPluginNotifier notifier;
|
||||
final PluginType _pluginType;
|
||||
|
||||
BoardPlugin({
|
||||
required ViewPB view,
|
||||
required PluginType pluginType,
|
||||
}) : _pluginType = pluginType,
|
||||
_view = view;
|
||||
notifier = ViewPluginNotifier(view: view);
|
||||
|
||||
@override
|
||||
PluginDisplay get display => GridPluginDisplay(view: _view);
|
||||
PluginDisplay get display => GridPluginDisplay(notifier: notifier);
|
||||
|
||||
@override
|
||||
PluginId get id => _view.id;
|
||||
PluginId get id => notifier.view.id;
|
||||
|
||||
@override
|
||||
PluginType get ty => _pluginType;
|
||||
}
|
||||
|
||||
class GridPluginDisplay extends PluginDisplay {
|
||||
final ViewPB _view;
|
||||
GridPluginDisplay({required ViewPB view, Key? key}) : _view = view;
|
||||
final ViewPluginNotifier notifier;
|
||||
GridPluginDisplay({required this.notifier, Key? key});
|
||||
|
||||
ViewPB get view => notifier.view;
|
||||
|
||||
@override
|
||||
Widget get leftBarItem => ViewLeftBarItem(view: _view);
|
||||
Widget get leftBarItem => ViewLeftBarItem(view: view);
|
||||
|
||||
@override
|
||||
Widget buildWidget() => BoardPage(view: _view);
|
||||
Widget buildWidget(PluginContext context) {
|
||||
notifier.isDeleted.addListener(() {
|
||||
notifier.isDeleted.value.fold(() => null, (deletedView) {
|
||||
if (deletedView.hasIndex()) {
|
||||
context.onDeleted(view, deletedView.index);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return BoardPage(key: ValueKey(view.id), view: view);
|
||||
}
|
||||
|
||||
@override
|
||||
List<NavigationItem> get navigationItems => [this];
|
||||
|
||||
@@ -12,8 +12,8 @@ import 'package:app_flowy/plugins/grid/presentation/widgets/row/row_detail.dart'
|
||||
import 'package:appflowy_board/appflowy_board.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/image.dart';
|
||||
import 'package:flowy_infra/theme.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui_web.dart';
|
||||
import 'package:flowy_infra_ui/widget/error_page.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-grid/block_entities.pb.dart';
|
||||
@@ -28,7 +28,10 @@ import 'toolbar/board_toolbar.dart';
|
||||
|
||||
class BoardPage extends StatelessWidget {
|
||||
final ViewPB view;
|
||||
BoardPage({required this.view, Key? key}) : super(key: ValueKey(view.id));
|
||||
BoardPage({
|
||||
required this.view,
|
||||
Key? key,
|
||||
}) : super(key: ValueKey(view.id));
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -63,7 +66,6 @@ class BoardContent extends StatefulWidget {
|
||||
|
||||
class _BoardContentState extends State<BoardContent> {
|
||||
late AppFlowyBoardScrollController scrollManager;
|
||||
final Map<String, ValueKey> cardKeysCache = {};
|
||||
|
||||
final config = AppFlowyBoardConfig(
|
||||
groupBackgroundColor: HexColor.fromHex('#F7F8FC'),
|
||||
@@ -78,7 +80,7 @@ class _BoardContentState extends State<BoardContent> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocListener<BoardBloc, BoardState>(
|
||||
listener: (context, state) => _handleEditState(state, context),
|
||||
listener: (context, state) => _handleEditStateChanged(state, context),
|
||||
child: BlocBuilder<BoardBloc, BoardState>(
|
||||
buildWhen: (previous, current) => previous.groupIds != current.groupIds,
|
||||
builder: (context, state) {
|
||||
@@ -87,19 +89,16 @@ class _BoardContentState extends State<BoardContent> {
|
||||
children: [const _ToolbarBlocAdaptor(), _buildBoard(context)],
|
||||
);
|
||||
|
||||
return Container(
|
||||
color: context.read<AppTheme>().surface,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: column,
|
||||
),
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: column,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Expanded _buildBoard(BuildContext context) {
|
||||
Widget _buildBoard(BuildContext context) {
|
||||
return Expanded(
|
||||
child: AppFlowyBoard(
|
||||
boardScrollController: scrollManager,
|
||||
@@ -114,27 +113,20 @@ class _BoardContentState extends State<BoardContent> {
|
||||
),
|
||||
groupConstraints: const BoxConstraints.tightFor(width: 300),
|
||||
config: AppFlowyBoardConfig(
|
||||
groupBackgroundColor: HexColor.fromHex('#F7F8FC'),
|
||||
groupBackgroundColor: Theme.of(context).colorScheme.surfaceVariant,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _handleEditState(BoardState state, BuildContext context) {
|
||||
void _handleEditStateChanged(BoardState state, BuildContext context) {
|
||||
state.editingRow.fold(
|
||||
() => null,
|
||||
(editingRow) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (editingRow.index != null) {
|
||||
context
|
||||
.read<BoardBloc>()
|
||||
.add(BoardEvent.endEditRow(editingRow.row.id));
|
||||
} else {
|
||||
scrollManager.scrollToBottom(editingRow.columnId, () {
|
||||
context
|
||||
.read<BoardBloc>()
|
||||
.add(BoardEvent.endEditRow(editingRow.row.id));
|
||||
});
|
||||
scrollManager.scrollToBottom(editingRow.group.groupId);
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -148,17 +140,16 @@ class _BoardContentState extends State<BoardContent> {
|
||||
|
||||
Widget _buildHeader(
|
||||
BuildContext context,
|
||||
AppFlowyGroupData columnData,
|
||||
AppFlowyGroupData groupData,
|
||||
) {
|
||||
final boardCustomData = columnData.customData as BoardCustomData;
|
||||
final boardCustomData = groupData.customData as GroupData;
|
||||
return AppFlowyGroupHeader(
|
||||
title: Flexible(
|
||||
fit: FlexFit.tight,
|
||||
child: FlowyText.medium(
|
||||
columnData.headerData.groupName,
|
||||
groupData.headerData.groupName,
|
||||
fontSize: 14,
|
||||
overflow: TextOverflow.clip,
|
||||
color: context.read<AppTheme>().textColor,
|
||||
),
|
||||
),
|
||||
icon: _buildHeaderIcon(boardCustomData),
|
||||
@@ -167,12 +158,12 @@ class _BoardContentState extends State<BoardContent> {
|
||||
width: 20,
|
||||
child: svgWidget(
|
||||
"home/add",
|
||||
color: context.read<AppTheme>().iconColor,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
onAddButtonClick: () {
|
||||
context.read<BoardBloc>().add(
|
||||
BoardEvent.createHeaderRow(columnData.id),
|
||||
BoardEvent.createHeaderRow(groupData.id),
|
||||
);
|
||||
},
|
||||
height: 50,
|
||||
@@ -181,48 +172,44 @@ class _BoardContentState extends State<BoardContent> {
|
||||
}
|
||||
|
||||
Widget _buildFooter(BuildContext context, AppFlowyGroupData columnData) {
|
||||
final boardCustomData = columnData.customData as BoardCustomData;
|
||||
final group = boardCustomData.group;
|
||||
// final boardCustomData = columnData.customData as BoardCustomData;
|
||||
// final group = boardCustomData.group;
|
||||
|
||||
if (group.isDefault) {
|
||||
return const SizedBox();
|
||||
} else {
|
||||
return AppFlowyGroupFooter(
|
||||
icon: SizedBox(
|
||||
height: 20,
|
||||
width: 20,
|
||||
child: svgWidget(
|
||||
"home/add",
|
||||
color: context.read<AppTheme>().iconColor,
|
||||
),
|
||||
return AppFlowyGroupFooter(
|
||||
icon: SizedBox(
|
||||
height: 20,
|
||||
width: 20,
|
||||
child: svgWidget(
|
||||
"home/add",
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
title: FlowyText.medium(
|
||||
LocaleKeys.board_column_create_new_card.tr(),
|
||||
fontSize: 14,
|
||||
color: context.read<AppTheme>().textColor,
|
||||
),
|
||||
height: 50,
|
||||
margin: config.footerPadding,
|
||||
onAddButtonClick: () {
|
||||
context.read<BoardBloc>().add(
|
||||
BoardEvent.createBottomRow(columnData.id),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
),
|
||||
title: FlowyText.medium(
|
||||
LocaleKeys.board_column_create_new_card.tr(),
|
||||
fontSize: 14,
|
||||
),
|
||||
height: 50,
|
||||
margin: config.footerPadding,
|
||||
onAddButtonClick: () {
|
||||
context.read<BoardBloc>().add(
|
||||
BoardEvent.createBottomRow(columnData.id),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCard(
|
||||
BuildContext context,
|
||||
AppFlowyGroupData column,
|
||||
AppFlowyGroupItem columnItem,
|
||||
AppFlowyGroupData afGroupData,
|
||||
AppFlowyGroupItem afGroupItem,
|
||||
) {
|
||||
final boardColumnItem = columnItem as BoardColumnItem;
|
||||
final rowPB = boardColumnItem.row;
|
||||
final groupItem = afGroupItem as GroupItem;
|
||||
final groupData = afGroupData.customData as GroupData;
|
||||
final rowPB = groupItem.row;
|
||||
final rowCache = context.read<BoardBloc>().getRowCache(rowPB.blockId);
|
||||
|
||||
/// Return placeholder widget if the rowCache is null.
|
||||
if (rowCache == null) return SizedBox(key: ObjectKey(columnItem));
|
||||
if (rowCache == null) return SizedBox(key: ObjectKey(groupItem));
|
||||
|
||||
final fieldController = context.read<BoardBloc>().fieldController;
|
||||
final gridId = context.read<BoardBloc>().gridId;
|
||||
@@ -237,25 +224,19 @@ class _BoardContentState extends State<BoardContent> {
|
||||
context.read<BoardBloc>().state.editingRow.fold(
|
||||
() => null,
|
||||
(editingRow) {
|
||||
isEditing = editingRow.row.id == columnItem.row.id;
|
||||
isEditing = editingRow.row.id == groupItem.row.id;
|
||||
},
|
||||
);
|
||||
|
||||
ValueKey? key = cardKeysCache[columnItem.id];
|
||||
if (key == null) {
|
||||
final newKey = ValueKey(columnItem.id);
|
||||
cardKeysCache[columnItem.id] = newKey;
|
||||
key = newKey;
|
||||
}
|
||||
|
||||
final groupItemId = groupItem.row.id + groupData.group.groupId;
|
||||
return AppFlowyGroupCard(
|
||||
key: key,
|
||||
key: ValueKey(groupItemId),
|
||||
margin: config.cardPadding,
|
||||
decoration: _makeBoxDecoration(context),
|
||||
child: BoardCard(
|
||||
gridId: gridId,
|
||||
groupId: column.id,
|
||||
fieldId: boardColumnItem.fieldContext.id,
|
||||
groupId: groupData.group.groupId,
|
||||
fieldId: groupItem.fieldInfo.id,
|
||||
isEditing: isEditing,
|
||||
cellBuilder: cellBuilder,
|
||||
dataController: cardController,
|
||||
@@ -266,25 +247,45 @@ class _BoardContentState extends State<BoardContent> {
|
||||
rowCache,
|
||||
context,
|
||||
),
|
||||
onStartEditing: () {
|
||||
context.read<BoardBloc>().add(
|
||||
BoardEvent.startEditingRow(
|
||||
groupData.group,
|
||||
groupItem.row,
|
||||
),
|
||||
);
|
||||
},
|
||||
onEndEditing: () {
|
||||
context
|
||||
.read<BoardBloc>()
|
||||
.add(BoardEvent.endEditingRow(groupItem.row.id));
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
BoxDecoration _makeBoxDecoration(BuildContext context) {
|
||||
final theme = context.read<AppTheme>();
|
||||
final borderSide = BorderSide(color: theme.shader6, width: 1.0);
|
||||
final borderSide = BorderSide(
|
||||
color: Theme.of(context).dividerColor,
|
||||
width: 1.0,
|
||||
);
|
||||
return BoxDecoration(
|
||||
color: theme.surface,
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
border: Border.fromBorderSide(borderSide),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(6)),
|
||||
);
|
||||
}
|
||||
|
||||
void _openCard(String gridId, GridFieldController fieldController,
|
||||
RowPB rowPB, GridRowCache rowCache, BuildContext context) {
|
||||
void _openCard(
|
||||
String gridId,
|
||||
GridFieldController fieldController,
|
||||
RowPB rowPB,
|
||||
GridRowCache rowCache,
|
||||
BuildContext context,
|
||||
) {
|
||||
final rowInfo = RowInfo(
|
||||
gridId: gridId,
|
||||
fields: UnmodifiableListView(fieldController.fieldContexts),
|
||||
fields: UnmodifiableListView(fieldController.fieldInfos),
|
||||
rowPB: rowPB,
|
||||
);
|
||||
|
||||
@@ -294,10 +295,15 @@ class _BoardContentState extends State<BoardContent> {
|
||||
rowCache: rowCache,
|
||||
);
|
||||
|
||||
RowDetailPage(
|
||||
cellBuilder: GridCellBuilder(delegate: dataController),
|
||||
dataController: dataController,
|
||||
).show(context);
|
||||
FlowyOverlay.show(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return RowDetailPage(
|
||||
cellBuilder: GridCellBuilder(delegate: dataController),
|
||||
dataController: dataController,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -329,7 +335,7 @@ extension HexColor on Color {
|
||||
}
|
||||
}
|
||||
|
||||
Widget? _buildHeaderIcon(BoardCustomData customData) {
|
||||
Widget? _buildHeaderIcon(GroupData customData) {
|
||||
Widget? widget;
|
||||
switch (customData.fieldType) {
|
||||
case FieldType.Checkbox:
|
||||
|
||||
@@ -23,7 +23,7 @@ class EditableRowNotifier {
|
||||
EditableRowNotifier({required bool isEditing})
|
||||
: isEditing = ValueNotifier(isEditing);
|
||||
|
||||
void insertCell(
|
||||
void bindCell(
|
||||
GridCellIdentifier cellIdentifier,
|
||||
EditableCellNotifier notifier,
|
||||
) {
|
||||
@@ -59,7 +59,7 @@ class EditableRowNotifier {
|
||||
_cells.values.first.isCellEditing.value = false;
|
||||
}
|
||||
|
||||
void clear() {
|
||||
void unbind() {
|
||||
for (final notifier in _cells.values) {
|
||||
notifier.dispose();
|
||||
}
|
||||
@@ -76,6 +76,10 @@ class EditableRowNotifier {
|
||||
}
|
||||
|
||||
abstract class EditableCell {
|
||||
// Each cell notifier will be bind to the [EditableRowNotifier], which enable
|
||||
// the row notifier receive its cells event. For example: begin editing the
|
||||
// cell or end editing the cell.
|
||||
//
|
||||
EditableCellNotifier? get editableNotifier;
|
||||
}
|
||||
|
||||
|
||||
@@ -48,6 +48,9 @@ class _BoardCheckboxCellState extends State<BoardCheckboxCell> {
|
||||
iconPadding: EdgeInsets.zero,
|
||||
icon: icon,
|
||||
width: 20,
|
||||
onPressed: () => context
|
||||
.read<BoardCheckboxCellBloc>()
|
||||
.add(const BoardCheckboxCellEvent.select()),
|
||||
),
|
||||
);
|
||||
},
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import 'package:app_flowy/plugins/board/application/card/board_date_cell_bloc.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
|
||||
import 'package:flowy_infra/theme.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
@@ -53,7 +52,7 @@ class _BoardDateCellState extends State<BoardDateCell> {
|
||||
child: FlowyText.regular(
|
||||
state.dateStr,
|
||||
fontSize: 13,
|
||||
color: context.read<AppTheme>().shader3,
|
||||
color: Theme.of(context).hintColor,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -3,7 +3,6 @@ import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_servic
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import 'define.dart';
|
||||
|
||||
class BoardNumberCell extends StatefulWidget {
|
||||
|
||||
+56
-56
@@ -2,6 +2,8 @@ import 'package:app_flowy/plugins/board/application/card/board_select_option_cel
|
||||
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
|
||||
import 'package:app_flowy/plugins/grid/presentation/widgets/cell/select_option_cell/extension.dart';
|
||||
import 'package:app_flowy/plugins/grid/presentation/widgets/cell/select_option_cell/select_option_editor.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
@@ -26,9 +28,11 @@ class BoardSelectOptionCell extends StatefulWidget with EditableCell {
|
||||
|
||||
class _BoardSelectOptionCellState extends State<BoardSelectOptionCell> {
|
||||
late BoardSelectOptionCellBloc _cellBloc;
|
||||
late PopoverController _popover;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_popover = PopoverController();
|
||||
final cellController =
|
||||
widget.cellControllerBuilder.build() as GridSelectOptionCellController;
|
||||
_cellBloc = BoardSelectOptionCellBloc(cellController: cellController)
|
||||
@@ -41,43 +45,60 @@ class _BoardSelectOptionCellState extends State<BoardSelectOptionCell> {
|
||||
return BlocProvider.value(
|
||||
value: _cellBloc,
|
||||
child: BlocBuilder<BoardSelectOptionCellBloc, BoardSelectOptionCellState>(
|
||||
buildWhen: (previous, current) {
|
||||
return previous.selectedOptions != current.selectedOptions;
|
||||
},
|
||||
builder: (context, state) {
|
||||
if (state.selectedOptions
|
||||
.where((element) => element.id == widget.groupId)
|
||||
.isNotEmpty ||
|
||||
state.selectedOptions.isEmpty) {
|
||||
return const SizedBox();
|
||||
} else {
|
||||
final children = state.selectedOptions
|
||||
.map(
|
||||
(option) => SelectOptionTag.fromOption(
|
||||
context: context,
|
||||
option: option,
|
||||
onSelected: () {
|
||||
SelectOptionCellEditor.show(
|
||||
context: context,
|
||||
cellController: widget.cellControllerBuilder.build()
|
||||
as GridSelectOptionCellController,
|
||||
);
|
||||
},
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
buildWhen: (previous, current) {
|
||||
return previous.selectedOptions != current.selectedOptions;
|
||||
}, builder: (context, state) {
|
||||
// Returns SizedBox if the content of the cell is empty
|
||||
if (_isEmpty(state)) return const SizedBox();
|
||||
|
||||
return IntrinsicHeight(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 6),
|
||||
child: SizedBox.expand(
|
||||
child: Wrap(spacing: 4, runSpacing: 2, children: children),
|
||||
),
|
||||
),
|
||||
final children = state.selectedOptions.map(
|
||||
(option) {
|
||||
final tag = SelectOptionTag.fromOption(
|
||||
context: context,
|
||||
option: option,
|
||||
onSelected: () => _popover.show(),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
return _wrapPopover(tag);
|
||||
},
|
||||
).toList();
|
||||
|
||||
return IntrinsicHeight(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 6),
|
||||
child: SizedBox.expand(
|
||||
child: Wrap(spacing: 4, runSpacing: 2, children: children),
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
bool _isEmpty(BoardSelectOptionCellState state) {
|
||||
// The cell should hide if the option id is equal to the groupId.
|
||||
final isInGroup = state.selectedOptions
|
||||
.where((element) => element.id == widget.groupId)
|
||||
.isNotEmpty;
|
||||
return isInGroup || state.selectedOptions.isEmpty;
|
||||
}
|
||||
|
||||
Widget _wrapPopover(Widget child) {
|
||||
final constraints = BoxConstraints.loose(Size(
|
||||
SelectOptionCellEditor.editorPanelWidth,
|
||||
300,
|
||||
));
|
||||
return AppFlowyPopover(
|
||||
controller: _popover,
|
||||
constraints: constraints,
|
||||
direction: PopoverDirection.bottomWithLeftAligned,
|
||||
popupBuilder: (BuildContext context) {
|
||||
return SelectOptionCellEditor(
|
||||
cellController: widget.cellControllerBuilder.build()
|
||||
as GridSelectOptionCellController,
|
||||
);
|
||||
},
|
||||
onClose: () {},
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -87,24 +108,3 @@ class _BoardSelectOptionCellState extends State<BoardSelectOptionCell> {
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
class _SelectOptionDialog extends StatelessWidget {
|
||||
final GridSelectOptionCellController _controller;
|
||||
const _SelectOptionDialog({
|
||||
Key? key,
|
||||
required IGridCellController controller,
|
||||
}) : _controller = controller as GridSelectOptionCellController,
|
||||
super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
child: InkWell(onTap: () {
|
||||
SelectOptionCellEditor.show(
|
||||
context: context,
|
||||
cellController: _controller,
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
import 'package:app_flowy/plugins/board/application/card/board_text_cell_bloc.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
|
||||
import 'package:app_flowy/plugins/grid/presentation/widgets/cell/cell_builder.dart';
|
||||
import 'package:flowy_infra/size.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:textstyle_extensions/textstyle_extensions.dart';
|
||||
import 'board_cell.dart';
|
||||
import 'define.dart';
|
||||
|
||||
@@ -42,6 +44,9 @@ class _BoardTextCellState extends State<BoardTextCell> {
|
||||
focusNode.requestFocus();
|
||||
}
|
||||
|
||||
// If the focusNode lost its focus, the widget's editableNotifier will
|
||||
// set to false, which will cause the [EditableRowNotifier] to receive
|
||||
// end edit event.
|
||||
focusNode.addListener(() {
|
||||
if (!focusNode.hasFocus) {
|
||||
focusWhenInit = false;
|
||||
@@ -131,7 +136,11 @@ class _BoardTextCellState extends State<BoardTextCell> {
|
||||
padding: EdgeInsets.symmetric(
|
||||
vertical: BoardSizes.cardCellVPadding,
|
||||
),
|
||||
child: FlowyText.medium(state.content, fontSize: 14),
|
||||
child: FlowyText.medium(
|
||||
state.content,
|
||||
fontSize: 14,
|
||||
maxLines: null, // Enable multiple lines
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -143,11 +152,7 @@ class _BoardTextCellState extends State<BoardTextCell> {
|
||||
onChanged: (value) => focusChanged(),
|
||||
onEditingComplete: () => focusNode.unfocus(),
|
||||
maxLines: null,
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontFamily: 'Mulish',
|
||||
),
|
||||
style: Theme.of(context).textTheme.bodyMedium!.size(FontSizes.s14),
|
||||
decoration: InputDecoration(
|
||||
// Magic number 4 makes the textField take up the same space as FlowyText
|
||||
contentPadding: EdgeInsets.symmetric(
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import 'package:app_flowy/plugins/board/application/card/board_url_cell_bloc.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
|
||||
import 'package:flowy_infra/theme.dart';
|
||||
import 'package:flowy_infra/size.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:textstyle_extensions/textstyle_extensions.dart';
|
||||
|
||||
import 'define.dart';
|
||||
|
||||
@@ -34,7 +35,6 @@ class _BoardUrlCellState extends State<BoardUrlCell> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = context.watch<AppTheme>();
|
||||
return BlocProvider.value(
|
||||
value: _cellBloc,
|
||||
child: BlocBuilder<BoardURLCellBloc, BoardURLCellState>(
|
||||
@@ -53,11 +53,12 @@ class _BoardUrlCellState extends State<BoardUrlCell> {
|
||||
textAlign: TextAlign.left,
|
||||
text: TextSpan(
|
||||
text: state.content,
|
||||
style: TextStyle(
|
||||
color: theme.main2,
|
||||
fontSize: 14,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium!
|
||||
.size(FontSizes.s14)
|
||||
.textColor(Theme.of(context).colorScheme.primary)
|
||||
.underline,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import 'package:app_flowy/plugins/board/application/card/card_bloc.dart';
|
||||
import 'package:app_flowy/plugins/board/application/card/card_data_controller.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
|
||||
import 'package:app_flowy/plugins/grid/presentation/widgets/row/row_action_sheet.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:flowy_infra/image.dart';
|
||||
import 'package:flowy_infra/theme.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui_web.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'board_cell.dart';
|
||||
import 'card_cell_builder.dart';
|
||||
import 'card_container.dart';
|
||||
import 'container/accessory.dart';
|
||||
import 'container/card_container.dart';
|
||||
|
||||
class BoardCard extends StatefulWidget {
|
||||
final String gridId;
|
||||
@@ -20,6 +20,8 @@ class BoardCard extends StatefulWidget {
|
||||
final CardDataController dataController;
|
||||
final BoardCellBuilder cellBuilder;
|
||||
final void Function(BuildContext) openCard;
|
||||
final VoidCallback onStartEditing;
|
||||
final VoidCallback onEndEditing;
|
||||
|
||||
const BoardCard({
|
||||
required this.gridId,
|
||||
@@ -29,6 +31,8 @@ class BoardCard extends StatefulWidget {
|
||||
required this.dataController,
|
||||
required this.cellBuilder,
|
||||
required this.openCard,
|
||||
required this.onStartEditing,
|
||||
required this.onEndEditing,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@@ -39,6 +43,8 @@ class BoardCard extends StatefulWidget {
|
||||
class _BoardCardState extends State<BoardCard> {
|
||||
late BoardCardBloc _cardBloc;
|
||||
late EditableRowNotifier rowNotifier;
|
||||
late PopoverController popoverController;
|
||||
AccessoryType? accessoryType;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -53,8 +59,15 @@ class _BoardCardState extends State<BoardCard> {
|
||||
rowNotifier.isEditing.addListener(() {
|
||||
if (!mounted) return;
|
||||
_cardBloc.add(BoardCardEvent.setIsEditing(rowNotifier.isEditing.value));
|
||||
|
||||
if (rowNotifier.isEditing.value) {
|
||||
widget.onStartEditing();
|
||||
} else {
|
||||
widget.onEndEditing();
|
||||
}
|
||||
});
|
||||
|
||||
popoverController = PopoverController();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@@ -64,31 +77,44 @@ class _BoardCardState extends State<BoardCard> {
|
||||
value: _cardBloc,
|
||||
child: BlocBuilder<BoardCardBloc, BoardCardState>(
|
||||
buildWhen: (previous, current) {
|
||||
// Rebuild when:
|
||||
// 1.If the length of the cells is not the same
|
||||
// 2.isEditing changed
|
||||
if (previous.cells.length != current.cells.length ||
|
||||
previous.isEditing != current.isEditing) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 3.Compare the content of the cells. The cells consists of
|
||||
// list of [BoardCellEquatable] that extends the [Equatable].
|
||||
return !listEquals(previous.cells, current.cells);
|
||||
},
|
||||
builder: (context, state) {
|
||||
return BoardCardContainer(
|
||||
buildAccessoryWhen: () => state.isEditing == false,
|
||||
accessoryBuilder: (context) {
|
||||
return [
|
||||
_CardEditOption(
|
||||
startEditing: () => rowNotifier.becomeFirstResponder(),
|
||||
),
|
||||
const _CardMoreOption(),
|
||||
];
|
||||
},
|
||||
onTap: (context) {
|
||||
widget.openCard(context);
|
||||
},
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: _makeCells(
|
||||
context,
|
||||
state.cells.map((cell) => cell.identifier).toList(),
|
||||
return AppFlowyPopover(
|
||||
controller: popoverController,
|
||||
triggerActions: PopoverTriggerFlags.none,
|
||||
constraints: BoxConstraints.loose(const Size(140, 200)),
|
||||
margin: const EdgeInsets.all(6),
|
||||
direction: PopoverDirection.rightWithCenterAligned,
|
||||
popupBuilder: (popoverContext) => _handlePopoverBuilder(
|
||||
context,
|
||||
popoverContext,
|
||||
),
|
||||
child: BoardCardContainer(
|
||||
buildAccessoryWhen: () => state.isEditing == false,
|
||||
accessoryBuilder: (context) {
|
||||
return [
|
||||
_CardEditOption(rowNotifier: rowNotifier),
|
||||
_CardMoreOption(),
|
||||
];
|
||||
},
|
||||
openAccessory: _handleOpenAccessory,
|
||||
openCard: (context) => widget.openCard(context),
|
||||
child: _CellColumn(
|
||||
groupId: widget.groupId,
|
||||
rowNotifier: rowNotifier,
|
||||
cellBuilder: widget.cellBuilder,
|
||||
cells: state.cells,
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -97,42 +123,29 @@ class _BoardCardState extends State<BoardCard> {
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> _makeCells(
|
||||
void _handleOpenAccessory(AccessoryType newAccessoryType) {
|
||||
accessoryType = newAccessoryType;
|
||||
switch (newAccessoryType) {
|
||||
case AccessoryType.edit:
|
||||
break;
|
||||
case AccessoryType.more:
|
||||
popoverController.show();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Widget _handlePopoverBuilder(
|
||||
BuildContext context,
|
||||
List<GridCellIdentifier> cells,
|
||||
BuildContext popoverContext,
|
||||
) {
|
||||
final List<Widget> children = [];
|
||||
rowNotifier.clear();
|
||||
cells.asMap().forEach(
|
||||
(int index, GridCellIdentifier cellId) {
|
||||
EditableCellNotifier cellNotifier;
|
||||
if (index == 0) {
|
||||
// Only use the first cell to receive user's input when click the edit
|
||||
// button
|
||||
cellNotifier = EditableCellNotifier(
|
||||
isEditing: rowNotifier.isEditing.value,
|
||||
);
|
||||
rowNotifier.insertCell(cellId, cellNotifier);
|
||||
} else {
|
||||
cellNotifier = EditableCellNotifier();
|
||||
}
|
||||
|
||||
Widget child = widget.cellBuilder.buildCell(
|
||||
widget.groupId,
|
||||
cellId,
|
||||
cellNotifier,
|
||||
switch (accessoryType!) {
|
||||
case AccessoryType.edit:
|
||||
throw UnimplementedError();
|
||||
case AccessoryType.more:
|
||||
return GridRowActionSheet(
|
||||
rowData: context.read<BoardCardBloc>().rowInfo(),
|
||||
);
|
||||
|
||||
child = Padding(
|
||||
key: cellId.key(),
|
||||
padding: const EdgeInsets.only(left: 4, right: 4),
|
||||
child: child,
|
||||
);
|
||||
|
||||
children.add(child);
|
||||
},
|
||||
);
|
||||
return children;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -143,30 +156,85 @@ class _BoardCardState extends State<BoardCard> {
|
||||
}
|
||||
}
|
||||
|
||||
class _CellColumn extends StatelessWidget {
|
||||
final String groupId;
|
||||
final BoardCellBuilder cellBuilder;
|
||||
final EditableRowNotifier rowNotifier;
|
||||
final List<BoardCellEquatable> cells;
|
||||
const _CellColumn({
|
||||
required this.groupId,
|
||||
required this.rowNotifier,
|
||||
required this.cellBuilder,
|
||||
required this.cells,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: _makeCells(context, cells),
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> _makeCells(
|
||||
BuildContext context,
|
||||
List<BoardCellEquatable> cells,
|
||||
) {
|
||||
final List<Widget> children = [];
|
||||
// Remove all the cell listeners.
|
||||
rowNotifier.unbind();
|
||||
|
||||
cells.asMap().forEach(
|
||||
(int index, BoardCellEquatable cell) {
|
||||
final isEditing = index == 0 ? rowNotifier.isEditing.value : false;
|
||||
final cellNotifier = EditableCellNotifier(isEditing: isEditing);
|
||||
|
||||
if (index == 0) {
|
||||
// Only use the first cell to receive user's input when click the edit
|
||||
// button
|
||||
rowNotifier.bindCell(cell.identifier, cellNotifier);
|
||||
}
|
||||
|
||||
final child = Padding(
|
||||
key: cell.identifier.key(),
|
||||
padding: const EdgeInsets.only(left: 4, right: 4),
|
||||
child: cellBuilder.buildCell(
|
||||
groupId,
|
||||
cell.identifier,
|
||||
cellNotifier,
|
||||
),
|
||||
);
|
||||
|
||||
children.add(child);
|
||||
},
|
||||
);
|
||||
return children;
|
||||
}
|
||||
}
|
||||
|
||||
class _CardMoreOption extends StatelessWidget with CardAccessory {
|
||||
const _CardMoreOption({Key? key}) : super(key: key);
|
||||
_CardMoreOption({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(3.0),
|
||||
child:
|
||||
svgWidget('grid/details', color: context.read<AppTheme>().iconColor),
|
||||
child: svgWidget(
|
||||
'grid/details',
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void onTap(BuildContext context) {
|
||||
GridRowActionSheet(
|
||||
rowData: context.read<BoardCardBloc>().rowInfo(),
|
||||
).show(context, direction: AnchorDirection.bottomWithCenterAligned);
|
||||
}
|
||||
AccessoryType get type => AccessoryType.more;
|
||||
}
|
||||
|
||||
class _CardEditOption extends StatelessWidget with CardAccessory {
|
||||
final VoidCallback startEditing;
|
||||
final EditableRowNotifier rowNotifier;
|
||||
const _CardEditOption({
|
||||
required this.startEditing,
|
||||
required this.rowNotifier,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@@ -176,13 +244,14 @@ class _CardEditOption extends StatelessWidget with CardAccessory {
|
||||
padding: const EdgeInsets.all(3.0),
|
||||
child: svgWidget(
|
||||
'editor/edit',
|
||||
color: context.read<AppTheme>().iconColor,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void onTap(BuildContext context) {
|
||||
startEditing();
|
||||
}
|
||||
void onTap(BuildContext context) => rowNotifier.becomeFirstResponder();
|
||||
|
||||
@override
|
||||
AccessoryType get type => AccessoryType.edit;
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
import 'package:flowy_infra_ui/style_widget/hover.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
enum AccessoryType {
|
||||
edit,
|
||||
more,
|
||||
}
|
||||
|
||||
abstract class CardAccessory implements Widget {
|
||||
AccessoryType get type;
|
||||
void onTap(BuildContext context) {}
|
||||
}
|
||||
|
||||
typedef CardAccessoryBuilder = List<CardAccessory> Function(
|
||||
BuildContext buildContext,
|
||||
);
|
||||
|
||||
class CardAccessoryContainer extends StatelessWidget {
|
||||
final void Function(AccessoryType) onTapAccessory;
|
||||
final List<CardAccessory> accessories;
|
||||
const CardAccessoryContainer({
|
||||
required this.accessories,
|
||||
required this.onTapAccessory,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final children = accessories.map((accessory) {
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () {
|
||||
accessory.onTap(context);
|
||||
onTapAccessory(accessory.type);
|
||||
},
|
||||
child: _wrapHover(context, accessory),
|
||||
);
|
||||
}).toList();
|
||||
return _wrapDecoration(context, Row(children: children));
|
||||
}
|
||||
|
||||
FlowyHover _wrapHover(BuildContext context, CardAccessory accessory) {
|
||||
return FlowyHover(
|
||||
style: HoverStyle(
|
||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||
borderRadius: BorderRadius.zero,
|
||||
),
|
||||
builder: (_, onHover) => SizedBox(
|
||||
width: 24,
|
||||
height: 24,
|
||||
child: accessory,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _wrapDecoration(BuildContext context, Widget child) {
|
||||
final borderSide = BorderSide(
|
||||
color: Theme.of(context).dividerColor,
|
||||
width: 1.0,
|
||||
);
|
||||
final decoration = BoxDecoration(
|
||||
color: Colors.transparent,
|
||||
border: Border.fromBorderSide(borderSide),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(4)),
|
||||
);
|
||||
return Container(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
decoration: decoration,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
||||
+21
-74
@@ -1,17 +1,19 @@
|
||||
import 'package:flowy_infra/theme.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/hover.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
|
||||
import 'accessory.dart';
|
||||
|
||||
class BoardCardContainer extends StatelessWidget {
|
||||
final Widget child;
|
||||
final CardAccessoryBuilder? accessoryBuilder;
|
||||
final bool Function()? buildAccessoryWhen;
|
||||
final void Function(BuildContext) onTap;
|
||||
final void Function(BuildContext) openCard;
|
||||
final void Function(AccessoryType) openAccessory;
|
||||
const BoardCardContainer({
|
||||
required this.child,
|
||||
required this.onTap,
|
||||
required this.openCard,
|
||||
required this.openAccessory,
|
||||
this.accessoryBuilder,
|
||||
this.buildAccessoryWhen,
|
||||
Key? key,
|
||||
@@ -34,13 +36,14 @@ class BoardCardContainer extends StatelessWidget {
|
||||
if (accessories.isNotEmpty) {
|
||||
container = _CardEnterRegion(
|
||||
accessories: accessories,
|
||||
onTapAccessory: openAccessory,
|
||||
child: container,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () => onTap(context),
|
||||
onTap: () => openCard(context),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: ConstrainedBox(
|
||||
@@ -55,75 +58,16 @@ class BoardCardContainer extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
abstract class CardAccessory implements Widget {
|
||||
void onTap(BuildContext context);
|
||||
}
|
||||
|
||||
typedef CardAccessoryBuilder = List<CardAccessory> Function(
|
||||
BuildContext buildContext,
|
||||
);
|
||||
|
||||
class CardAccessoryContainer extends StatelessWidget {
|
||||
final List<CardAccessory> accessories;
|
||||
const CardAccessoryContainer({required this.accessories, Key? key})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = context.read<AppTheme>();
|
||||
final children = accessories.map((accessory) {
|
||||
final hover = FlowyHover(
|
||||
style: HoverStyle(
|
||||
hoverColor: theme.hover,
|
||||
backgroundColor: theme.surface,
|
||||
borderRadius: BorderRadius.zero,
|
||||
),
|
||||
builder: (_, onHover) => SizedBox(
|
||||
width: 24,
|
||||
height: 24,
|
||||
child: accessory,
|
||||
),
|
||||
);
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () => accessory.onTap(context),
|
||||
child: hover,
|
||||
);
|
||||
}).toList();
|
||||
|
||||
return Container(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
decoration: _makeBoxDecoration(context),
|
||||
child: Row(children: children),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
BoxDecoration _makeBoxDecoration(BuildContext context) {
|
||||
final theme = context.read<AppTheme>();
|
||||
final borderSide = BorderSide(color: theme.shader6, width: 1.0);
|
||||
return BoxDecoration(
|
||||
color: Colors.transparent,
|
||||
border: Border.fromBorderSide(borderSide),
|
||||
// boxShadow: const [
|
||||
// BoxShadow(
|
||||
// color: Colors.transparent,
|
||||
// spreadRadius: 0,
|
||||
// blurRadius: 5,
|
||||
// offset: Offset.zero,
|
||||
// )
|
||||
// ],
|
||||
|
||||
borderRadius: const BorderRadius.all(Radius.circular(4)),
|
||||
);
|
||||
}
|
||||
|
||||
class _CardEnterRegion extends StatelessWidget {
|
||||
final Widget child;
|
||||
final List<CardAccessory> accessories;
|
||||
const _CardEnterRegion(
|
||||
{required this.child, required this.accessories, Key? key})
|
||||
: super(key: key);
|
||||
final void Function(AccessoryType) onTapAccessory;
|
||||
const _CardEnterRegion({
|
||||
required this.child,
|
||||
required this.accessories,
|
||||
required this.onTapAccessory,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -132,9 +76,12 @@ class _CardEnterRegion extends StatelessWidget {
|
||||
builder: (context, onEnter, _) {
|
||||
List<Widget> children = [child];
|
||||
if (onEnter) {
|
||||
children.add(CardAccessoryContainer(
|
||||
accessories: accessories,
|
||||
).positioned(right: 0));
|
||||
children.add(
|
||||
CardAccessoryContainer(
|
||||
accessories: accessories,
|
||||
onTapAccessory: onTapAccessory,
|
||||
).positioned(right: 0),
|
||||
);
|
||||
}
|
||||
|
||||
return MouseRegion(
|
||||
@@ -4,10 +4,9 @@ import 'package:app_flowy/plugins/grid/application/field/field_controller.dart';
|
||||
import 'package:app_flowy/plugins/grid/presentation/layout/sizes.dart';
|
||||
import 'package:app_flowy/plugins/grid/presentation/widgets/toolbar/grid_group.dart';
|
||||
import 'package:app_flowy/plugins/grid/presentation/widgets/toolbar/grid_property.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/image.dart';
|
||||
import 'package:flowy_infra/theme.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/button.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
@@ -50,7 +49,6 @@ class BoardSettingList extends StatelessWidget {
|
||||
previous.selectedAction != current.selectedAction,
|
||||
listener: (context, state) {
|
||||
state.selectedAction.foldLeft(null, (_, action) {
|
||||
FlowyOverlay.of(context).remove(identifier());
|
||||
onAction(action, settingContext);
|
||||
});
|
||||
},
|
||||
@@ -84,43 +82,6 @@ class BoardSettingList extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
static void show(BuildContext context, BoardSettingContext settingContext) {
|
||||
final list = BoardSettingList(
|
||||
settingContext: settingContext,
|
||||
onAction: (action, settingContext) {
|
||||
switch (action) {
|
||||
case BoardSettingAction.properties:
|
||||
GridPropertyList(
|
||||
gridId: settingContext.viewId,
|
||||
fieldController: settingContext.fieldController)
|
||||
.show(context);
|
||||
break;
|
||||
case BoardSettingAction.groups:
|
||||
GridGroupList(
|
||||
viewId: settingContext.viewId,
|
||||
fieldController: settingContext.fieldController)
|
||||
.show(context);
|
||||
break;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
FlowyOverlay.of(context).insertWithAnchor(
|
||||
widget: OverlayContainer(
|
||||
constraints: BoxConstraints.loose(const Size(140, 400)),
|
||||
child: list,
|
||||
),
|
||||
identifier: identifier(),
|
||||
anchorContext: context,
|
||||
anchorDirection: AnchorDirection.bottomRight,
|
||||
style: FlowyOverlayStyle(blur: false),
|
||||
);
|
||||
}
|
||||
|
||||
static String identifier() {
|
||||
return (BoardSettingList).toString();
|
||||
}
|
||||
}
|
||||
|
||||
class _SettingItem extends StatelessWidget {
|
||||
@@ -133,7 +94,6 @@ class _SettingItem extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = context.read<AppTheme>();
|
||||
final isSelected = context
|
||||
.read<BoardSettingBloc>()
|
||||
.state
|
||||
@@ -144,15 +104,16 @@ class _SettingItem extends StatelessWidget {
|
||||
height: 30,
|
||||
child: FlowyButton(
|
||||
isSelected: isSelected,
|
||||
text: FlowyText.medium(action.title(),
|
||||
fontSize: 12, color: theme.textColor),
|
||||
hoverColor: theme.hover,
|
||||
text: FlowyText.medium(action.title()),
|
||||
onTap: () {
|
||||
context
|
||||
.read<BoardSettingBloc>()
|
||||
.add(BoardSettingEvent.performAction(action));
|
||||
},
|
||||
leftIcon: svgWidget(action.iconName(), color: theme.iconColor),
|
||||
leftIcon: svgWidget(
|
||||
action.iconName(),
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -177,3 +138,49 @@ extension _GridSettingExtension on BoardSettingAction {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class BoardSettingListPopover extends StatefulWidget {
|
||||
final PopoverController popoverController;
|
||||
final BoardSettingContext settingContext;
|
||||
|
||||
const BoardSettingListPopover({
|
||||
Key? key,
|
||||
required this.popoverController,
|
||||
required this.settingContext,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => _BoardSettingListPopoverState();
|
||||
}
|
||||
|
||||
class _BoardSettingListPopoverState extends State<BoardSettingListPopover> {
|
||||
BoardSettingAction? _action;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (_action != null) {
|
||||
switch (_action!) {
|
||||
case BoardSettingAction.groups:
|
||||
return GridGroupList(
|
||||
viewId: widget.settingContext.viewId,
|
||||
fieldController: widget.settingContext.fieldController,
|
||||
onDismissed: () {
|
||||
widget.popoverController.close();
|
||||
},
|
||||
);
|
||||
case BoardSettingAction.properties:
|
||||
return GridPropertyList(
|
||||
gridId: widget.settingContext.viewId,
|
||||
fieldController: widget.settingContext.fieldController,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return BoardSettingList(
|
||||
settingContext: widget.settingContext,
|
||||
onAction: (action, settingContext) {
|
||||
setState(() => _action = action);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
import 'package:app_flowy/plugins/grid/application/field/field_controller.dart';
|
||||
import 'package:flowy_infra/image.dart';
|
||||
import 'package:flowy_infra/theme.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/color_extension.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/button.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../../generated/locale_keys.g.dart';
|
||||
import 'board_setting.dart';
|
||||
|
||||
class BoardToolbarContext {
|
||||
@@ -30,6 +32,7 @@ class BoardToolbar extends StatelessWidget {
|
||||
height: 40,
|
||||
child: Row(
|
||||
children: [
|
||||
const Spacer(),
|
||||
_SettingButton(
|
||||
settingContext: BoardSettingContext.from(toolbarContext),
|
||||
),
|
||||
@@ -39,22 +42,47 @@ class BoardToolbar extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class _SettingButton extends StatelessWidget {
|
||||
class _SettingButton extends StatefulWidget {
|
||||
final BoardSettingContext settingContext;
|
||||
const _SettingButton({required this.settingContext, Key? key})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
State<_SettingButton> createState() => _SettingButtonState();
|
||||
}
|
||||
|
||||
class _SettingButtonState extends State<_SettingButton> {
|
||||
late PopoverController popoverController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
popoverController = PopoverController();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = context.read<AppTheme>();
|
||||
return FlowyIconButton(
|
||||
hoverColor: theme.hover,
|
||||
width: 22,
|
||||
onPressed: () => BoardSettingList.show(context, settingContext),
|
||||
icon: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 3.0, horizontal: 3.0),
|
||||
child: svgWidget("grid/setting/setting"),
|
||||
return AppFlowyPopover(
|
||||
controller: popoverController,
|
||||
direction: PopoverDirection.leftWithTopAligned,
|
||||
triggerActions: PopoverTriggerFlags.none,
|
||||
constraints: BoxConstraints.loose(const Size(260, 400)),
|
||||
child: FlowyTextButton(
|
||||
LocaleKeys.settings_title.tr(),
|
||||
fontSize: 14,
|
||||
fillColor: Colors.transparent,
|
||||
hoverColor: AFThemeExtension.of(context).lightGreyHover,
|
||||
padding: const EdgeInsets.symmetric(vertical: 2, horizontal: 6),
|
||||
onPressed: () {
|
||||
popoverController.show();
|
||||
},
|
||||
),
|
||||
popupBuilder: (BuildContext popoverContext) {
|
||||
return BoardSettingListPopover(
|
||||
settingContext: widget.settingContext,
|
||||
popoverController: popoverController,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// import 'package:flutter_test/flutter_test.dart';
|
||||
// import 'package:integration_test/integration_test.dart';
|
||||
// import 'package:app_flowy/main.dart' as app;
|
||||
|
||||
// void main() {
|
||||
// IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
// group('end-to-end test', () {
|
||||
// testWidgets('tap on the floating action button, verify counter',
|
||||
// (tester) async {
|
||||
// app.main();
|
||||
|
||||
// await tester.pumpAndSettle();
|
||||
// });
|
||||
// });
|
||||
// }
|
||||
@@ -1,29 +0,0 @@
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:flowy_sdk/dispatch/dispatch.dart';
|
||||
|
||||
import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-sync/text_block.pb.dart';
|
||||
|
||||
class DocumentService {
|
||||
Future<Either<TextBlockDeltaPB, FlowyError>> openDocument({
|
||||
required String docId,
|
||||
}) async {
|
||||
await FolderEventSetLatestView(ViewIdPB(value: docId)).send();
|
||||
|
||||
final payload = TextBlockIdPB(value: docId);
|
||||
return TextBlockEventGetBlockData(payload).send();
|
||||
}
|
||||
|
||||
Future<Either<TextBlockDeltaPB, FlowyError>> composeDelta({required String docId, required String data}) {
|
||||
final payload = TextBlockDeltaPB.create()
|
||||
..blockId = docId
|
||||
..deltaStr = data;
|
||||
return TextBlockEventApplyDelta(payload).send();
|
||||
}
|
||||
|
||||
Future<Either<Unit, FlowyError>> closeDocument({required String docId}) {
|
||||
final request = ViewIdPB(value: docId);
|
||||
return FolderEventCloseView(request).send();
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
import 'dart:async';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:flowy_sdk/dispatch/dispatch.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-text-block/protobuf.dart';
|
||||
|
||||
class ShareService {
|
||||
Future<Either<ExportDataPB, FlowyError>> export(String docId, ExportType type) {
|
||||
final request = ExportPayloadPB.create()
|
||||
..viewId = docId
|
||||
..exportType = type;
|
||||
|
||||
return TextBlockEventExportDocument(request).send();
|
||||
}
|
||||
|
||||
Future<Either<ExportDataPB, FlowyError>> exportText(String docId) {
|
||||
return export(docId, ExportType.Text);
|
||||
}
|
||||
|
||||
Future<Either<ExportDataPB, FlowyError>> exportMarkdown(String docId) {
|
||||
return export(docId, ExportType.Markdown);
|
||||
}
|
||||
|
||||
Future<Either<ExportDataPB, FlowyError>> exportURL(String docId) {
|
||||
return export(docId, ExportType.Link);
|
||||
}
|
||||
}
|
||||
@@ -1,263 +0,0 @@
|
||||
library document_plugin;
|
||||
|
||||
import 'package:app_flowy/generated/locale_keys.g.dart';
|
||||
import 'package:app_flowy/startup/plugin/plugin.dart';
|
||||
import 'package:app_flowy/startup/startup.dart';
|
||||
import 'package:app_flowy/workspace/application/appearance.dart';
|
||||
import 'package:app_flowy/workspace/application/view/view_listener.dart';
|
||||
import 'package:app_flowy/plugins/doc/application/share_bloc.dart';
|
||||
import 'package:app_flowy/workspace/presentation/home/home_stack.dart';
|
||||
import 'package:app_flowy/workspace/presentation/home/toast.dart';
|
||||
import 'package:app_flowy/workspace/presentation/widgets/left_bar_item.dart';
|
||||
import 'package:app_flowy/workspace/presentation/widgets/dialogs.dart';
|
||||
import 'package:app_flowy/workspace/presentation/widgets/pop_up_action.dart';
|
||||
import 'package:clipboard/clipboard.dart';
|
||||
import 'package:dartz/dartz.dart' as dartz;
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/notifier.dart';
|
||||
import 'package:flowy_infra/size.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flowy_infra_ui/widget/rounded_button.dart';
|
||||
import 'package:flowy_sdk/log.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-text-block/entities.pb.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'document_page.dart';
|
||||
|
||||
class DocumentPluginBuilder extends PluginBuilder {
|
||||
@override
|
||||
Plugin build(dynamic data) {
|
||||
if (data is ViewPB) {
|
||||
return DocumentPlugin(pluginType: pluginType, view: data);
|
||||
} else {
|
||||
throw FlowyPluginException.invalidData;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
String get menuName => LocaleKeys.document_menuName.tr();
|
||||
|
||||
@override
|
||||
PluginType get pluginType => PluginType.editor;
|
||||
|
||||
@override
|
||||
ViewDataTypePB get dataType => ViewDataTypePB.Text;
|
||||
}
|
||||
|
||||
class DocumentPlugin implements Plugin {
|
||||
late ViewPB _view;
|
||||
ViewListener? _listener;
|
||||
late PluginType _pluginType;
|
||||
|
||||
DocumentPlugin(
|
||||
{required PluginType pluginType, required ViewPB view, Key? key})
|
||||
: _view = view {
|
||||
_pluginType = pluginType;
|
||||
_listener = getIt<ViewListener>(param1: view);
|
||||
_listener?.start(onViewUpdated: (result) {
|
||||
result.fold(
|
||||
(newView) {
|
||||
_view = newView;
|
||||
display.notifier!.value = _view.hashCode;
|
||||
},
|
||||
(error) {},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_listener?.stop();
|
||||
_listener = null;
|
||||
}
|
||||
|
||||
@override
|
||||
PluginDisplay<int> get display => DocumentPluginDisplay(view: _view);
|
||||
|
||||
@override
|
||||
PluginType get ty => _pluginType;
|
||||
|
||||
@override
|
||||
PluginId get id => _view.id;
|
||||
}
|
||||
|
||||
class DocumentPluginDisplay extends PluginDisplay<int> with NavigationItem {
|
||||
final PublishNotifier<int> _displayNotifier = PublishNotifier<int>();
|
||||
final ViewPB _view;
|
||||
|
||||
DocumentPluginDisplay({required ViewPB view, Key? key}) : _view = view;
|
||||
|
||||
@override
|
||||
Widget buildWidget() => DocumentPage(view: _view, key: ValueKey(_view.id));
|
||||
|
||||
@override
|
||||
Widget get leftBarItem => ViewLeftBarItem(view: _view);
|
||||
|
||||
@override
|
||||
Widget? get rightBarItem => DocumentShareButton(view: _view);
|
||||
|
||||
@override
|
||||
List<NavigationItem> get navigationItems => [this];
|
||||
|
||||
@override
|
||||
PublishNotifier<int>? get notifier => _displayNotifier;
|
||||
}
|
||||
|
||||
class DocumentShareButton extends StatelessWidget {
|
||||
final ViewPB view;
|
||||
DocumentShareButton({Key? key, required this.view})
|
||||
: super(key: ValueKey(view.hashCode));
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
double buttonWidth = 60;
|
||||
return BlocProvider(
|
||||
create: (context) => getIt<DocShareBloc>(param1: view),
|
||||
child: BlocListener<DocShareBloc, DocShareState>(
|
||||
listener: (context, state) {
|
||||
state.map(
|
||||
initial: (_) {},
|
||||
loading: (_) {},
|
||||
finish: (state) {
|
||||
state.successOrFail.fold(
|
||||
_handleExportData,
|
||||
_handleExportError,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
child: BlocBuilder<DocShareBloc, DocShareState>(
|
||||
builder: (context, state) {
|
||||
return ChangeNotifierProvider.value(
|
||||
value: Provider.of<AppearanceSettingModel>(context, listen: true),
|
||||
child: Selector<AppearanceSettingModel, Locale>(
|
||||
selector: (ctx, notifier) => notifier.locale,
|
||||
builder: (ctx, _, child) => ConstrainedBox(
|
||||
constraints: const BoxConstraints.expand(
|
||||
height: 30,
|
||||
// minWidth: buttonWidth,
|
||||
width: 100,
|
||||
),
|
||||
child: RoundedTextButton(
|
||||
title: LocaleKeys.shareAction_buttonText.tr(),
|
||||
fontSize: 12,
|
||||
borderRadius: Corners.s6Border,
|
||||
color: Colors.lightBlue,
|
||||
onPressed: () => _showActionList(
|
||||
context, Offset(-(buttonWidth / 2), 10)),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _handleExportData(ExportDataPB exportData) {
|
||||
switch (exportData.exportType) {
|
||||
case ExportType.Link:
|
||||
break;
|
||||
case ExportType.Markdown:
|
||||
FlutterClipboard.copy(exportData.data)
|
||||
.then((value) => Log.info('copied to clipboard'));
|
||||
break;
|
||||
case ExportType.Text:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void _handleExportError(FlowyError error) {}
|
||||
|
||||
void _showActionList(BuildContext context, Offset offset) {
|
||||
final actionList = ShareActions(onSelected: (result) {
|
||||
result.fold(() {}, (action) {
|
||||
switch (action) {
|
||||
case ShareAction.markdown:
|
||||
context
|
||||
.read<DocShareBloc>()
|
||||
.add(const DocShareEvent.shareMarkdown());
|
||||
showMessageToast(
|
||||
'Exported to: ${LocaleKeys.notifications_export_path.tr()}');
|
||||
break;
|
||||
case ShareAction.copyLink:
|
||||
FlowyAlertDialog(title: LocaleKeys.shareAction_workInProgress.tr())
|
||||
.show(context);
|
||||
break;
|
||||
}
|
||||
});
|
||||
});
|
||||
actionList.show(
|
||||
context,
|
||||
anchorDirection: AnchorDirection.bottomWithCenterAligned,
|
||||
anchorOffset: offset,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ShareActions with ActionList<ShareActionWrapper>, FlowyOverlayDelegate {
|
||||
final Function(dartz.Option<ShareAction>) onSelected;
|
||||
final _items =
|
||||
ShareAction.values.map((action) => ShareActionWrapper(action)).toList();
|
||||
|
||||
ShareActions({required this.onSelected});
|
||||
|
||||
@override
|
||||
double get maxWidth => 130;
|
||||
|
||||
@override
|
||||
double get itemHeight => 22;
|
||||
|
||||
@override
|
||||
List<ShareActionWrapper> get items => _items;
|
||||
|
||||
@override
|
||||
void Function(dartz.Option<ShareActionWrapper> p1) get selectCallback =>
|
||||
(result) {
|
||||
result.fold(
|
||||
() => onSelected(dartz.none()),
|
||||
(wrapper) => onSelected(
|
||||
dartz.some(wrapper.inner),
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
@override
|
||||
FlowyOverlayDelegate? get delegate => this;
|
||||
|
||||
@override
|
||||
void didRemove() => onSelected(dartz.none());
|
||||
}
|
||||
|
||||
enum ShareAction {
|
||||
markdown,
|
||||
copyLink,
|
||||
}
|
||||
|
||||
class ShareActionWrapper extends ActionItem {
|
||||
final ShareAction inner;
|
||||
|
||||
ShareActionWrapper(this.inner);
|
||||
|
||||
@override
|
||||
Widget? get icon => null;
|
||||
|
||||
@override
|
||||
String get name => inner.name;
|
||||
}
|
||||
|
||||
extension QuestionBubbleExtension on ShareAction {
|
||||
String get name {
|
||||
switch (this) {
|
||||
case ShareAction.markdown:
|
||||
return LocaleKeys.shareAction_markdown.tr();
|
||||
case ShareAction.copyLink:
|
||||
return LocaleKeys.shareAction_copyLink.tr();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,143 +0,0 @@
|
||||
import 'package:app_flowy/startup/startup.dart';
|
||||
import 'package:app_flowy/workspace/application/appearance.dart';
|
||||
import 'package:app_flowy/plugins/doc/presentation/banner.dart';
|
||||
import 'package:app_flowy/plugins/doc/presentation/toolbar/tool_bar.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/scrolling/styled_scroll_bar.dart';
|
||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||
import 'package:flutter_quill/flutter_quill.dart' as quill;
|
||||
import 'package:flowy_infra_ui/widget/error_page.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'application/doc_bloc.dart';
|
||||
import 'styles.dart';
|
||||
|
||||
class DocumentPage extends StatefulWidget {
|
||||
final ViewPB view;
|
||||
|
||||
DocumentPage({Key? key, required this.view}) : super(key: ValueKey(view.id));
|
||||
|
||||
@override
|
||||
State<DocumentPage> createState() => _DocumentPageState();
|
||||
}
|
||||
|
||||
class _DocumentPageState extends State<DocumentPage> {
|
||||
late DocumentBloc documentBloc;
|
||||
final scrollController = ScrollController();
|
||||
final FocusNode _focusNode = FocusNode();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
documentBloc = getIt<DocumentBloc>(param1: super.widget.view)
|
||||
..add(const DocumentEvent.initial());
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider<DocumentBloc>.value(value: documentBloc),
|
||||
],
|
||||
child:
|
||||
BlocBuilder<DocumentBloc, DocumentState>(builder: (context, state) {
|
||||
return state.loadingState.map(
|
||||
// loading: (_) => const FlowyProgressIndicator(),
|
||||
loading: (_) =>
|
||||
SizedBox.expand(child: Container(color: Colors.transparent)),
|
||||
finish: (result) => result.successOrFail.fold(
|
||||
(_) {
|
||||
if (state.forceClose) {
|
||||
return _renderAppPage();
|
||||
} else {
|
||||
return _renderDocument(context, state);
|
||||
}
|
||||
},
|
||||
(err) => FlowyErrorPage(err.toString()),
|
||||
),
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> dispose() async {
|
||||
documentBloc.close();
|
||||
_focusNode.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Widget _renderDocument(BuildContext context, DocumentState state) {
|
||||
quill.QuillController controller = quill.QuillController(
|
||||
document: context.read<DocumentBloc>().document,
|
||||
selection: const TextSelection.collapsed(offset: 0),
|
||||
);
|
||||
return Column(
|
||||
children: [
|
||||
if (state.isDeleted) _renderBanner(context),
|
||||
Expanded(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
_renderEditor(controller),
|
||||
const VSpace(10),
|
||||
_renderToolbar(controller),
|
||||
const VSpace(10),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _renderBanner(BuildContext context) {
|
||||
return DocumentBanner(
|
||||
onRestore: () =>
|
||||
context.read<DocumentBloc>().add(const DocumentEvent.restorePage()),
|
||||
onDelete: () => context
|
||||
.read<DocumentBloc>()
|
||||
.add(const DocumentEvent.deletePermanently()),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _renderEditor(quill.QuillController controller) {
|
||||
final editor = quill.QuillEditor(
|
||||
controller: controller,
|
||||
focusNode: _focusNode,
|
||||
scrollable: true,
|
||||
paintCursorAboveText: true,
|
||||
autoFocus: controller.document.isEmpty(),
|
||||
expands: false,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
readOnly: false,
|
||||
scrollBottomInset: 0,
|
||||
scrollController: scrollController,
|
||||
customStyles: customStyles(context),
|
||||
);
|
||||
|
||||
return Expanded(
|
||||
child: ScrollbarListStack(
|
||||
axis: Axis.vertical,
|
||||
controller: scrollController,
|
||||
barSize: 6.0,
|
||||
child: SizedBox.expand(child: editor),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _renderToolbar(quill.QuillController controller) {
|
||||
return ChangeNotifierProvider.value(
|
||||
value: Provider.of<AppearanceSettingModel>(context, listen: true),
|
||||
child: EditorToolbar.basic(
|
||||
controller: controller,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _renderAppPage() {
|
||||
return Container(
|
||||
color: Colors.black,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
import 'package:flowy_infra/image.dart';
|
||||
import 'package:flowy_infra/theme.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_quill/flutter_quill.dart';
|
||||
|
||||
class StyleWidgetBuilder {
|
||||
static QuillCheckboxBuilder checkbox(AppTheme theme) {
|
||||
return EditorCheckboxBuilder(theme);
|
||||
}
|
||||
}
|
||||
|
||||
class EditorCheckboxBuilder extends QuillCheckboxBuilder {
|
||||
final AppTheme theme;
|
||||
|
||||
EditorCheckboxBuilder(this.theme);
|
||||
|
||||
@override
|
||||
Widget build(
|
||||
{required BuildContext context,
|
||||
required bool isChecked,
|
||||
required ValueChanged<bool> onChanged}) {
|
||||
return FlowyEditorCheckbox(
|
||||
theme: theme,
|
||||
isChecked: isChecked,
|
||||
onChanged: onChanged,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class FlowyEditorCheckbox extends StatefulWidget {
|
||||
final bool isChecked;
|
||||
final ValueChanged<bool> onChanged;
|
||||
final AppTheme theme;
|
||||
const FlowyEditorCheckbox({
|
||||
required this.theme,
|
||||
required this.isChecked,
|
||||
required this.onChanged,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
FlowyEditorCheckboxState createState() => FlowyEditorCheckboxState();
|
||||
}
|
||||
|
||||
class FlowyEditorCheckboxState extends State<FlowyEditorCheckbox> {
|
||||
late bool isChecked;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
isChecked = widget.isChecked;
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final icon = isChecked
|
||||
? svgWidget('editor/editor_check')
|
||||
: svgWidget('editor/editor_uncheck');
|
||||
return Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: FlowyIconButton(
|
||||
onPressed: () {
|
||||
isChecked = !isChecked;
|
||||
widget.onChanged(isChecked);
|
||||
setState(() {});
|
||||
},
|
||||
iconPadding: EdgeInsets.zero,
|
||||
icon: icon,
|
||||
width: 23,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
import 'package:flutter_quill/flutter_quill.dart';
|
||||
import 'package:flutter_quill/models/documents/style.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'toolbar_icon_button.dart';
|
||||
|
||||
class FlowyCheckListButton extends StatefulWidget {
|
||||
const FlowyCheckListButton({
|
||||
required this.controller,
|
||||
required this.attribute,
|
||||
required this.tooltipText,
|
||||
this.iconSize = defaultIconSize,
|
||||
this.fillColor,
|
||||
this.childBuilder = defaultToggleStyleButtonBuilder,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
final double iconSize;
|
||||
|
||||
final Color? fillColor;
|
||||
|
||||
final QuillController controller;
|
||||
|
||||
final ToggleStyleButtonBuilder childBuilder;
|
||||
|
||||
final Attribute attribute;
|
||||
|
||||
final String tooltipText;
|
||||
|
||||
@override
|
||||
FlowyCheckListButtonState createState() => FlowyCheckListButtonState();
|
||||
}
|
||||
|
||||
class FlowyCheckListButtonState extends State<FlowyCheckListButton> {
|
||||
bool? _isToggled;
|
||||
|
||||
Style get _selectionStyle => widget.controller.getSelectionStyle();
|
||||
|
||||
void _didChangeEditingValue() {
|
||||
setState(() {
|
||||
_isToggled =
|
||||
_getIsToggled(widget.controller.getSelectionStyle().attributes);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_isToggled = _getIsToggled(_selectionStyle.attributes);
|
||||
widget.controller.addListener(_didChangeEditingValue);
|
||||
}
|
||||
|
||||
bool _getIsToggled(Map<String, Attribute> attrs) {
|
||||
if (widget.attribute.key == Attribute.list.key) {
|
||||
final attribute = attrs[widget.attribute.key];
|
||||
if (attribute == null) {
|
||||
return false;
|
||||
}
|
||||
return attribute.value == widget.attribute.value ||
|
||||
attribute.value == Attribute.checked.value;
|
||||
}
|
||||
return attrs.containsKey(widget.attribute.key);
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant FlowyCheckListButton oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (oldWidget.controller != widget.controller) {
|
||||
oldWidget.controller.removeListener(_didChangeEditingValue);
|
||||
widget.controller.addListener(_didChangeEditingValue);
|
||||
_isToggled = _getIsToggled(_selectionStyle.attributes);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
widget.controller.removeListener(_didChangeEditingValue);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ToolbarIconButton(
|
||||
onPressed: _toggleAttribute,
|
||||
width: widget.iconSize * kIconButtonFactor,
|
||||
iconName: 'editor/checkbox',
|
||||
isToggled: _isToggled ?? false,
|
||||
tooltipText: widget.tooltipText,
|
||||
);
|
||||
}
|
||||
|
||||
void _toggleAttribute() {
|
||||
widget.controller.formatSelection(_isToggled!
|
||||
? Attribute.clone(Attribute.unchecked, null)
|
||||
: Attribute.unchecked);
|
||||
}
|
||||
}
|
||||
@@ -1,280 +0,0 @@
|
||||
import 'package:flowy_infra_ui/widget/dialog/styled_dialogs.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_quill/flutter_quill.dart';
|
||||
import 'package:flutter_quill/models/documents/style.dart';
|
||||
import 'package:flutter_quill/utils/color.dart';
|
||||
import 'package:app_flowy/generated/locale_keys.g.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'toolbar_icon_button.dart';
|
||||
|
||||
class FlowyColorButton extends StatefulWidget {
|
||||
const FlowyColorButton({
|
||||
required this.icon,
|
||||
required this.controller,
|
||||
required this.background,
|
||||
this.iconSize = defaultIconSize,
|
||||
this.iconTheme,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
final IconData icon;
|
||||
final double iconSize;
|
||||
final bool background;
|
||||
final QuillController controller;
|
||||
final QuillIconTheme? iconTheme;
|
||||
|
||||
@override
|
||||
FlowyColorButtonState createState() => FlowyColorButtonState();
|
||||
}
|
||||
|
||||
class FlowyColorButtonState extends State<FlowyColorButton> {
|
||||
late bool _isToggledColor;
|
||||
late bool _isToggledBackground;
|
||||
late bool _isWhite;
|
||||
late bool _isWhitebackground;
|
||||
|
||||
Style get _selectionStyle => widget.controller.getSelectionStyle();
|
||||
|
||||
void _didChangeEditingValue() {
|
||||
setState(() {
|
||||
_isToggledColor =
|
||||
_getIsToggledColor(widget.controller.getSelectionStyle().attributes);
|
||||
_isToggledBackground = _getIsToggledBackground(
|
||||
widget.controller.getSelectionStyle().attributes);
|
||||
_isWhite = _isToggledColor &&
|
||||
_selectionStyle.attributes['color']!.value == '#ffffff';
|
||||
_isWhitebackground = _isToggledBackground &&
|
||||
_selectionStyle.attributes['background']!.value == '#ffffff';
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_isToggledColor = _getIsToggledColor(_selectionStyle.attributes);
|
||||
_isToggledBackground = _getIsToggledBackground(_selectionStyle.attributes);
|
||||
_isWhite = _isToggledColor &&
|
||||
_selectionStyle.attributes['color']!.value == '#ffffff';
|
||||
_isWhitebackground = _isToggledBackground &&
|
||||
_selectionStyle.attributes['background']!.value == '#ffffff';
|
||||
widget.controller.addListener(_didChangeEditingValue);
|
||||
}
|
||||
|
||||
bool _getIsToggledColor(Map<String, Attribute> attrs) {
|
||||
return attrs.containsKey(Attribute.color.key);
|
||||
}
|
||||
|
||||
bool _getIsToggledBackground(Map<String, Attribute> attrs) {
|
||||
return attrs.containsKey(Attribute.background.key);
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant FlowyColorButton oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (oldWidget.controller != widget.controller) {
|
||||
oldWidget.controller.removeListener(_didChangeEditingValue);
|
||||
widget.controller.addListener(_didChangeEditingValue);
|
||||
_isToggledColor = _getIsToggledColor(_selectionStyle.attributes);
|
||||
_isToggledBackground =
|
||||
_getIsToggledBackground(_selectionStyle.attributes);
|
||||
_isWhite = _isToggledColor &&
|
||||
_selectionStyle.attributes['color']!.value == '#ffffff';
|
||||
_isWhitebackground = _isToggledBackground &&
|
||||
_selectionStyle.attributes['background']!.value == '#ffffff';
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
widget.controller.removeListener(_didChangeEditingValue);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
|
||||
final fillColor = _isToggledColor && !widget.background && _isWhite
|
||||
? stringToColor('#ffffff')
|
||||
: (widget.iconTheme?.iconUnselectedFillColor ?? theme.canvasColor);
|
||||
final fillColorBackground =
|
||||
_isToggledBackground && widget.background && _isWhitebackground
|
||||
? stringToColor('#ffffff')
|
||||
: (widget.iconTheme?.iconUnselectedFillColor ?? theme.canvasColor);
|
||||
|
||||
return Tooltip(
|
||||
message: LocaleKeys.toolbar_highlight.tr(),
|
||||
showDuration: Duration.zero,
|
||||
child: QuillIconButton(
|
||||
highlightElevation: 0,
|
||||
hoverElevation: 0,
|
||||
size: widget.iconSize * kIconButtonFactor,
|
||||
icon: Icon(widget.icon,
|
||||
size: widget.iconSize, color: theme.iconTheme.color),
|
||||
fillColor: widget.background ? fillColorBackground : fillColor,
|
||||
onPressed: _showColorPicker,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _changeColor(BuildContext context, Color color) {
|
||||
var hex = color.value.toRadixString(16);
|
||||
if (hex.startsWith('ff')) {
|
||||
hex = hex.substring(2);
|
||||
}
|
||||
hex = '#$hex';
|
||||
widget.controller.formatSelection(
|
||||
widget.background ? BackgroundAttribute(hex) : ColorAttribute(hex));
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
|
||||
void _showColorPicker() {
|
||||
final style = widget.controller.getSelectionStyle();
|
||||
final values = style.values
|
||||
.where((v) => v.key == Attribute.background.key)
|
||||
.map((v) => v.value);
|
||||
int initialColor = 0;
|
||||
if (values.isNotEmpty) {
|
||||
assert(values.length == 1);
|
||||
initialColor = stringToHex(values.first);
|
||||
}
|
||||
|
||||
StyledDialog(
|
||||
child: SingleChildScrollView(
|
||||
child: FlowyColorPicker(
|
||||
onColorChanged: (color) {
|
||||
if (color == null) {
|
||||
widget.controller.formatSelection(BackgroundAttribute(null));
|
||||
Navigator.of(context).pop();
|
||||
} else {
|
||||
_changeColor(context, color);
|
||||
}
|
||||
},
|
||||
initialColor: initialColor,
|
||||
),
|
||||
),
|
||||
).show(context);
|
||||
}
|
||||
}
|
||||
|
||||
int stringToHex(String code) {
|
||||
return int.parse(code.substring(1, 7), radix: 16) + 0xFF000000;
|
||||
}
|
||||
|
||||
class FlowyColorPicker extends StatefulWidget {
|
||||
final List<int> colors = [
|
||||
0xffe8e0ff,
|
||||
0xffffe7fd,
|
||||
0xffffe7ee,
|
||||
0xffffefe3,
|
||||
0xfffff2cd,
|
||||
0xfff5ffdc,
|
||||
0xffddffd6,
|
||||
0xffdefff1,
|
||||
];
|
||||
final Function(Color?) onColorChanged;
|
||||
final int initialColor;
|
||||
FlowyColorPicker(
|
||||
{Key? key, required this.onColorChanged, this.initialColor = 0})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
State<FlowyColorPicker> createState() => _FlowyColorPickerState();
|
||||
}
|
||||
|
||||
// if (shrinkWrap) {
|
||||
// innerContent = IntrinsicWidth(child: IntrinsicHeight(child: innerContent));
|
||||
// }
|
||||
class _FlowyColorPickerState extends State<FlowyColorPicker> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
const double width = 480;
|
||||
const int crossAxisCount = 6;
|
||||
const double mainAxisSpacing = 10;
|
||||
const double crossAxisSpacing = 10;
|
||||
final numberOfRows = (widget.colors.length / crossAxisCount).ceil();
|
||||
|
||||
const perRowHeight =
|
||||
((width - ((crossAxisCount - 1) * mainAxisSpacing)) / crossAxisCount);
|
||||
final totalHeight =
|
||||
numberOfRows * perRowHeight + numberOfRows * crossAxisSpacing;
|
||||
|
||||
return Container(
|
||||
constraints: BoxConstraints.tightFor(width: width, height: totalHeight),
|
||||
child: CustomScrollView(
|
||||
scrollDirection: Axis.vertical,
|
||||
controller: ScrollController(),
|
||||
physics: const ClampingScrollPhysics(),
|
||||
slivers: [
|
||||
SliverGrid(
|
||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: crossAxisCount,
|
||||
mainAxisSpacing: mainAxisSpacing,
|
||||
crossAxisSpacing: crossAxisSpacing,
|
||||
childAspectRatio: 1.0,
|
||||
),
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(BuildContext context, int index) {
|
||||
if (widget.colors.length > index) {
|
||||
final isSelected =
|
||||
widget.colors[index] == widget.initialColor;
|
||||
return ColorItem(
|
||||
color: Color(widget.colors[index]),
|
||||
onPressed: widget.onColorChanged,
|
||||
isSelected: isSelected,
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
childCount: widget.colors.length,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ColorItem extends StatelessWidget {
|
||||
final Function(Color?) onPressed;
|
||||
final bool isSelected;
|
||||
final Color color;
|
||||
const ColorItem({
|
||||
Key? key,
|
||||
required this.color,
|
||||
required this.onPressed,
|
||||
this.isSelected = false,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (!isSelected) {
|
||||
return RawMaterialButton(
|
||||
onPressed: () {
|
||||
onPressed(color);
|
||||
},
|
||||
elevation: 0,
|
||||
hoverElevation: 0.6,
|
||||
fillColor: color,
|
||||
shape: const CircleBorder(),
|
||||
);
|
||||
} else {
|
||||
return RawMaterialButton(
|
||||
shape: const CircleBorder(
|
||||
side: BorderSide(color: Colors.white, width: 8)) +
|
||||
CircleBorder(side: BorderSide(color: color, width: 4)),
|
||||
onPressed: () {
|
||||
if (isSelected) {
|
||||
onPressed(null);
|
||||
} else {
|
||||
onPressed(color);
|
||||
}
|
||||
},
|
||||
elevation: 1.0,
|
||||
hoverElevation: 0.6,
|
||||
fillColor: color,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
import 'package:flutter_quill/flutter_quill.dart';
|
||||
import 'package:flutter_quill/models/documents/style.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:app_flowy/generated/locale_keys.g.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'toolbar_icon_button.dart';
|
||||
|
||||
class FlowyHeaderStyleButton extends StatefulWidget {
|
||||
const FlowyHeaderStyleButton({
|
||||
required this.controller,
|
||||
this.iconSize = defaultIconSize,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
final QuillController controller;
|
||||
final double iconSize;
|
||||
|
||||
@override
|
||||
FlowyHeaderStyleButtonState createState() => FlowyHeaderStyleButtonState();
|
||||
}
|
||||
|
||||
class FlowyHeaderStyleButtonState extends State<FlowyHeaderStyleButton> {
|
||||
Attribute? _value;
|
||||
|
||||
Style get _selectionStyle => widget.controller.getSelectionStyle();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
setState(() {
|
||||
_value =
|
||||
_selectionStyle.attributes[Attribute.header.key] ?? Attribute.header;
|
||||
});
|
||||
widget.controller.addListener(_didChangeEditingValue);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final valueToText = <Attribute, String>{
|
||||
Attribute.h1: 'H1',
|
||||
Attribute.h2: 'H2',
|
||||
Attribute.h3: 'H3',
|
||||
};
|
||||
|
||||
final valueAttribute = <Attribute>[
|
||||
Attribute.h1,
|
||||
Attribute.h2,
|
||||
Attribute.h3
|
||||
];
|
||||
final valueString = <String>['H1', 'H2', 'H3'];
|
||||
final attributeImageName = <String>['editor/H1', 'editor/H2', 'editor/H3'];
|
||||
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: List.generate(3, (index) {
|
||||
// final child =
|
||||
// _valueToText[_value] == _valueString[index] ? svg('editor/H1', color: Colors.white) : svg('editor/H1');
|
||||
|
||||
final headerTitle = "${LocaleKeys.toolbar_header.tr()} ${index + 1}";
|
||||
final isToggled = valueToText[_value] == valueString[index];
|
||||
return ToolbarIconButton(
|
||||
onPressed: () {
|
||||
if (isToggled) {
|
||||
widget.controller.formatSelection(Attribute.header);
|
||||
} else {
|
||||
widget.controller.formatSelection(valueAttribute[index]);
|
||||
}
|
||||
},
|
||||
width: widget.iconSize * kIconButtonFactor,
|
||||
iconName: attributeImageName[index],
|
||||
isToggled: isToggled,
|
||||
tooltipText: headerTitle,
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
void _didChangeEditingValue() {
|
||||
setState(() {
|
||||
_value =
|
||||
_selectionStyle.attributes[Attribute.header.key] ?? Attribute.header;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant FlowyHeaderStyleButton oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (oldWidget.controller != widget.controller) {
|
||||
oldWidget.controller.removeListener(_didChangeEditingValue);
|
||||
widget.controller.addListener(_didChangeEditingValue);
|
||||
_value =
|
||||
_selectionStyle.attributes[Attribute.header.key] ?? Attribute.header;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
widget.controller.removeListener(_didChangeEditingValue);
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_quill/flutter_quill.dart';
|
||||
|
||||
class FlowyHistoryButton extends StatelessWidget {
|
||||
final IconData icon;
|
||||
final double iconSize;
|
||||
final bool undo;
|
||||
final QuillController controller;
|
||||
final String tooltipText;
|
||||
|
||||
const FlowyHistoryButton({
|
||||
required this.icon,
|
||||
required this.controller,
|
||||
required this.undo,
|
||||
required this.tooltipText,
|
||||
required this.iconSize,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Tooltip(
|
||||
message: tooltipText,
|
||||
showDuration: Duration.zero,
|
||||
child: HistoryButton(
|
||||
icon: icon,
|
||||
iconSize: iconSize,
|
||||
controller: controller,
|
||||
undo: undo,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
import 'package:flutter_quill/flutter_quill.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'toolbar_icon_button.dart';
|
||||
|
||||
class FlowyImageButton extends StatelessWidget {
|
||||
const FlowyImageButton({
|
||||
required this.controller,
|
||||
required this.tooltipText,
|
||||
this.iconSize = defaultIconSize,
|
||||
this.onImagePickCallback,
|
||||
this.fillColor,
|
||||
this.filePickImpl,
|
||||
this.webImagePickImpl,
|
||||
this.mediaPickSettingSelector,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
final double iconSize;
|
||||
|
||||
final Color? fillColor;
|
||||
|
||||
final QuillController controller;
|
||||
|
||||
final OnImagePickCallback? onImagePickCallback;
|
||||
|
||||
final WebImagePickImpl? webImagePickImpl;
|
||||
|
||||
final FilePickImpl? filePickImpl;
|
||||
|
||||
final MediaPickSettingSelector? mediaPickSettingSelector;
|
||||
|
||||
final String tooltipText;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ToolbarIconButton(
|
||||
iconName: 'editor/image',
|
||||
width: iconSize * 1.77,
|
||||
onPressed: () => _onPressedHandler(context),
|
||||
isToggled: false,
|
||||
tooltipText: tooltipText,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onPressedHandler(BuildContext context) async {
|
||||
// if (onImagePickCallback != null) {
|
||||
// final selector = mediaPickSettingSelector ?? ImageVideoUtils.selectMediaPickSetting;
|
||||
// final source = await selector(context);
|
||||
// if (source != null) {
|
||||
// if (source == MediaPickSetting.Gallery) {
|
||||
// _pickImage(context);
|
||||
// } else {
|
||||
// _typeLink(context);
|
||||
// }
|
||||
// }
|
||||
// } else {
|
||||
// _typeLink(context);
|
||||
// }
|
||||
}
|
||||
|
||||
// void _pickImage(BuildContext context) => ImageVideoUtils.handleImageButtonTap(
|
||||
// context,
|
||||
// controller,
|
||||
// ImageSource.gallery,
|
||||
// onImagePickCallback!,
|
||||
// filePickImpl: filePickImpl,
|
||||
// webImagePickImpl: webImagePickImpl,
|
||||
// );
|
||||
|
||||
// void _typeLink(BuildContext context) {
|
||||
// TextFieldDialog(
|
||||
// title: 'URL',
|
||||
// value: "",
|
||||
// confirm: (newValue) {
|
||||
// if (newValue.isEmpty) {
|
||||
// return;
|
||||
// }
|
||||
// final index = controller.selection.baseOffset;
|
||||
// final length = controller.selection.extentOffset - index;
|
||||
|
||||
// controller.replaceText(index, length, BlockEmbed.image(newValue), null);
|
||||
// },
|
||||
// ).show(context);
|
||||
// }
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
import 'package:app_flowy/workspace/presentation/widgets/dialogs.dart';
|
||||
import 'package:flutter_quill/flutter_quill.dart';
|
||||
import 'package:flowy_infra/image.dart';
|
||||
import 'package:flowy_infra/theme.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'toolbar_icon_button.dart';
|
||||
|
||||
class FlowyLinkStyleButton extends StatefulWidget {
|
||||
const FlowyLinkStyleButton({
|
||||
required this.controller,
|
||||
this.iconSize = defaultIconSize,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
final QuillController controller;
|
||||
final double iconSize;
|
||||
|
||||
@override
|
||||
FlowyLinkStyleButtonState createState() => FlowyLinkStyleButtonState();
|
||||
}
|
||||
|
||||
class FlowyLinkStyleButtonState extends State<FlowyLinkStyleButton> {
|
||||
void _didChangeSelection() {
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
widget.controller.addListener(_didChangeSelection);
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant FlowyLinkStyleButton oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (oldWidget.controller != widget.controller) {
|
||||
oldWidget.controller.removeListener(_didChangeSelection);
|
||||
widget.controller.addListener(_didChangeSelection);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
widget.controller.removeListener(_didChangeSelection);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = context.watch<AppTheme>();
|
||||
final isEnabled = !widget.controller.selection.isCollapsed;
|
||||
final pressedHandler = isEnabled ? () => _openLinkDialog(context) : null;
|
||||
final icon = isEnabled
|
||||
? svgWidget(
|
||||
'editor/share',
|
||||
color: theme.iconColor,
|
||||
)
|
||||
: svgWidget(
|
||||
'editor/share',
|
||||
color: theme.disableIconColor,
|
||||
);
|
||||
|
||||
return FlowyIconButton(
|
||||
onPressed: pressedHandler,
|
||||
iconPadding: const EdgeInsets.symmetric(horizontal: 4, vertical: 4),
|
||||
icon: icon,
|
||||
fillColor: theme.shader6,
|
||||
hoverColor: theme.shader5,
|
||||
width: widget.iconSize * kIconButtonFactor,
|
||||
);
|
||||
}
|
||||
|
||||
void _openLinkDialog(BuildContext context) {
|
||||
final style = widget.controller.getSelectionStyle();
|
||||
final values = style.values
|
||||
.where((v) => v.key == Attribute.link.key)
|
||||
.map((v) => v.value);
|
||||
String value = "";
|
||||
if (values.isNotEmpty) {
|
||||
assert(values.length == 1);
|
||||
value = values.first;
|
||||
}
|
||||
|
||||
TextFieldDialog(
|
||||
title: 'URL',
|
||||
value: value,
|
||||
confirm: (newValue) {
|
||||
if (newValue.isEmpty) {
|
||||
return;
|
||||
}
|
||||
widget.controller.formatSelection(LinkAttribute(newValue));
|
||||
},
|
||||
).show(context);
|
||||
}
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
import 'package:flutter_quill/flutter_quill.dart';
|
||||
import 'package:flutter_quill/models/documents/style.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'toolbar_icon_button.dart';
|
||||
|
||||
class FlowyToggleStyleButton extends StatefulWidget {
|
||||
final Attribute attribute;
|
||||
final String normalIcon;
|
||||
final double iconSize;
|
||||
final QuillController controller;
|
||||
final String tooltipText;
|
||||
|
||||
const FlowyToggleStyleButton({
|
||||
required this.attribute,
|
||||
required this.normalIcon,
|
||||
required this.controller,
|
||||
required this.tooltipText,
|
||||
this.iconSize = defaultIconSize,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
ToggleStyleButtonState createState() => ToggleStyleButtonState();
|
||||
}
|
||||
|
||||
class ToggleStyleButtonState extends State<FlowyToggleStyleButton> {
|
||||
bool? _isToggled;
|
||||
Style get _selectionStyle => widget.controller.getSelectionStyle();
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_isToggled = _getIsToggled(_selectionStyle.attributes);
|
||||
widget.controller.addListener(_didChangeEditingValue);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ToolbarIconButton(
|
||||
onPressed: _toggleAttribute,
|
||||
width: widget.iconSize * kIconButtonFactor,
|
||||
isToggled: _isToggled ?? false,
|
||||
iconName: widget.normalIcon,
|
||||
tooltipText: widget.tooltipText,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant FlowyToggleStyleButton oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (oldWidget.controller != widget.controller) {
|
||||
oldWidget.controller.removeListener(_didChangeEditingValue);
|
||||
widget.controller.addListener(_didChangeEditingValue);
|
||||
_isToggled = _getIsToggled(_selectionStyle.attributes);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
widget.controller.removeListener(_didChangeEditingValue);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _didChangeEditingValue() {
|
||||
setState(() => _isToggled = _getIsToggled(_selectionStyle.attributes));
|
||||
}
|
||||
|
||||
bool _getIsToggled(Map<String, Attribute> attrs) {
|
||||
if (widget.attribute.key == Attribute.list.key) {
|
||||
final attribute = attrs[widget.attribute.key];
|
||||
if (attribute == null) {
|
||||
return false;
|
||||
}
|
||||
return attribute.value == widget.attribute.value;
|
||||
}
|
||||
return attrs.containsKey(widget.attribute.key);
|
||||
}
|
||||
|
||||
void _toggleAttribute() {
|
||||
widget.controller.formatSelection(_isToggled!
|
||||
? Attribute.clone(widget.attribute, null)
|
||||
: widget.attribute);
|
||||
}
|
||||
}
|
||||
@@ -1,308 +0,0 @@
|
||||
import 'dart:async';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:app_flowy/workspace/presentation/widgets/emoji_picker/emoji_picker.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter_quill/flutter_quill.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
import 'check_button.dart';
|
||||
import 'color_picker.dart';
|
||||
import 'header_button.dart';
|
||||
import 'history_button.dart';
|
||||
import 'link_button.dart';
|
||||
import 'toggle_button.dart';
|
||||
import 'toolbar_icon_button.dart';
|
||||
import 'package:app_flowy/generated/locale_keys.g.dart';
|
||||
|
||||
class EditorToolbar extends StatelessWidget implements PreferredSizeWidget {
|
||||
final List<Widget> children;
|
||||
final double toolBarHeight;
|
||||
final Color? color;
|
||||
|
||||
const EditorToolbar({
|
||||
required this.children,
|
||||
this.toolBarHeight = 46,
|
||||
this.color,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
color: Theme.of(context).canvasColor,
|
||||
constraints: BoxConstraints.tightFor(height: preferredSize.height),
|
||||
child: ToolbarButtonList(buttons: children)
|
||||
.padding(horizontal: 4, vertical: 4),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Size get preferredSize => Size.fromHeight(toolBarHeight);
|
||||
|
||||
factory EditorToolbar.basic({
|
||||
required QuillController controller,
|
||||
double toolbarIconSize = defaultIconSize,
|
||||
OnImagePickCallback? onImagePickCallback,
|
||||
OnVideoPickCallback? onVideoPickCallback,
|
||||
MediaPickSettingSelector? mediaPickSettingSelector,
|
||||
FilePickImpl? filePickImpl,
|
||||
WebImagePickImpl? webImagePickImpl,
|
||||
WebVideoPickImpl? webVideoPickImpl,
|
||||
Key? key,
|
||||
}) {
|
||||
return EditorToolbar(
|
||||
key: key,
|
||||
toolBarHeight: toolbarIconSize * 2,
|
||||
children: [
|
||||
FlowyHistoryButton(
|
||||
icon: Icons.undo_outlined,
|
||||
iconSize: toolbarIconSize,
|
||||
controller: controller,
|
||||
undo: true,
|
||||
tooltipText: LocaleKeys.toolbar_undo.tr(),
|
||||
),
|
||||
FlowyHistoryButton(
|
||||
icon: Icons.redo_outlined,
|
||||
iconSize: toolbarIconSize,
|
||||
controller: controller,
|
||||
undo: false,
|
||||
tooltipText: LocaleKeys.toolbar_redo.tr(),
|
||||
),
|
||||
FlowyToggleStyleButton(
|
||||
attribute: Attribute.bold,
|
||||
normalIcon: 'editor/bold',
|
||||
iconSize: toolbarIconSize,
|
||||
controller: controller,
|
||||
tooltipText: LocaleKeys.toolbar_bold.tr(),
|
||||
),
|
||||
FlowyToggleStyleButton(
|
||||
attribute: Attribute.italic,
|
||||
normalIcon: 'editor/italic',
|
||||
iconSize: toolbarIconSize,
|
||||
controller: controller,
|
||||
tooltipText: LocaleKeys.toolbar_italic.tr(),
|
||||
),
|
||||
FlowyToggleStyleButton(
|
||||
attribute: Attribute.underline,
|
||||
normalIcon: 'editor/underline',
|
||||
iconSize: toolbarIconSize,
|
||||
controller: controller,
|
||||
tooltipText: LocaleKeys.toolbar_underline.tr(),
|
||||
),
|
||||
FlowyToggleStyleButton(
|
||||
attribute: Attribute.strikeThrough,
|
||||
normalIcon: 'editor/strikethrough',
|
||||
iconSize: toolbarIconSize,
|
||||
controller: controller,
|
||||
tooltipText: LocaleKeys.toolbar_strike.tr(),
|
||||
),
|
||||
FlowyColorButton(
|
||||
icon: Icons.format_color_fill,
|
||||
iconSize: toolbarIconSize,
|
||||
controller: controller,
|
||||
background: true,
|
||||
),
|
||||
// FlowyImageButton(
|
||||
// iconSize: toolbarIconSize,
|
||||
// controller: controller,
|
||||
// onImagePickCallback: onImagePickCallback,
|
||||
// filePickImpl: filePickImpl,
|
||||
// webImagePickImpl: webImagePickImpl,
|
||||
// mediaPickSettingSelector: mediaPickSettingSelector,
|
||||
// ),
|
||||
FlowyHeaderStyleButton(
|
||||
controller: controller,
|
||||
iconSize: toolbarIconSize,
|
||||
),
|
||||
FlowyToggleStyleButton(
|
||||
attribute: Attribute.ol,
|
||||
controller: controller,
|
||||
normalIcon: 'editor/numbers',
|
||||
iconSize: toolbarIconSize,
|
||||
tooltipText: LocaleKeys.toolbar_numList.tr(),
|
||||
),
|
||||
FlowyToggleStyleButton(
|
||||
attribute: Attribute.ul,
|
||||
controller: controller,
|
||||
normalIcon: 'editor/bullet_list',
|
||||
iconSize: toolbarIconSize,
|
||||
tooltipText: LocaleKeys.toolbar_bulletList.tr(),
|
||||
),
|
||||
FlowyCheckListButton(
|
||||
attribute: Attribute.unchecked,
|
||||
controller: controller,
|
||||
iconSize: toolbarIconSize,
|
||||
tooltipText: LocaleKeys.toolbar_checkList.tr(),
|
||||
),
|
||||
FlowyToggleStyleButton(
|
||||
attribute: Attribute.inlineCode,
|
||||
controller: controller,
|
||||
normalIcon: 'editor/inline_block',
|
||||
iconSize: toolbarIconSize,
|
||||
tooltipText: LocaleKeys.toolbar_inlineCode.tr(),
|
||||
),
|
||||
FlowyToggleStyleButton(
|
||||
attribute: Attribute.blockQuote,
|
||||
controller: controller,
|
||||
normalIcon: 'editor/quote',
|
||||
iconSize: toolbarIconSize,
|
||||
tooltipText: LocaleKeys.toolbar_quote.tr(),
|
||||
),
|
||||
FlowyLinkStyleButton(
|
||||
controller: controller,
|
||||
iconSize: toolbarIconSize,
|
||||
),
|
||||
FlowyEmojiStyleButton(
|
||||
normalIcon: 'editor/insert_emoticon',
|
||||
controller: controller,
|
||||
tooltipText: "Emoji Picker",
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ToolbarButtonList extends StatefulWidget {
|
||||
const ToolbarButtonList({required this.buttons, Key? key}) : super(key: key);
|
||||
|
||||
final List<Widget> buttons;
|
||||
|
||||
@override
|
||||
ToolbarButtonListState createState() => ToolbarButtonListState();
|
||||
}
|
||||
|
||||
class ToolbarButtonListState extends State<ToolbarButtonList>
|
||||
with WidgetsBindingObserver {
|
||||
final ScrollController _controller = ScrollController();
|
||||
bool _showLeftArrow = false;
|
||||
bool _showRightArrow = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller.addListener(_handleScroll);
|
||||
|
||||
// Listening to the WidgetsBinding instance is necessary so that we can
|
||||
// hide the arrows when the window gets a new size and thus the toolbar
|
||||
// becomes scrollable/unscrollable.
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
|
||||
// Workaround to allow the scroll controller attach to our ListView so that
|
||||
// we can detect if overflow arrows need to be shown on init.
|
||||
Timer.run(_handleScroll);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return LayoutBuilder(
|
||||
builder: (BuildContext context, BoxConstraints constraints) {
|
||||
List<Widget> children = [];
|
||||
double width =
|
||||
(widget.buttons.length + 2) * defaultIconSize * kIconButtonFactor;
|
||||
final isFit = constraints.maxWidth > width;
|
||||
if (!isFit) {
|
||||
children.add(_buildLeftArrow());
|
||||
width = width + 18;
|
||||
}
|
||||
|
||||
children.add(_buildScrollableList(constraints, isFit));
|
||||
|
||||
if (!isFit) {
|
||||
children.add(_buildRightArrow());
|
||||
width = width + 18;
|
||||
}
|
||||
|
||||
return SizedBox(
|
||||
width: min(constraints.maxWidth, width),
|
||||
child: Row(
|
||||
children: children,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeMetrics() => _handleScroll();
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
WidgetsBinding.instance.removeObserver(this);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _handleScroll() {
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_showLeftArrow =
|
||||
_controller.position.minScrollExtent != _controller.position.pixels;
|
||||
_showRightArrow =
|
||||
_controller.position.maxScrollExtent != _controller.position.pixels;
|
||||
});
|
||||
}
|
||||
|
||||
Widget _buildLeftArrow() {
|
||||
return SizedBox(
|
||||
width: 8,
|
||||
child: Transform.translate(
|
||||
// Move the icon a few pixels to center it
|
||||
offset: const Offset(-5, 0),
|
||||
child: _showLeftArrow ? const Icon(Icons.arrow_left, size: 18) : null,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// [[sliver: https://medium.com/flutter/slivers-demystified-6ff68ab0296f]]
|
||||
Widget _buildScrollableList(BoxConstraints constraints, bool isFit) {
|
||||
Widget child = Expanded(
|
||||
child: CustomScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
controller: _controller,
|
||||
physics: const ClampingScrollPhysics(),
|
||||
slivers: [
|
||||
SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(BuildContext context, int index) {
|
||||
return widget.buttons[index];
|
||||
},
|
||||
childCount: widget.buttons.length,
|
||||
addAutomaticKeepAlives: false,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
if (!isFit) {
|
||||
child = ScrollConfiguration(
|
||||
// Remove the glowing effect, as we already have the arrow indicators
|
||||
behavior: _NoGlowBehavior(),
|
||||
// The CustomScrollView is necessary so that the children are not
|
||||
// stretched to the height of the toolbar, https://bit.ly/3uC3bjI
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
return child;
|
||||
}
|
||||
|
||||
Widget _buildRightArrow() {
|
||||
return SizedBox(
|
||||
width: 8,
|
||||
child: Transform.translate(
|
||||
// Move the icon a few pixels to center it
|
||||
offset: const Offset(-5, 0),
|
||||
child: _showRightArrow ? const Icon(Icons.arrow_right, size: 18) : null,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _NoGlowBehavior extends ScrollBehavior {
|
||||
@override
|
||||
Widget buildViewportChrome(BuildContext _, Widget child, AxisDirection __) {
|
||||
return child;
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
import 'package:flowy_infra/image.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flowy_infra/theme.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
const double defaultIconSize = 18;
|
||||
|
||||
class ToolbarIconButton extends StatelessWidget {
|
||||
final double width;
|
||||
final VoidCallback? onPressed;
|
||||
final bool isToggled;
|
||||
final String iconName;
|
||||
final String tooltipText;
|
||||
|
||||
const ToolbarIconButton({
|
||||
Key? key,
|
||||
required this.onPressed,
|
||||
required this.isToggled,
|
||||
required this.width,
|
||||
required this.iconName,
|
||||
required this.tooltipText,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = context.watch<AppTheme>();
|
||||
return FlowyIconButton(
|
||||
iconPadding: const EdgeInsets.symmetric(horizontal: 4, vertical: 4),
|
||||
onPressed: onPressed,
|
||||
width: width,
|
||||
icon: isToggled == true ? svgWidget(iconName, color: Colors.white) : svgWidget(iconName, color: theme.iconColor),
|
||||
fillColor: isToggled == true ? theme.main1 : theme.shader6,
|
||||
hoverColor: isToggled == true ? theme.main1 : theme.hover,
|
||||
tooltipText: tooltipText,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,133 +0,0 @@
|
||||
import 'package:app_flowy/plugins/doc/presentation/style_widgets.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_quill/flutter_quill.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
import 'package:flowy_infra/theme.dart';
|
||||
|
||||
DefaultStyles customStyles(BuildContext context) {
|
||||
const baseSpacing = Tuple2<double, double>(6, 0);
|
||||
|
||||
final theme = context.watch<AppTheme>();
|
||||
final themeData = theme.themeData;
|
||||
final fontFamily = makeFontFamily(themeData);
|
||||
|
||||
final defaultTextStyle = DefaultTextStyle.of(context);
|
||||
final baseStyle = defaultTextStyle.style.copyWith(
|
||||
fontSize: 18,
|
||||
height: 1.3,
|
||||
fontWeight: FontWeight.w300,
|
||||
letterSpacing: 0.6,
|
||||
fontFamily: fontFamily,
|
||||
);
|
||||
|
||||
return DefaultStyles(
|
||||
h1: DefaultTextBlockStyle(
|
||||
defaultTextStyle.style.copyWith(
|
||||
fontSize: 34,
|
||||
color: defaultTextStyle.style.color!.withOpacity(0.70),
|
||||
height: 1.15,
|
||||
fontWeight: FontWeight.w300,
|
||||
),
|
||||
const Tuple2(16, 0),
|
||||
const Tuple2(0, 0),
|
||||
null),
|
||||
h2: DefaultTextBlockStyle(
|
||||
defaultTextStyle.style.copyWith(
|
||||
fontSize: 24,
|
||||
color: defaultTextStyle.style.color!.withOpacity(0.70),
|
||||
height: 1.15,
|
||||
fontWeight: FontWeight.normal,
|
||||
),
|
||||
const Tuple2(8, 0),
|
||||
const Tuple2(0, 0),
|
||||
null),
|
||||
h3: DefaultTextBlockStyle(
|
||||
defaultTextStyle.style.copyWith(
|
||||
fontSize: 20,
|
||||
color: defaultTextStyle.style.color!.withOpacity(0.70),
|
||||
height: 1.25,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
const Tuple2(8, 0),
|
||||
const Tuple2(0, 0),
|
||||
null),
|
||||
paragraph: DefaultTextBlockStyle(
|
||||
baseStyle, const Tuple2(10, 0), const Tuple2(0, 0), null),
|
||||
bold: const TextStyle(fontWeight: FontWeight.bold),
|
||||
italic: const TextStyle(fontStyle: FontStyle.italic),
|
||||
small: const TextStyle(fontSize: 12, color: Colors.black45),
|
||||
underline: const TextStyle(decoration: TextDecoration.underline),
|
||||
strikeThrough: const TextStyle(decoration: TextDecoration.lineThrough),
|
||||
inlineCode: TextStyle(
|
||||
color: Colors.blue.shade900.withOpacity(0.9),
|
||||
fontFamily: fontFamily,
|
||||
fontSize: 13,
|
||||
),
|
||||
link: TextStyle(
|
||||
color: themeData.colorScheme.secondary,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
color: theme.textColor,
|
||||
placeHolder: DefaultTextBlockStyle(
|
||||
defaultTextStyle.style.copyWith(
|
||||
fontSize: 20,
|
||||
height: 1.5,
|
||||
color: Colors.grey.withOpacity(0.6),
|
||||
),
|
||||
const Tuple2(0, 0),
|
||||
const Tuple2(0, 0),
|
||||
null),
|
||||
lists: DefaultListBlockStyle(baseStyle, baseSpacing, const Tuple2(0, 6),
|
||||
null, StyleWidgetBuilder.checkbox(theme)),
|
||||
quote: DefaultTextBlockStyle(
|
||||
TextStyle(color: baseStyle.color!.withOpacity(0.6)),
|
||||
baseSpacing,
|
||||
const Tuple2(6, 2),
|
||||
BoxDecoration(
|
||||
border: Border(
|
||||
left: BorderSide(width: 4, color: theme.shader5),
|
||||
),
|
||||
)),
|
||||
code: DefaultTextBlockStyle(
|
||||
TextStyle(
|
||||
color: Colors.blue.shade900.withOpacity(0.9),
|
||||
fontFamily: fontFamily,
|
||||
fontSize: 13,
|
||||
height: 1.15,
|
||||
),
|
||||
baseSpacing,
|
||||
const Tuple2(0, 0),
|
||||
BoxDecoration(
|
||||
color: Colors.grey.shade50,
|
||||
borderRadius: BorderRadius.circular(2),
|
||||
)),
|
||||
indent: DefaultTextBlockStyle(
|
||||
baseStyle, baseSpacing, const Tuple2(0, 6), null),
|
||||
align: DefaultTextBlockStyle(
|
||||
baseStyle, const Tuple2(0, 0), const Tuple2(0, 0), null),
|
||||
leading: DefaultTextBlockStyle(
|
||||
baseStyle, const Tuple2(0, 0), const Tuple2(0, 0), null),
|
||||
sizeSmall: const TextStyle(fontSize: 10),
|
||||
sizeLarge: const TextStyle(fontSize: 18),
|
||||
sizeHuge: const TextStyle(fontSize: 22));
|
||||
}
|
||||
|
||||
String makeFontFamily(ThemeData themeData) {
|
||||
String fontFamily;
|
||||
switch (themeData.platform) {
|
||||
case TargetPlatform.iOS:
|
||||
case TargetPlatform.macOS:
|
||||
fontFamily = 'Mulish';
|
||||
break;
|
||||
case TargetPlatform.android:
|
||||
case TargetPlatform.fuchsia:
|
||||
case TargetPlatform.windows:
|
||||
case TargetPlatform.linux:
|
||||
fontFamily = 'Roboto Mono';
|
||||
break;
|
||||
default:
|
||||
throw UnimplementedError();
|
||||
}
|
||||
return fontFamily;
|
||||
}
|
||||
+93
-59
@@ -1,11 +1,12 @@
|
||||
import 'dart:convert';
|
||||
import 'package:app_flowy/plugins/trash/application/trash_service.dart';
|
||||
import 'package:app_flowy/workspace/application/view/view_listener.dart';
|
||||
import 'package:app_flowy/plugins/doc/application/doc_service.dart';
|
||||
import 'package:app_flowy/plugins/document/application/doc_service.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart'
|
||||
show EditorState, Document, Transaction;
|
||||
import 'package:flowy_sdk/protobuf/flowy-folder/trash.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:flutter_quill/flutter_quill.dart' show Document, Delta;
|
||||
import 'package:flowy_sdk/log.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
@@ -14,27 +15,26 @@ import 'dart:async';
|
||||
|
||||
part 'doc_bloc.freezed.dart';
|
||||
|
||||
typedef FlutterQuillDocument = Document;
|
||||
|
||||
class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
|
||||
final ViewPB view;
|
||||
final DocumentService service;
|
||||
final DocumentService _documentService;
|
||||
|
||||
final ViewListener listener;
|
||||
final TrashService trashService;
|
||||
late FlutterQuillDocument document;
|
||||
final ViewListener _listener;
|
||||
final TrashService _trashService;
|
||||
late EditorState editorState;
|
||||
StreamSubscription? _subscription;
|
||||
|
||||
DocumentBloc({
|
||||
required this.view,
|
||||
required this.service,
|
||||
required this.listener,
|
||||
required this.trashService,
|
||||
}) : super(DocumentState.initial()) {
|
||||
}) : _documentService = DocumentService(),
|
||||
_listener = ViewListener(view: view),
|
||||
_trashService = TrashService(),
|
||||
super(DocumentState.initial()) {
|
||||
on<DocumentEvent>((event, emit) async {
|
||||
await event.map(
|
||||
initial: (Initial value) async {
|
||||
await _initial(value, emit);
|
||||
_listenOnViewChange();
|
||||
},
|
||||
deleted: (Deleted value) async {
|
||||
emit(state.copyWith(isDeleted: true));
|
||||
@@ -43,7 +43,7 @@ class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
|
||||
emit(state.copyWith(isDeleted: false));
|
||||
},
|
||||
deletePermanently: (DeletePermanently value) async {
|
||||
final result = await trashService
|
||||
final result = await _trashService
|
||||
.deleteViews([Tuple2(view.id, TrashType.TrashView)]);
|
||||
|
||||
final newState = result.fold(
|
||||
@@ -51,7 +51,7 @@ class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
|
||||
emit(newState);
|
||||
},
|
||||
restorePage: (RestorePage value) async {
|
||||
final result = await trashService.putback(view.id);
|
||||
final result = await _trashService.putback(view.id);
|
||||
final newState = result.fold(
|
||||
(l) => state.copyWith(isDeleted: false), (r) => state);
|
||||
emit(newState);
|
||||
@@ -62,18 +62,41 @@ class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
await listener.stop();
|
||||
await _listener.stop();
|
||||
|
||||
if (_subscription != null) {
|
||||
await _subscription?.cancel();
|
||||
}
|
||||
|
||||
await service.closeDocument(docId: view.id);
|
||||
await _documentService.closeDocument(docId: view.id);
|
||||
return super.close();
|
||||
}
|
||||
|
||||
Future<void> _initial(Initial value, Emitter<DocumentState> emit) async {
|
||||
listener.start(
|
||||
final result = await _documentService.openDocument(view: view);
|
||||
result.fold(
|
||||
(block) {
|
||||
final document = Document.fromJson(jsonDecode(block.snapshot));
|
||||
editorState = EditorState(document: document);
|
||||
_listenOnDocumentChange();
|
||||
emit(
|
||||
state.copyWith(
|
||||
loadingState: DocumentLoadingState.finish(left(unit)),
|
||||
),
|
||||
);
|
||||
},
|
||||
(err) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
loadingState: DocumentLoadingState.finish(right(err)),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _listenOnViewChange() {
|
||||
_listener.start(
|
||||
onViewDeleted: (result) {
|
||||
result.fold(
|
||||
(view) => add(const DocumentEvent.deleted()),
|
||||
@@ -87,50 +110,20 @@ class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
|
||||
);
|
||||
},
|
||||
);
|
||||
final result = await service.openDocument(docId: view.id);
|
||||
result.fold(
|
||||
(block) {
|
||||
document = _decodeJsonToDocument(block.deltaStr);
|
||||
_subscription = document.changes.listen((event) {
|
||||
final delta = event.item2;
|
||||
final documentDelta = document.toDelta();
|
||||
_composeDelta(delta, documentDelta);
|
||||
});
|
||||
emit(state.copyWith(
|
||||
loadingState: DocumentLoadingState.finish(left(unit))));
|
||||
},
|
||||
(err) {
|
||||
emit(state.copyWith(
|
||||
loadingState: DocumentLoadingState.finish(right(err))));
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Document _decodeListToDocument(Uint8List data) {
|
||||
// final json = jsonDecode(utf8.decode(data));
|
||||
// final document = Document.fromJson(json);
|
||||
// return document;
|
||||
// }
|
||||
|
||||
void _composeDelta(Delta composedDelta, Delta documentDelta) async {
|
||||
final json = jsonEncode(composedDelta.toJson());
|
||||
Log.debug("doc_id: $view.id - Send json: $json");
|
||||
final result = await service.composeDelta(docId: view.id, data: json);
|
||||
|
||||
result.fold((rustDoc) {
|
||||
// final json = utf8.decode(doc.data);
|
||||
final rustDelta = Delta.fromJson(jsonDecode(rustDoc.deltaStr));
|
||||
if (documentDelta != rustDelta) {
|
||||
Log.error("Receive : $rustDelta");
|
||||
Log.error("Expected : $documentDelta");
|
||||
}
|
||||
}, (r) => null);
|
||||
}
|
||||
|
||||
Document _decodeJsonToDocument(String data) {
|
||||
final json = jsonDecode(data);
|
||||
final document = Document.fromJson(json);
|
||||
return document;
|
||||
void _listenOnDocumentChange() {
|
||||
_subscription = editorState.transactionStream.listen((transaction) {
|
||||
final json = jsonEncode(TransactionAdaptor(transaction).toJson());
|
||||
_documentService
|
||||
.applyEdit(docId: view.id, operations: json)
|
||||
.then((result) {
|
||||
result.fold(
|
||||
(l) => null,
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,3 +157,44 @@ class DocumentLoadingState with _$DocumentLoadingState {
|
||||
const factory DocumentLoadingState.finish(
|
||||
Either<Unit, FlowyError> successOrFail) = _Finish;
|
||||
}
|
||||
|
||||
/// Uses to erase the different between appflowy editor and the backend
|
||||
class TransactionAdaptor {
|
||||
final Transaction transaction;
|
||||
TransactionAdaptor(this.transaction);
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
if (transaction.operations.isNotEmpty) {
|
||||
// The backend uses [0,0] as the beginning path, but the editor uses [0].
|
||||
// So it needs to extend the path by inserting `0` at the head for all
|
||||
// operations before passing to the backend.
|
||||
json['operations'] = transaction.operations
|
||||
.map((e) => e.copyWith(path: [0, ...e.path]).toJson())
|
||||
.toList();
|
||||
}
|
||||
if (transaction.afterSelection != null) {
|
||||
final selection = transaction.afterSelection!;
|
||||
final start = selection.start;
|
||||
final end = selection.end;
|
||||
json['after_selection'] = selection
|
||||
.copyWith(
|
||||
start: start.copyWith(path: [0, ...start.path]),
|
||||
end: end.copyWith(path: [0, ...end.path]),
|
||||
)
|
||||
.toJson();
|
||||
}
|
||||
if (transaction.beforeSelection != null) {
|
||||
final selection = transaction.beforeSelection!;
|
||||
final start = selection.start;
|
||||
final end = selection.end;
|
||||
json['before_selection'] = selection
|
||||
.copyWith(
|
||||
start: start.copyWith(path: [0, ...start.path]),
|
||||
end: end.copyWith(path: [0, ...end.path]),
|
||||
)
|
||||
.toJson();
|
||||
}
|
||||
return json;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:flowy_sdk/dispatch/dispatch.dart';
|
||||
|
||||
import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-document/entities.pb.dart';
|
||||
|
||||
class DocumentService {
|
||||
Future<Either<DocumentSnapshotPB, FlowyError>> openDocument({
|
||||
required ViewPB view,
|
||||
}) async {
|
||||
await FolderEventSetLatestView(ViewIdPB(value: view.id)).send();
|
||||
|
||||
final payload = OpenDocumentContextPB()
|
||||
..documentId = view.id
|
||||
..documentVersion = DocumentVersionPB.V1;
|
||||
// switch (view.dataFormat) {
|
||||
// case ViewDataFormatPB.DeltaFormat:
|
||||
// payload.documentVersion = DocumentVersionPB.V0;
|
||||
// break;
|
||||
// default:
|
||||
// break;
|
||||
// }
|
||||
|
||||
return DocumentEventGetDocument(payload).send();
|
||||
}
|
||||
|
||||
Future<Either<Unit, FlowyError>> applyEdit({
|
||||
required String docId,
|
||||
required String operations,
|
||||
}) {
|
||||
final payload = EditPayloadPB.create()
|
||||
..docId = docId
|
||||
..operations = operations;
|
||||
return DocumentEventApplyEdit(payload).send();
|
||||
}
|
||||
|
||||
Future<Either<Unit, FlowyError>> closeDocument({required String docId}) {
|
||||
final request = ViewIdPB(value: docId);
|
||||
return FolderEventCloseView(request).send();
|
||||
}
|
||||
}
|
||||
File renamed without changes.
+20
-32
@@ -1,14 +1,14 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'package:app_flowy/startup/tasks/rust_sdk.dart';
|
||||
import 'package:app_flowy/workspace/application/markdown/delta_markdown.dart';
|
||||
import 'package:app_flowy/plugins/doc/application/share_service.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-text-block/entities.pb.dart';
|
||||
import 'package:app_flowy/plugins/document/application/share_service.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-document/entities.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart'
|
||||
show Document, documentToMarkdown;
|
||||
part 'share_bloc.freezed.dart';
|
||||
|
||||
class DocShareBloc extends Bloc<DocShareEvent, DocShareState> {
|
||||
@@ -18,11 +18,14 @@ class DocShareBloc extends Bloc<DocShareEvent, DocShareState> {
|
||||
: super(const DocShareState.initial()) {
|
||||
on<DocShareEvent>((event, emit) async {
|
||||
await event.map(
|
||||
shareMarkdown: (ShareMarkdown value) async {
|
||||
await service.exportMarkdown(view.id).then((result) {
|
||||
shareMarkdown: (ShareMarkdown shareMarkdown) async {
|
||||
await service.exportMarkdown(view).then((result) {
|
||||
result.fold(
|
||||
(value) => emit(
|
||||
DocShareState.finish(left(_convertDeltaToMarkdown(value)))),
|
||||
DocShareState.finish(
|
||||
left(_saveMarkdown(value, shareMarkdown.path)),
|
||||
),
|
||||
),
|
||||
(error) => emit(DocShareState.finish(right(error))),
|
||||
);
|
||||
});
|
||||
@@ -35,38 +38,23 @@ class DocShareBloc extends Bloc<DocShareEvent, DocShareState> {
|
||||
});
|
||||
}
|
||||
|
||||
ExportDataPB _convertDeltaToMarkdown(ExportDataPB value) {
|
||||
final result = deltaToMarkdown(value.data);
|
||||
value.data = result;
|
||||
writeFile(result);
|
||||
ExportDataPB _saveMarkdown(ExportDataPB value, String path) {
|
||||
final markdown = _convertDocumentToMarkdown(value);
|
||||
value.data = markdown;
|
||||
File(path).writeAsStringSync(markdown);
|
||||
return value;
|
||||
}
|
||||
|
||||
Future<Directory> get _exportDir async {
|
||||
Directory documentsDir = await appFlowyDocumentDirectory();
|
||||
|
||||
return documentsDir;
|
||||
}
|
||||
|
||||
Future<String> get _localPath async {
|
||||
final dir = await _exportDir;
|
||||
return dir.path;
|
||||
}
|
||||
|
||||
Future<File> get _localFile async {
|
||||
final path = await _localPath;
|
||||
return File('$path/${view.name}.md');
|
||||
}
|
||||
|
||||
Future<File> writeFile(String md) async {
|
||||
final file = await _localFile;
|
||||
return file.writeAsString(md);
|
||||
String _convertDocumentToMarkdown(ExportDataPB value) {
|
||||
final json = jsonDecode(value.data);
|
||||
final document = Document.fromJson(json);
|
||||
return documentToMarkdown(document);
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class DocShareEvent with _$DocShareEvent {
|
||||
const factory DocShareEvent.shareMarkdown() = ShareMarkdown;
|
||||
const factory DocShareEvent.shareMarkdown(String path) = ShareMarkdown;
|
||||
const factory DocShareEvent.shareText() = ShareText;
|
||||
const factory DocShareEvent.shareLink() = ShareLink;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import 'dart:async';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:flowy_sdk/dispatch/dispatch.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-document/protobuf.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
|
||||
|
||||
class ShareService {
|
||||
Future<Either<ExportDataPB, FlowyError>> export(
|
||||
ViewPB view, ExportType type) {
|
||||
var payload = ExportPayloadPB.create()
|
||||
..viewId = view.id
|
||||
..exportType = type
|
||||
..documentVersion = DocumentVersionPB.V1;
|
||||
|
||||
return DocumentEventExportDocument(payload).send();
|
||||
}
|
||||
|
||||
Future<Either<ExportDataPB, FlowyError>> exportText(ViewPB view) {
|
||||
return export(view, ExportType.Text);
|
||||
}
|
||||
|
||||
Future<Either<ExportDataPB, FlowyError>> exportMarkdown(ViewPB view) {
|
||||
return export(view, ExportType.Markdown);
|
||||
}
|
||||
|
||||
Future<Either<ExportDataPB, FlowyError>> exportURL(ViewPB view) {
|
||||
return export(view, ExportType.Link);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
library document_plugin;
|
||||
|
||||
import 'package:app_flowy/generated/locale_keys.g.dart';
|
||||
import 'package:app_flowy/plugins/util.dart';
|
||||
import 'package:app_flowy/startup/plugin/plugin.dart';
|
||||
import 'package:app_flowy/startup/startup.dart';
|
||||
import 'package:app_flowy/plugins/document/application/share_bloc.dart';
|
||||
import 'package:app_flowy/workspace/presentation/home/home_stack.dart';
|
||||
import 'package:app_flowy/workspace/presentation/home/toast.dart';
|
||||
import 'package:app_flowy/workspace/presentation/widgets/left_bar_item.dart';
|
||||
import 'package:app_flowy/workspace/presentation/widgets/dialogs.dart';
|
||||
import 'package:app_flowy/workspace/presentation/widgets/pop_up_action.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:clipboard/clipboard.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flowy_infra_ui/widget/rounded_button.dart';
|
||||
import 'package:flowy_sdk/log.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-document/entities.pb.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import 'document_page.dart';
|
||||
|
||||
class DocumentPluginBuilder extends PluginBuilder {
|
||||
@override
|
||||
Plugin build(dynamic data) {
|
||||
if (data is ViewPB) {
|
||||
return DocumentPlugin(pluginType: pluginType, view: data);
|
||||
} else {
|
||||
throw FlowyPluginException.invalidData;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
String get menuName => LocaleKeys.document_menuName.tr();
|
||||
|
||||
@override
|
||||
String get menuIcon => "editor/documents";
|
||||
|
||||
@override
|
||||
PluginType get pluginType => PluginType.editor;
|
||||
|
||||
@override
|
||||
ViewDataFormatPB get dataFormatType => ViewDataFormatPB.TreeFormat;
|
||||
}
|
||||
|
||||
class DocumentPlugin extends Plugin<int> {
|
||||
late PluginType _pluginType;
|
||||
|
||||
@override
|
||||
final ViewPluginNotifier notifier;
|
||||
|
||||
DocumentPlugin({
|
||||
required PluginType pluginType,
|
||||
required ViewPB view,
|
||||
Key? key,
|
||||
}) : notifier = ViewPluginNotifier(view: view) {
|
||||
_pluginType = pluginType;
|
||||
}
|
||||
|
||||
@override
|
||||
PluginDisplay get display => DocumentPluginDisplay(notifier: notifier);
|
||||
|
||||
@override
|
||||
PluginType get ty => _pluginType;
|
||||
|
||||
@override
|
||||
PluginId get id => notifier.view.id;
|
||||
}
|
||||
|
||||
class DocumentPluginDisplay extends PluginDisplay with NavigationItem {
|
||||
final ViewPluginNotifier notifier;
|
||||
ViewPB get view => notifier.view;
|
||||
int? deletedViewIndex;
|
||||
|
||||
DocumentPluginDisplay({required this.notifier, Key? key});
|
||||
|
||||
@override
|
||||
Widget buildWidget(PluginContext context) {
|
||||
notifier.isDeleted.addListener(() {
|
||||
notifier.isDeleted.value.fold(() => null, (deletedView) {
|
||||
if (deletedView.hasIndex()) {
|
||||
deletedViewIndex = deletedView.index;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return DocumentPage(
|
||||
view: view,
|
||||
onDeleted: () => context.onDeleted(view, deletedViewIndex),
|
||||
key: ValueKey(view.id),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget get leftBarItem => ViewLeftBarItem(view: view);
|
||||
|
||||
@override
|
||||
Widget? get rightBarItem => DocumentShareButton(view: view);
|
||||
|
||||
@override
|
||||
List<NavigationItem> get navigationItems => [this];
|
||||
}
|
||||
|
||||
class DocumentShareButton extends StatelessWidget {
|
||||
final ViewPB view;
|
||||
DocumentShareButton({Key? key, required this.view})
|
||||
: super(key: ValueKey(view.hashCode));
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => getIt<DocShareBloc>(param1: view),
|
||||
child: BlocListener<DocShareBloc, DocShareState>(
|
||||
listener: (context, state) {
|
||||
state.map(
|
||||
initial: (_) {},
|
||||
loading: (_) {},
|
||||
finish: (state) {
|
||||
state.successOrFail.fold(
|
||||
_handleExportData,
|
||||
_handleExportError,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
child: BlocBuilder<DocShareBloc, DocShareState>(
|
||||
builder: (context, state) => ConstrainedBox(
|
||||
constraints: const BoxConstraints.expand(
|
||||
height: 30,
|
||||
width: 100,
|
||||
),
|
||||
child: ShareActionList(view: view),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _handleExportData(ExportDataPB exportData) {
|
||||
switch (exportData.exportType) {
|
||||
case ExportType.Link:
|
||||
break;
|
||||
case ExportType.Markdown:
|
||||
FlutterClipboard.copy(exportData.data)
|
||||
.then((value) => Log.info('copied to clipboard'));
|
||||
break;
|
||||
case ExportType.Text:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void _handleExportError(FlowyError error) {}
|
||||
}
|
||||
|
||||
class ShareActionList extends StatelessWidget {
|
||||
const ShareActionList({
|
||||
Key? key,
|
||||
required this.view,
|
||||
}) : super(key: key);
|
||||
|
||||
final ViewPB view;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final docShareBloc = context.read<DocShareBloc>();
|
||||
return PopoverActionList<ShareActionWrapper>(
|
||||
direction: PopoverDirection.bottomWithCenterAligned,
|
||||
actions: ShareAction.values
|
||||
.map((action) => ShareActionWrapper(action))
|
||||
.toList(),
|
||||
buildChild: (controller) {
|
||||
return RoundedTextButton(
|
||||
title: LocaleKeys.shareAction_buttonText.tr(),
|
||||
onPressed: () => controller.show(),
|
||||
);
|
||||
},
|
||||
onSelected: (action, controller) async {
|
||||
switch (action.inner) {
|
||||
case ShareAction.markdown:
|
||||
final exportPath = await FilePicker.platform.saveFile(
|
||||
dialogTitle: '',
|
||||
fileName: '${view.name}.md',
|
||||
);
|
||||
if (exportPath != null) {
|
||||
docShareBloc.add(DocShareEvent.shareMarkdown(exportPath));
|
||||
showMessageToast('Exported to: $exportPath');
|
||||
}
|
||||
break;
|
||||
case ShareAction.copyLink:
|
||||
NavigatorAlertDialog(
|
||||
title: LocaleKeys.shareAction_workInProgress.tr())
|
||||
.show(context);
|
||||
break;
|
||||
}
|
||||
controller.close();
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
enum ShareAction {
|
||||
markdown,
|
||||
copyLink,
|
||||
}
|
||||
|
||||
class ShareActionWrapper extends ActionCell {
|
||||
final ShareAction inner;
|
||||
|
||||
ShareActionWrapper(this.inner);
|
||||
|
||||
@override
|
||||
Widget? leftIcon(Color iconColor) => null;
|
||||
|
||||
@override
|
||||
String get name => inner.name;
|
||||
}
|
||||
|
||||
extension QuestionBubbleExtension on ShareAction {
|
||||
String get name {
|
||||
switch (this) {
|
||||
case ShareAction.markdown:
|
||||
return LocaleKeys.shareAction_markdown.tr();
|
||||
case ShareAction.copyLink:
|
||||
return LocaleKeys.shareAction_copyLink.tr();
|
||||
}
|
||||
}
|
||||
}
|
||||
Loaded 100 of 1206 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user