mirror of
https://github.com/diamondburned/cchat-gtk.git
synced 2024-12-23 04:36:49 +00:00
Added colors and stuff
This commit is contained in:
parent
b852498ee4
commit
0171ac6b52
5
go.mod
5
go.mod
|
@ -6,12 +6,11 @@ replace github.com/diamondburned/cchat-mock => ../cchat-mock/
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Xuanwo/go-locale v0.2.0
|
github.com/Xuanwo/go-locale v0.2.0
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
|
||||||
github.com/diamondburned/cchat v0.0.15
|
github.com/diamondburned/cchat v0.0.15
|
||||||
github.com/diamondburned/cchat-mock v0.0.0-20200604043646-de5384bd320d
|
github.com/diamondburned/cchat-mock v0.0.0-20200605224934-31a53c555ea2
|
||||||
github.com/goodsign/monday v1.0.0
|
github.com/goodsign/monday v1.0.0
|
||||||
github.com/gotk3/gotk3 v0.4.1-0.20200524052254-cb2aa31c6194
|
github.com/gotk3/gotk3 v0.4.1-0.20200524052254-cb2aa31c6194
|
||||||
|
github.com/markbates/pkger v0.17.0
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/zalando/go-keyring v0.0.0-20200121091418-667557018717
|
github.com/zalando/go-keyring v0.0.0-20200121091418-667557018717
|
||||||
golang.org/x/tools v0.0.0-20200529172331-a64b76657301 // indirect
|
|
||||||
)
|
)
|
||||||
|
|
36
go.sum
36
go.sum
|
@ -7,16 +7,12 @@ github.com/danieljoos/wincred v1.0.2/go.mod h1:SnuYRW9lp1oJrZX/dXJqr0cPK5gYXqx3E
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/diamondburned/cchat v0.0.10 h1:aiUVgGre5E/HV+Iw6tmBVbuGctQI+JndV9nIDoYuRPY=
|
|
||||||
github.com/diamondburned/cchat v0.0.10/go.mod h1:+zXktogE45A0om4fT6B/z6Ii7FXNafjxsNspI0rlhbU=
|
|
||||||
github.com/diamondburned/cchat v0.0.13 h1:p8SyFjiRVCTjvwSJ4FsICGVYVZ3g0Iu02FrwmLuKiKE=
|
|
||||||
github.com/diamondburned/cchat v0.0.13/go.mod h1:+zXktogE45A0om4fT6B/z6Ii7FXNafjxsNspI0rlhbU=
|
|
||||||
github.com/diamondburned/cchat v0.0.15 h1:1o4OX8zw/CdSv3Idaylz7vjHVOZKEi/xkg8BpEvtsHY=
|
github.com/diamondburned/cchat v0.0.15 h1:1o4OX8zw/CdSv3Idaylz7vjHVOZKEi/xkg8BpEvtsHY=
|
||||||
github.com/diamondburned/cchat v0.0.15/go.mod h1:+zXktogE45A0om4fT6B/z6Ii7FXNafjxsNspI0rlhbU=
|
github.com/diamondburned/cchat v0.0.15/go.mod h1:+zXktogE45A0om4fT6B/z6Ii7FXNafjxsNspI0rlhbU=
|
||||||
github.com/diamondburned/cchat-mock v0.0.0-20200529184140-47fa2491d2fc h1:xYSN3re1QOd5af5zG15pLKfBM+fergw7Rg62UHmE22g=
|
|
||||||
github.com/diamondburned/cchat-mock v0.0.0-20200529184140-47fa2491d2fc/go.mod h1:rQm5EKhNyBRYHKtirSkf+Db23nr3mTs2bnOThfTfzec=
|
|
||||||
github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI=
|
github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI=
|
||||||
github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
|
github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
|
||||||
|
github.com/gobuffalo/here v0.6.0 h1:hYrd0a6gDmWxBM4TnrGw8mQg24iSVoIkHEk7FodQcBI=
|
||||||
|
github.com/gobuffalo/here v0.6.0/go.mod h1:wAG085dHOYqUpf+Ap+WOdrPTp5IYcDAs/x7PLa8Y5fM=
|
||||||
github.com/godbus/dbus v4.1.0+incompatible h1:WqqLRTsQic3apZUK9qC5sGNfXthmPXzUZ7nQPrNITa4=
|
github.com/godbus/dbus v4.1.0+incompatible h1:WqqLRTsQic3apZUK9qC5sGNfXthmPXzUZ7nQPrNITa4=
|
||||||
github.com/godbus/dbus v4.1.0+incompatible/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
|
github.com/godbus/dbus v4.1.0+incompatible/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
|
||||||
github.com/goodsign/monday v1.0.0 h1:Yyk/s/WgudMbAJN6UWSU5xAs8jtNewfqtVblAlw0yoc=
|
github.com/goodsign/monday v1.0.0 h1:Yyk/s/WgudMbAJN6UWSU5xAs8jtNewfqtVblAlw0yoc=
|
||||||
|
@ -27,6 +23,13 @@ github.com/gotk3/gotk3 v0.4.1-0.20200524052254-cb2aa31c6194 h1:bB6XWpxMt2isCWqzj
|
||||||
github.com/gotk3/gotk3 v0.4.1-0.20200524052254-cb2aa31c6194/go.mod h1:/hqFpkNa9T3JgNAE2fLvCdov7c5bw//FHNZrZ3Uv9/Q=
|
github.com/gotk3/gotk3 v0.4.1-0.20200524052254-cb2aa31c6194/go.mod h1:/hqFpkNa9T3JgNAE2fLvCdov7c5bw//FHNZrZ3Uv9/Q=
|
||||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||||
|
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||||
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||||
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
github.com/markbates/pkger v0.17.0 h1:RFfyBPufP2V6cddUyyEVSHBpaAnM1WzaMNyqomeT+iY=
|
||||||
|
github.com/markbates/pkger v0.17.0/go.mod h1:0JoVlrol20BSywW79rN3kdFFsE5xYM+rSCQDXbLhiuI=
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
@ -39,35 +42,22 @@ github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
|
||||||
github.com/zalando/go-keyring v0.0.0-20200121091418-667557018717 h1:3M/uUZajYn/082wzUajekePxpUAZhMTfXvI9R+26SJ0=
|
github.com/zalando/go-keyring v0.0.0-20200121091418-667557018717 h1:3M/uUZajYn/082wzUajekePxpUAZhMTfXvI9R+26SJ0=
|
||||||
github.com/zalando/go-keyring v0.0.0-20200121091418-667557018717/go.mod h1:RaxNwUITJaHVdQ0VC7pELPZ3tOWn13nr0gZMZEhpVU0=
|
github.com/zalando/go-keyring v0.0.0-20200121091418-667557018717/go.mod h1:RaxNwUITJaHVdQ0VC7pELPZ3tOWn13nr0gZMZEhpVU0=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
|
||||||
golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ=
|
|
||||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|
||||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384 h1:TFlARGu6Czu1z7q93HTxcP1P+/ZFC/IKythI5RzrnRg=
|
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384 h1:TFlARGu6Czu1z7q93HTxcP1P+/ZFC/IKythI5RzrnRg=
|
||||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20200529172331-a64b76657301 h1:G6CNEgFU8/XwexSnuFw+Jq/WePjRitgy6ofBcPnAIPo=
|
|
||||||
golang.org/x/tools v0.0.0-20200529172331-a64b76657301/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo=
|
||||||
|
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
|
64
internal/gts/css.go
Normal file
64
internal/gts/css.go
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
package gts
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
|
||||||
|
"github.com/diamondburned/cchat-gtk/internal/log"
|
||||||
|
"github.com/gotk3/gotk3/gdk"
|
||||||
|
"github.com/gotk3/gotk3/gtk"
|
||||||
|
"github.com/markbates/pkger"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
var cssRepos = map[string]*gtk.CssProvider{}
|
||||||
|
|
||||||
|
func getDefaultScreen() *gdk.Screen {
|
||||||
|
d, _ := gdk.DisplayGetDefault()
|
||||||
|
s, _ := d.GetDefaultScreen()
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadProviders(screen *gdk.Screen) {
|
||||||
|
for file, repo := range cssRepos {
|
||||||
|
gtk.AddProviderForScreen(
|
||||||
|
screen, repo,
|
||||||
|
uint(gtk.STYLE_PROVIDER_PRIORITY_APPLICATION),
|
||||||
|
)
|
||||||
|
// mark as done
|
||||||
|
delete(cssRepos, file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadCSS(files ...string) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
for _, file := range files {
|
||||||
|
buf.Reset()
|
||||||
|
|
||||||
|
if err := readFile(&buf, file); err != nil {
|
||||||
|
log.Error(errors.Wrap(err, "Failed to load a CSS file"))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
prov, _ := gtk.CssProviderNew()
|
||||||
|
if err := prov.LoadFromData(buf.String()); err != nil {
|
||||||
|
log.Error(errors.Wrap(err, "Failed to parse CSS "+file))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
cssRepos[file] = prov
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func readFile(buf *bytes.Buffer, file string) error {
|
||||||
|
f, err := pkger.Open(file)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "Failed to load a CSS file")
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
if _, err := buf.ReadFrom(f); err != nil {
|
||||||
|
return errors.Wrap(err, "Failed to read file")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -9,6 +9,8 @@ import (
|
||||||
"github.com/gotk3/gotk3/gtk"
|
"github.com/gotk3/gotk3/gtk"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const AppID = "com.github.diamondburned.cchat-gtk"
|
||||||
|
|
||||||
var Args = append([]string{}, os.Args...)
|
var Args = append([]string{}, os.Args...)
|
||||||
var recvPool *sync.Pool
|
var recvPool *sync.Pool
|
||||||
|
|
||||||
|
@ -27,7 +29,7 @@ func init() {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
App.Application, _ = gtk.ApplicationNew("com.github.diamondburned.cchat-gtk", 0)
|
App.Application, _ = gtk.ApplicationNew(AppID, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Windower interface {
|
type Windower interface {
|
||||||
|
@ -47,10 +49,16 @@ type WindowHeaderer interface {
|
||||||
|
|
||||||
func Main(wfn func() WindowHeaderer) {
|
func Main(wfn func() WindowHeaderer) {
|
||||||
App.Application.Connect("activate", func() {
|
App.Application.Connect("activate", func() {
|
||||||
|
// Load all CSS onto the default screen.
|
||||||
|
loadProviders(getDefaultScreen())
|
||||||
|
|
||||||
App.Header, _ = gtk.HeaderBarNew()
|
App.Header, _ = gtk.HeaderBarNew()
|
||||||
App.Header.SetShowCloseButton(true)
|
App.Header.SetShowCloseButton(true)
|
||||||
App.Header.Show()
|
App.Header.Show()
|
||||||
|
|
||||||
|
b, _ := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 0)
|
||||||
|
App.Header.SetCustomTitle(b)
|
||||||
|
|
||||||
App.Window, _ = gtk.ApplicationWindowNew(App.Application)
|
App.Window, _ = gtk.ApplicationWindowNew(App.Application)
|
||||||
App.Window.SetDefaultSize(1000, 500)
|
App.Window.SetDefaultSize(1000, 500)
|
||||||
App.Window.SetTitlebar(App.Header)
|
App.Window.SetTitlebar(App.Header)
|
||||||
|
|
|
@ -2,6 +2,7 @@ package log
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
@ -59,3 +60,7 @@ func WriteEntry(entry Entry) {
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Println(v ...interface{}) {
|
||||||
|
log.Println(v...)
|
||||||
|
}
|
||||||
|
|
|
@ -1,14 +1,73 @@
|
||||||
package ui
|
package ui
|
||||||
|
|
||||||
import "github.com/gotk3/gotk3/gtk"
|
import (
|
||||||
|
"html"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/diamondburned/cchat-gtk/internal/ui/service/breadcrumb"
|
||||||
|
"github.com/gotk3/gotk3/gtk"
|
||||||
|
)
|
||||||
|
|
||||||
type header struct {
|
type header struct {
|
||||||
*gtk.Box
|
*gtk.Box
|
||||||
|
left *gtk.Box // TODO
|
||||||
|
right *headerRight
|
||||||
}
|
}
|
||||||
|
|
||||||
func newHeader() *header {
|
func newHeader() *header {
|
||||||
box, _ := gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 0)
|
left, _ := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 0)
|
||||||
|
left.SetSizeRequest(LeftWidth, -1)
|
||||||
|
left.Show()
|
||||||
|
|
||||||
|
right := newHeaderRight()
|
||||||
|
right.Show()
|
||||||
|
|
||||||
|
separator, _ := gtk.SeparatorNew(gtk.ORIENTATION_VERTICAL)
|
||||||
|
separator.Show()
|
||||||
|
|
||||||
|
box, _ := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 0)
|
||||||
|
box.PackStart(left, false, false, 0)
|
||||||
|
box.PackStart(separator, false, false, 0)
|
||||||
|
box.PackStart(right, true, true, 0)
|
||||||
box.Show()
|
box.Show()
|
||||||
|
|
||||||
// TODO
|
// TODO
|
||||||
return &header{box}
|
return &header{
|
||||||
|
box,
|
||||||
|
left,
|
||||||
|
right,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const BreadcrumbSlash = `<span weight="light" rise="-1024" size="x-large">/</span>`
|
||||||
|
|
||||||
|
func (h *header) SetBreadcrumb(b breadcrumb.Breadcrumb) {
|
||||||
|
for i := range b {
|
||||||
|
b[i] = html.EscapeString(b[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
h.right.breadcrumb.SetMarkup(
|
||||||
|
BreadcrumbSlash + " " + strings.Join(b, " "+BreadcrumbSlash+" "),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
type headerRight struct {
|
||||||
|
*gtk.Box
|
||||||
|
breadcrumb *gtk.Label
|
||||||
|
}
|
||||||
|
|
||||||
|
func newHeaderRight() *headerRight {
|
||||||
|
bc, _ := gtk.LabelNew(BreadcrumbSlash)
|
||||||
|
bc.SetUseMarkup(true)
|
||||||
|
bc.SetXAlign(0.0)
|
||||||
|
bc.Show()
|
||||||
|
|
||||||
|
box, _ := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 0)
|
||||||
|
box.PackStart(bc, true, true, 14)
|
||||||
|
box.Show()
|
||||||
|
|
||||||
|
return &headerRight{
|
||||||
|
Box: box,
|
||||||
|
breadcrumb: bc,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
|
|
||||||
"github.com/diamondburned/cchat"
|
"github.com/diamondburned/cchat"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/humanize"
|
"github.com/diamondburned/cchat-gtk/internal/humanize"
|
||||||
|
"github.com/diamondburned/cchat-gtk/internal/ui/rich/parser"
|
||||||
"github.com/diamondburned/cchat/text"
|
"github.com/diamondburned/cchat/text"
|
||||||
"github.com/gotk3/gotk3/gtk"
|
"github.com/gotk3/gotk3/gtk"
|
||||||
"github.com/gotk3/gotk3/pango"
|
"github.com/gotk3/gotk3/pango"
|
||||||
|
@ -104,10 +105,9 @@ func (m *Message) UpdateAuthor(author cchat.MessageAuthor) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Message) updateAuthorName(name text.Rich) {
|
func (m *Message) updateAuthorName(name text.Rich) {
|
||||||
m.Username.SetLabel(name.Content)
|
m.Username.SetMarkup(parser.RenderMarkup(name))
|
||||||
m.Username.SetTooltipText(name.Content)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Message) UpdateContent(content text.Rich) {
|
func (m *Message) UpdateContent(content text.Rich) {
|
||||||
m.Content.SetLabel(content.Content)
|
m.Content.SetMarkup(parser.RenderMarkup(content))
|
||||||
}
|
}
|
||||||
|
|
1
internal/ui/message/cozy/cozy.go
Normal file
1
internal/ui/message/cozy/cozy.go
Normal file
|
@ -0,0 +1 @@
|
||||||
|
package cozy
|
|
@ -10,11 +10,48 @@ import (
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type usernameContainer struct {
|
||||||
|
*gtk.Revealer
|
||||||
|
label *rich.Label
|
||||||
|
}
|
||||||
|
|
||||||
|
func newUsernameContainer() *usernameContainer {
|
||||||
|
label := rich.NewLabel(text.Rich{})
|
||||||
|
label.SetMaxWidthChars(35)
|
||||||
|
label.SetVAlign(gtk.ALIGN_START)
|
||||||
|
label.SetMarginTop(inputmargin)
|
||||||
|
label.SetMarginBottom(inputmargin)
|
||||||
|
label.SetMarginStart(10)
|
||||||
|
label.SetMarginEnd(10)
|
||||||
|
label.Show()
|
||||||
|
|
||||||
|
rev, _ := gtk.RevealerNew()
|
||||||
|
rev.SetRevealChild(false)
|
||||||
|
rev.SetTransitionType(gtk.REVEALER_TRANSITION_TYPE_SLIDE_RIGHT)
|
||||||
|
rev.SetTransitionDuration(50)
|
||||||
|
rev.Add(label)
|
||||||
|
|
||||||
|
return &usernameContainer{rev, label}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLabel is not thread-safe.
|
||||||
|
func (u *usernameContainer) GetLabel() text.Rich {
|
||||||
|
return u.label.GetLabel()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLabel is thread-safe.
|
||||||
|
func (u *usernameContainer) SetLabel(content text.Rich) {
|
||||||
|
gts.ExecAsync(func() {
|
||||||
|
u.label.SetLabelUnsafe(content)
|
||||||
|
|
||||||
|
// Reveal if the name is not empty.
|
||||||
|
u.SetRevealChild(!u.label.GetLabel().Empty())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
type Field struct {
|
type Field struct {
|
||||||
*gtk.Box
|
*gtk.Box
|
||||||
|
username *usernameContainer
|
||||||
namerev *gtk.Revealer
|
|
||||||
username *rich.Label // TODO
|
|
||||||
|
|
||||||
TextScroll *gtk.ScrolledWindow
|
TextScroll *gtk.ScrolledWindow
|
||||||
text *gtk.TextView
|
text *gtk.TextView
|
||||||
|
@ -30,25 +67,12 @@ type Controller interface {
|
||||||
PresendMessage(msg PresendMessage) (onErr func(error))
|
PresendMessage(msg PresendMessage) (onErr func(error))
|
||||||
}
|
}
|
||||||
|
|
||||||
const inputmargin = 3
|
const inputmargin = 4
|
||||||
|
|
||||||
func NewField(ctrl Controller) *Field {
|
func NewField(ctrl Controller) *Field {
|
||||||
username := rich.NewLabel(text.Rich{})
|
username := newUsernameContainer()
|
||||||
username.SetMaxWidthChars(35)
|
|
||||||
username.SetVAlign(gtk.ALIGN_START)
|
|
||||||
username.SetMarginTop(inputmargin)
|
|
||||||
username.SetMarginBottom(inputmargin)
|
|
||||||
username.SetMarginStart(10)
|
|
||||||
username.SetMarginEnd(10)
|
|
||||||
username.Show()
|
username.Show()
|
||||||
|
|
||||||
namerev, _ := gtk.RevealerNew()
|
|
||||||
namerev.SetRevealChild(false)
|
|
||||||
namerev.SetTransitionType(gtk.REVEALER_TRANSITION_TYPE_SLIDE_RIGHT)
|
|
||||||
namerev.SetTransitionDuration(50)
|
|
||||||
namerev.Add(username)
|
|
||||||
namerev.Show()
|
|
||||||
|
|
||||||
text, _ := gtk.TextViewNew()
|
text, _ := gtk.TextViewNew()
|
||||||
text.SetSensitive(false)
|
text.SetSensitive(false)
|
||||||
text.SetWrapMode(gtk.WRAP_WORD_CHAR)
|
text.SetWrapMode(gtk.WRAP_WORD_CHAR)
|
||||||
|
@ -68,13 +92,12 @@ func NewField(ctrl Controller) *Field {
|
||||||
sw.Show()
|
sw.Show()
|
||||||
|
|
||||||
box, _ := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 0)
|
box, _ := gtk.BoxNew(gtk.ORIENTATION_HORIZONTAL, 0)
|
||||||
box.PackStart(namerev, false, false, 0)
|
box.PackStart(username, false, false, 0)
|
||||||
box.PackStart(sw, true, true, 0)
|
box.PackStart(sw, true, true, 0)
|
||||||
box.Show()
|
box.Show()
|
||||||
|
|
||||||
field := &Field{
|
field := &Field{
|
||||||
Box: box,
|
Box: box,
|
||||||
namerev: namerev,
|
|
||||||
username: username,
|
username: username,
|
||||||
TextScroll: sw,
|
TextScroll: sw,
|
||||||
text: text,
|
text: text,
|
||||||
|
@ -107,9 +130,6 @@ func (f *Field) SetSender(session cchat.Session, sender cchat.ServerMessageSende
|
||||||
log.Warn(err)
|
log.Warn(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reveal if the name is not empty.
|
|
||||||
f.namerev.SetRevealChild(!f.username.GetLabel().Empty())
|
|
||||||
|
|
||||||
// Set the sender.
|
// Set the sender.
|
||||||
f.sender = sender
|
f.sender = sender
|
||||||
f.text.SetSensitive(sender != nil) // grey if sender is nil
|
f.text.SetSensitive(sender != nil) // grey if sender is nil
|
||||||
|
|
|
@ -1 +1,83 @@
|
||||||
package parser
|
package parser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"html"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"github.com/diamondburned/cchat/text"
|
||||||
|
)
|
||||||
|
|
||||||
|
type attrAppendMap struct {
|
||||||
|
appended map[int]string
|
||||||
|
indices []int
|
||||||
|
}
|
||||||
|
|
||||||
|
func newAttrAppendedMap() attrAppendMap {
|
||||||
|
return attrAppendMap{
|
||||||
|
appended: make(map[int]string),
|
||||||
|
indices: []int{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *attrAppendMap) add(ind int, attr string) {
|
||||||
|
if _, ok := a.appended[ind]; ok {
|
||||||
|
a.appended[ind] += attr
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
a.appended[ind] = attr
|
||||||
|
a.indices = append(a.indices, ind)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a attrAppendMap) get(ind int) string {
|
||||||
|
return a.appended[ind]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *attrAppendMap) finalize(strlen int) []int {
|
||||||
|
// make sure there's always a closing tag at the end so the entire string
|
||||||
|
// gets flushed.
|
||||||
|
a.add(strlen, "")
|
||||||
|
sort.Ints(a.indices)
|
||||||
|
return a.indices
|
||||||
|
}
|
||||||
|
|
||||||
|
func RenderMarkup(content text.Rich) string {
|
||||||
|
buf := bytes.Buffer{}
|
||||||
|
buf.Grow(len(content.Content))
|
||||||
|
|
||||||
|
// // Sort so that all starting points are sorted incrementally.
|
||||||
|
// sort.Slice(content.Segments, func(i, j int) bool {
|
||||||
|
// i, _ = content.Segments[i].Bounds()
|
||||||
|
// j, _ = content.Segments[j].Bounds()
|
||||||
|
// return i < j
|
||||||
|
// })
|
||||||
|
|
||||||
|
// map to append strings to indices
|
||||||
|
var appended = newAttrAppendedMap()
|
||||||
|
|
||||||
|
// Parse all segments.
|
||||||
|
for _, segment := range content.Segments {
|
||||||
|
start, end := segment.Bounds()
|
||||||
|
|
||||||
|
switch segment := segment.(type) {
|
||||||
|
case text.Colorer:
|
||||||
|
appended.add(start, fmt.Sprintf("<span color=\"#%06X\">", segment.Color()))
|
||||||
|
appended.add(end, "</span>")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var lastIndex = 0
|
||||||
|
|
||||||
|
for _, index := range appended.finalize(len(content.Content)) {
|
||||||
|
// Write the content.
|
||||||
|
buf.WriteString(html.EscapeString(content.Content[lastIndex:index]))
|
||||||
|
// Write the tags.
|
||||||
|
buf.WriteString(appended.get(index))
|
||||||
|
// Set the last index.
|
||||||
|
lastIndex = index
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
55
internal/ui/rich/parser/parser_test.go
Normal file
55
internal/ui/rich/parser/parser_test.go
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
package parser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/diamondburned/cchat-mock/segments"
|
||||||
|
"github.com/diamondburned/cchat/text"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRenderMarkup(t *testing.T) {
|
||||||
|
content := text.Rich{Content: "astolfo is the best trap"}
|
||||||
|
content.Segments = []text.Segment{
|
||||||
|
segments.NewColored(content.Content, 0x55CDFC),
|
||||||
|
}
|
||||||
|
expect := `<span color="#55CDFC">` + content.Content + "</span>"
|
||||||
|
|
||||||
|
if text := RenderMarkup(content); text != expect {
|
||||||
|
t.Fatal("Unexpected text:", text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRenderMarkupPartial(t *testing.T) {
|
||||||
|
content := text.Rich{Content: "random placeholder text go brrr"}
|
||||||
|
content.Segments = []text.Segment{
|
||||||
|
// This is absolutely jankery that should not work at all, but we'll try
|
||||||
|
// it anyway.
|
||||||
|
coloredSegment{0, 4, 0x55CDFC},
|
||||||
|
coloredSegment{2, 6, 0xFFFFFF}, // naive parsing, so spans close unexpectedly.
|
||||||
|
coloredSegment{4, 6, 0xF7A8B8},
|
||||||
|
}
|
||||||
|
const expect = "" +
|
||||||
|
`<span color="#55CDFC">ra<span color="#FFFFFF">nd</span>` +
|
||||||
|
`<span color="#F7A8B8">om</span></span>`
|
||||||
|
|
||||||
|
if text := RenderMarkup(content); !strings.HasPrefix(text, expect) {
|
||||||
|
t.Fatal("Unexpected text:", text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type coloredSegment struct {
|
||||||
|
start int
|
||||||
|
end int
|
||||||
|
color uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ text.Colorer = (*coloredSegment)(nil)
|
||||||
|
|
||||||
|
func (c coloredSegment) Bounds() (start, end int) {
|
||||||
|
return c.start, c.end
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c coloredSegment) Color() uint32 {
|
||||||
|
return c.color
|
||||||
|
}
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"github.com/diamondburned/cchat"
|
"github.com/diamondburned/cchat"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/gts"
|
"github.com/diamondburned/cchat-gtk/internal/gts"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/ui/primitives"
|
"github.com/diamondburned/cchat-gtk/internal/ui/primitives"
|
||||||
|
"github.com/diamondburned/cchat-gtk/internal/ui/rich/parser"
|
||||||
"github.com/diamondburned/cchat/text"
|
"github.com/diamondburned/cchat/text"
|
||||||
"github.com/gotk3/gotk3/gtk"
|
"github.com/gotk3/gotk3/gtk"
|
||||||
)
|
)
|
||||||
|
@ -13,6 +14,7 @@ import (
|
||||||
type Labeler interface {
|
type Labeler interface {
|
||||||
cchat.LabelContainer // thread-safe
|
cchat.LabelContainer // thread-safe
|
||||||
GetLabel() text.Rich // not thread-safe
|
GetLabel() text.Rich // not thread-safe
|
||||||
|
GetText() string
|
||||||
}
|
}
|
||||||
|
|
||||||
type Label struct {
|
type Label struct {
|
||||||
|
@ -26,7 +28,8 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewLabel(content text.Rich) *Label {
|
func NewLabel(content text.Rich) *Label {
|
||||||
label, _ := gtk.LabelNew(content.Content)
|
label, _ := gtk.LabelNew("")
|
||||||
|
label.SetMarkup(parser.RenderMarkup(content))
|
||||||
label.SetHAlign(gtk.ALIGN_START)
|
label.SetHAlign(gtk.ALIGN_START)
|
||||||
return &Label{*label, content}
|
return &Label{*label, content}
|
||||||
}
|
}
|
||||||
|
@ -34,16 +37,27 @@ func NewLabel(content text.Rich) *Label {
|
||||||
// SetLabel is thread-safe.
|
// SetLabel is thread-safe.
|
||||||
func (l *Label) SetLabel(content text.Rich) {
|
func (l *Label) SetLabel(content text.Rich) {
|
||||||
gts.ExecAsync(func() {
|
gts.ExecAsync(func() {
|
||||||
l.current = content
|
l.SetLabelUnsafe(content)
|
||||||
l.SetText(content.Content)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetLabelUnsafe sets the label in the current thread, meaning it's not
|
||||||
|
// thread-safe.
|
||||||
|
func (l *Label) SetLabelUnsafe(content text.Rich) {
|
||||||
|
l.current = content
|
||||||
|
l.SetMarkup(parser.RenderMarkup(content))
|
||||||
|
}
|
||||||
|
|
||||||
// GetLabel is NOT thread-safe.
|
// GetLabel is NOT thread-safe.
|
||||||
func (l *Label) GetLabel() text.Rich {
|
func (l *Label) GetLabel() text.Rich {
|
||||||
return l.current
|
return l.current
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetText is NOT thread-safe.
|
||||||
|
func (l *Label) GetText() string {
|
||||||
|
return l.current.Content
|
||||||
|
}
|
||||||
|
|
||||||
type ToggleButton struct {
|
type ToggleButton struct {
|
||||||
gtk.ToggleButton
|
gtk.ToggleButton
|
||||||
Label
|
Label
|
||||||
|
|
21
internal/ui/service/breadcrumb/breadcrumb.go
Normal file
21
internal/ui/service/breadcrumb/breadcrumb.go
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
package breadcrumb
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
type Breadcrumb []string
|
||||||
|
|
||||||
|
func (b Breadcrumb) String() string {
|
||||||
|
return strings.Join([]string(b), "/")
|
||||||
|
}
|
||||||
|
|
||||||
|
type Breadcrumber interface {
|
||||||
|
Breadcrumb() Breadcrumb
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try accepts a nilable breadcrumber and handles it appropriately.
|
||||||
|
func Try(i Breadcrumber, appended ...string) []string {
|
||||||
|
if i == nil {
|
||||||
|
return appended
|
||||||
|
}
|
||||||
|
return append(i.Breadcrumb(), appended...)
|
||||||
|
}
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"github.com/diamondburned/cchat"
|
"github.com/diamondburned/cchat"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/ui/primitives"
|
"github.com/diamondburned/cchat-gtk/internal/ui/primitives"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/ui/rich"
|
"github.com/diamondburned/cchat-gtk/internal/ui/rich"
|
||||||
|
"github.com/diamondburned/cchat-gtk/internal/ui/service/breadcrumb"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/ui/service/session"
|
"github.com/diamondburned/cchat-gtk/internal/ui/service/session"
|
||||||
"github.com/diamondburned/cchat/text"
|
"github.com/diamondburned/cchat/text"
|
||||||
"github.com/gotk3/gotk3/gtk"
|
"github.com/gotk3/gotk3/gtk"
|
||||||
|
@ -91,7 +92,7 @@ func NewContainer(svc cchat.Service, ctrl Controller) *Container {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Container) AddSession(ses cchat.Session) {
|
func (c *Container) AddSession(ses cchat.Session) {
|
||||||
srow := session.New(ses, c.rowctrl)
|
srow := session.New(c, ses, c.rowctrl)
|
||||||
c.children.addSessionRow(srow)
|
c.children.addSessionRow(srow)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -103,6 +104,10 @@ func (c *Container) Sessions() []cchat.Session {
|
||||||
return sessions
|
return sessions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Container) Breadcrumb() breadcrumb.Breadcrumb {
|
||||||
|
return breadcrumb.Try(nil, c.header.reveal.GetText())
|
||||||
|
}
|
||||||
|
|
||||||
type header struct {
|
type header struct {
|
||||||
*gtk.Box
|
*gtk.Box
|
||||||
reveal *rich.ToggleButtonImage // no rich text here but it's left aligned
|
reveal *rich.ToggleButtonImage // no rich text here but it's left aligned
|
||||||
|
|
177
internal/ui/service/session/server/server.go
Normal file
177
internal/ui/service/session/server/server.go
Normal file
|
@ -0,0 +1,177 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/diamondburned/cchat"
|
||||||
|
"github.com/diamondburned/cchat-gtk/internal/gts"
|
||||||
|
"github.com/diamondburned/cchat-gtk/internal/log"
|
||||||
|
"github.com/diamondburned/cchat-gtk/internal/ui/primitives"
|
||||||
|
"github.com/diamondburned/cchat-gtk/internal/ui/rich"
|
||||||
|
"github.com/diamondburned/cchat-gtk/internal/ui/service/breadcrumb"
|
||||||
|
"github.com/diamondburned/cchat/text"
|
||||||
|
"github.com/gotk3/gotk3/gtk"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
const ChildrenMargin = 24
|
||||||
|
|
||||||
|
type Controller interface {
|
||||||
|
MessageRowSelected(*Row, cchat.ServerMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Row struct {
|
||||||
|
*gtk.Box
|
||||||
|
Button *rich.ToggleButtonImage
|
||||||
|
Server cchat.Server
|
||||||
|
Parent breadcrumb.Breadcrumber
|
||||||
|
|
||||||
|
ctrl Controller
|
||||||
|
|
||||||
|
// enum 1
|
||||||
|
message cchat.ServerMessage
|
||||||
|
|
||||||
|
// enum 2
|
||||||
|
children *Children
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRow(parent breadcrumb.Breadcrumber, server cchat.Server, ctrl Controller) *Row {
|
||||||
|
button := rich.NewToggleButtonImage(text.Rich{}, "")
|
||||||
|
button.Box.SetHAlign(gtk.ALIGN_START)
|
||||||
|
button.SetRelief(gtk.RELIEF_NONE)
|
||||||
|
button.Show()
|
||||||
|
|
||||||
|
if err := server.Name(button); err != nil {
|
||||||
|
log.Error(errors.Wrap(err, "Failed to get the server name"))
|
||||||
|
button.SetLabel(text.Rich{Content: "Unknown"})
|
||||||
|
}
|
||||||
|
|
||||||
|
box, _ := gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 0)
|
||||||
|
box.PackStart(button, false, false, 0)
|
||||||
|
box.Show()
|
||||||
|
|
||||||
|
primitives.AddClass(box, "server")
|
||||||
|
|
||||||
|
// TODO: images
|
||||||
|
|
||||||
|
var row = &Row{
|
||||||
|
Box: box,
|
||||||
|
Button: button,
|
||||||
|
Server: server,
|
||||||
|
Parent: parent,
|
||||||
|
ctrl: ctrl,
|
||||||
|
}
|
||||||
|
|
||||||
|
switch server := server.(type) {
|
||||||
|
case cchat.ServerList:
|
||||||
|
row.children = NewChildren(row, server, ctrl)
|
||||||
|
box.PackStart(row.children, false, false, 0)
|
||||||
|
|
||||||
|
primitives.AddClass(box, "server-list")
|
||||||
|
|
||||||
|
case cchat.ServerMessage:
|
||||||
|
row.message = server
|
||||||
|
|
||||||
|
primitives.AddClass(box, "server-message")
|
||||||
|
}
|
||||||
|
|
||||||
|
button.Connect("clicked", row.onClick)
|
||||||
|
|
||||||
|
return row
|
||||||
|
}
|
||||||
|
|
||||||
|
func (row *Row) GetActive() bool {
|
||||||
|
return row.Button.GetActive()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (row *Row) onClick() {
|
||||||
|
switch {
|
||||||
|
|
||||||
|
// If the server is a message server. We're only selected if the button is
|
||||||
|
// pressed.
|
||||||
|
case row.message != nil && row.GetActive():
|
||||||
|
row.ctrl.MessageRowSelected(row, row.message)
|
||||||
|
|
||||||
|
// If the server is a list of smaller servers.
|
||||||
|
case row.children != nil:
|
||||||
|
row.children.SetRevealChild(!row.children.GetRevealChild())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Row) Breadcrumb() breadcrumb.Breadcrumb {
|
||||||
|
return breadcrumb.Try(r.Parent, r.Button.GetText())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Children is a children server with a reference to the parent.
|
||||||
|
type Children struct {
|
||||||
|
*gtk.Revealer
|
||||||
|
Main *gtk.Box
|
||||||
|
List cchat.ServerList
|
||||||
|
|
||||||
|
rowctrl Controller
|
||||||
|
|
||||||
|
Rows []*Row
|
||||||
|
Parent breadcrumb.Breadcrumber
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewChildren(parent breadcrumb.Breadcrumber, list cchat.ServerList, ctrl Controller) *Children {
|
||||||
|
main, _ := gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 0)
|
||||||
|
main.SetMarginStart(ChildrenMargin)
|
||||||
|
main.Show()
|
||||||
|
|
||||||
|
rev, _ := gtk.RevealerNew()
|
||||||
|
rev.SetRevealChild(false)
|
||||||
|
rev.Add(main)
|
||||||
|
rev.Show()
|
||||||
|
|
||||||
|
children := &Children{
|
||||||
|
Revealer: rev,
|
||||||
|
Main: main,
|
||||||
|
List: list,
|
||||||
|
rowctrl: ctrl,
|
||||||
|
Parent: parent,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := list.Servers(children); err != nil {
|
||||||
|
log.Error(errors.Wrap(err, "Failed to get servers"))
|
||||||
|
}
|
||||||
|
|
||||||
|
return children
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Children) SetServers(servers []cchat.Server) {
|
||||||
|
gts.ExecAsync(func() {
|
||||||
|
// Save the current state.
|
||||||
|
var oldID string
|
||||||
|
for _, row := range c.Rows {
|
||||||
|
if row.GetActive() {
|
||||||
|
oldID = row.Server.ID()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the server list.
|
||||||
|
for _, row := range c.Rows {
|
||||||
|
c.Main.Remove(row)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Rows = make([]*Row, len(servers))
|
||||||
|
|
||||||
|
for i, server := range servers {
|
||||||
|
row := NewRow(c, server, c.rowctrl)
|
||||||
|
c.Rows[i] = row
|
||||||
|
c.Main.Add(row)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update parent reference? Only if it's activated.
|
||||||
|
if oldID != "" {
|
||||||
|
for _, row := range c.Rows {
|
||||||
|
if row.Server.ID() == oldID {
|
||||||
|
row.Button.SetActive(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Children) Breadcrumb() breadcrumb.Breadcrumb {
|
||||||
|
return breadcrumb.Try(c.Parent)
|
||||||
|
}
|
|
@ -1,87 +0,0 @@
|
||||||
package server
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/diamondburned/cchat"
|
|
||||||
"github.com/diamondburned/cchat-gtk/internal/gts"
|
|
||||||
"github.com/diamondburned/cchat-gtk/internal/log"
|
|
||||||
"github.com/gotk3/gotk3/gtk"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
const ChildrenMargin = 24
|
|
||||||
|
|
||||||
type Controller interface {
|
|
||||||
MessageRowSelected(*Row, cchat.ServerMessage)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Children is a children server with a reference to the parent.
|
|
||||||
type Children struct {
|
|
||||||
*gtk.Revealer
|
|
||||||
Main *gtk.Box
|
|
||||||
List cchat.ServerList
|
|
||||||
|
|
||||||
rowctrl Controller
|
|
||||||
|
|
||||||
Rows []*Row
|
|
||||||
ParentRow *Row
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewChildren(parent *Row, list cchat.ServerList, ctrl Controller) *Children {
|
|
||||||
main, _ := gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 0)
|
|
||||||
main.SetMarginStart(ChildrenMargin)
|
|
||||||
main.Show()
|
|
||||||
|
|
||||||
rev, _ := gtk.RevealerNew()
|
|
||||||
rev.SetRevealChild(false)
|
|
||||||
rev.Add(main)
|
|
||||||
rev.Show()
|
|
||||||
|
|
||||||
children := &Children{
|
|
||||||
Revealer: rev,
|
|
||||||
Main: main,
|
|
||||||
List: list,
|
|
||||||
rowctrl: ctrl,
|
|
||||||
ParentRow: parent,
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := list.Servers(children); err != nil {
|
|
||||||
log.Error(errors.Wrap(err, "Failed to get servers"))
|
|
||||||
}
|
|
||||||
|
|
||||||
return children
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Children) SetServers(servers []cchat.Server) {
|
|
||||||
gts.ExecAsync(func() {
|
|
||||||
// Save the current state.
|
|
||||||
var oldID string
|
|
||||||
for _, row := range c.Rows {
|
|
||||||
if row.GetActive() {
|
|
||||||
oldID = row.Server.ID()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the server list.
|
|
||||||
for _, row := range c.Rows {
|
|
||||||
c.Main.Remove(row)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Rows = make([]*Row, len(servers))
|
|
||||||
|
|
||||||
for i, server := range servers {
|
|
||||||
row := NewRow(c, server, c.rowctrl)
|
|
||||||
c.Rows[i] = row
|
|
||||||
c.Main.Add(row)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update parent reference? Only if it's activated.
|
|
||||||
if oldID != "" {
|
|
||||||
for _, row := range c.Rows {
|
|
||||||
if row.Server.ID() == oldID {
|
|
||||||
row.Button.SetActive(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -1,100 +0,0 @@
|
||||||
package server
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/diamondburned/cchat"
|
|
||||||
"github.com/diamondburned/cchat-gtk/internal/log"
|
|
||||||
"github.com/diamondburned/cchat-gtk/internal/ui/primitives"
|
|
||||||
"github.com/diamondburned/cchat-gtk/internal/ui/rich"
|
|
||||||
"github.com/diamondburned/cchat/text"
|
|
||||||
"github.com/gotk3/gotk3/gtk"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Row struct {
|
|
||||||
*gtk.Box
|
|
||||||
Button *rich.ToggleButtonImage
|
|
||||||
Server cchat.Server
|
|
||||||
Parent *Children
|
|
||||||
|
|
||||||
ctrl Controller
|
|
||||||
|
|
||||||
// enum 1
|
|
||||||
message cchat.ServerMessage
|
|
||||||
|
|
||||||
// enum 2
|
|
||||||
children *Children
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRow(parent *Children, server cchat.Server, ctrl Controller) *Row {
|
|
||||||
button := rich.NewToggleButtonImage(text.Rich{}, "")
|
|
||||||
button.Box.SetHAlign(gtk.ALIGN_START)
|
|
||||||
button.SetRelief(gtk.RELIEF_NONE)
|
|
||||||
button.Show()
|
|
||||||
|
|
||||||
if err := server.Name(button); err != nil {
|
|
||||||
log.Error(errors.Wrap(err, "Failed to get the server name"))
|
|
||||||
button.SetLabel(text.Rich{Content: "Unknown"})
|
|
||||||
}
|
|
||||||
|
|
||||||
box, _ := gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 0)
|
|
||||||
box.PackStart(button, false, false, 0)
|
|
||||||
box.Show()
|
|
||||||
|
|
||||||
primitives.AddClass(box, "server")
|
|
||||||
|
|
||||||
// TODO: images
|
|
||||||
|
|
||||||
var row = &Row{
|
|
||||||
Box: box,
|
|
||||||
Button: button,
|
|
||||||
Server: server,
|
|
||||||
Parent: parent,
|
|
||||||
ctrl: ctrl,
|
|
||||||
}
|
|
||||||
|
|
||||||
switch server := server.(type) {
|
|
||||||
case cchat.ServerList:
|
|
||||||
row.children = NewChildren(row, server, ctrl)
|
|
||||||
box.PackStart(row.children, false, false, 0)
|
|
||||||
|
|
||||||
primitives.AddClass(box, "server-list")
|
|
||||||
|
|
||||||
case cchat.ServerMessage:
|
|
||||||
row.message = server
|
|
||||||
|
|
||||||
primitives.AddClass(box, "server-message")
|
|
||||||
}
|
|
||||||
|
|
||||||
button.Connect("clicked", row.onClick)
|
|
||||||
|
|
||||||
return row
|
|
||||||
}
|
|
||||||
|
|
||||||
func (row *Row) GetActive() bool {
|
|
||||||
return row.Button.GetActive()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (row *Row) onClick() {
|
|
||||||
switch {
|
|
||||||
|
|
||||||
// If the server is a message server. We're only selected if the button is
|
|
||||||
// pressed.
|
|
||||||
case row.message != nil && row.GetActive():
|
|
||||||
row.ctrl.MessageRowSelected(row, row.message)
|
|
||||||
|
|
||||||
// If the server is a list of smaller servers.
|
|
||||||
case row.children != nil:
|
|
||||||
row.children.SetRevealChild(!row.children.GetRevealChild())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Row) Breadcrumb() string {
|
|
||||||
var label = r.Button.GetLabel().Content
|
|
||||||
|
|
||||||
// Does the row have a parent?
|
|
||||||
if r.Parent != nil {
|
|
||||||
return r.Parent.ParentRow.Breadcrumb() + "/" + label
|
|
||||||
}
|
|
||||||
|
|
||||||
return label
|
|
||||||
}
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"github.com/diamondburned/cchat-gtk/internal/log"
|
"github.com/diamondburned/cchat-gtk/internal/log"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/ui/primitives"
|
"github.com/diamondburned/cchat-gtk/internal/ui/primitives"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/ui/rich"
|
"github.com/diamondburned/cchat-gtk/internal/ui/rich"
|
||||||
|
"github.com/diamondburned/cchat-gtk/internal/ui/service/breadcrumb"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/ui/service/session/server"
|
"github.com/diamondburned/cchat-gtk/internal/ui/service/session/server"
|
||||||
"github.com/diamondburned/cchat/text"
|
"github.com/diamondburned/cchat/text"
|
||||||
"github.com/gotk3/gotk3/gtk"
|
"github.com/gotk3/gotk3/gtk"
|
||||||
|
@ -25,15 +26,17 @@ type Row struct {
|
||||||
|
|
||||||
Servers *server.Children
|
Servers *server.Children
|
||||||
|
|
||||||
ctrl Controller
|
ctrl Controller
|
||||||
|
parent breadcrumb.Breadcrumber
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(ses cchat.Session, ctrl Controller) *Row {
|
func New(parent breadcrumb.Breadcrumber, ses cchat.Session, ctrl Controller) *Row {
|
||||||
row := &Row{
|
row := &Row{
|
||||||
Session: ses,
|
Session: ses,
|
||||||
ctrl: ctrl,
|
ctrl: ctrl,
|
||||||
|
parent: parent,
|
||||||
}
|
}
|
||||||
row.Servers = server.NewChildren(ses, row)
|
row.Servers = server.NewChildren(row, ses, row)
|
||||||
|
|
||||||
row.Button = rich.NewToggleButtonImage(text.Rich{}, "")
|
row.Button = rich.NewToggleButtonImage(text.Rich{}, "")
|
||||||
row.Button.Box.SetHAlign(gtk.ALIGN_START)
|
row.Button.Box.SetHAlign(gtk.ALIGN_START)
|
||||||
|
@ -67,3 +70,7 @@ func New(ses cchat.Session, ctrl Controller) *Row {
|
||||||
func (r *Row) MessageRowSelected(server *server.Row, smsg cchat.ServerMessage) {
|
func (r *Row) MessageRowSelected(server *server.Row, smsg cchat.ServerMessage) {
|
||||||
r.ctrl.MessageRowSelected(r, server, smsg)
|
r.ctrl.MessageRowSelected(r, server, smsg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Row) Breadcrumb() breadcrumb.Breadcrumb {
|
||||||
|
return breadcrumb.Try(r.parent, r.Button.GetLabel().Content)
|
||||||
|
}
|
||||||
|
|
1
internal/ui/style.css
Normal file
1
internal/ui/style.css
Normal file
|
@ -0,0 +1 @@
|
||||||
|
headerbar { padding: 0; }
|
|
@ -10,8 +10,14 @@ import (
|
||||||
"github.com/diamondburned/cchat-gtk/internal/ui/service/session"
|
"github.com/diamondburned/cchat-gtk/internal/ui/service/session"
|
||||||
"github.com/diamondburned/cchat-gtk/internal/ui/service/session/server"
|
"github.com/diamondburned/cchat-gtk/internal/ui/service/session/server"
|
||||||
"github.com/gotk3/gotk3/gtk"
|
"github.com/gotk3/gotk3/gtk"
|
||||||
|
"github.com/markbates/pkger"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// Load the local CSS.
|
||||||
|
gts.LoadCSS(pkger.Include("/internal/ui/style.css"))
|
||||||
|
}
|
||||||
|
|
||||||
const LeftWidth = 220
|
const LeftWidth = 220
|
||||||
|
|
||||||
type App struct {
|
type App struct {
|
||||||
|
@ -54,7 +60,7 @@ func (app *App) MessageRowSelected(ses *session.Row, srv *server.Row, smsg cchat
|
||||||
app.lastRowHighlighter = srv.Button.SetActive
|
app.lastRowHighlighter = srv.Button.SetActive
|
||||||
app.lastRowHighlighter(true)
|
app.lastRowHighlighter(true)
|
||||||
|
|
||||||
log.Println("Breadcrumb:")
|
app.header.SetBreadcrumb(srv.Breadcrumb())
|
||||||
|
|
||||||
// Show the messages.
|
// Show the messages.
|
||||||
app.window.MessageView.JoinServer(ses.Session, smsg)
|
app.window.MessageView.JoinServer(ses.Session, smsg)
|
||||||
|
|
12
pkged.go
Normal file
12
pkged.go
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
// Code generated by pkger; DO NOT EDIT.
|
||||||
|
|
||||||
|
// +build !skippkger
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/markbates/pkger"
|
||||||
|
"github.com/markbates/pkger/pkging/mem"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = pkger.Apply(mem.UnmarshalEmbed([]byte(`1f8b08000000000000ffec58dd8fdb380eff570a3da7a366da29ae792eaeb88739146d713860512c688996b5d1d7ea639a7430fffb42b293c8f99ccc2eb058c00fb145f2474aa4258ae12391a6b5812c1e8990b14bcd0db39a7209da1ade246f9053c63a88af455c66d447e9c982d0ce6adcc0e857e6a58ba10756f019f98f76d6c7cf103bb278ce0433f25fd0481644833464463e5a461684ccc837f002e3c1cc37c2d2469a91852fd61e024f2ff11e22ebc8e2177243becfc8d7080ac922fa8403f105215843162464ea15478786a361ebc5abca21619bd4b6a02cedd02399914ff6df5261c886b32f37c266f37d3c0af77234a83411bd0145450c64769586b2e24a8d242f2b68cb96175134a07f900c4376f823bae26d935a69c98c34eb88d91766b5f318026d1544ac19e2a774853611a4414f950c7160e0aa8cfcda45bb1d50e82df60493ae43bfa3792de4017604b231c96fefeee61f0e18bb0821ff019e877d9852d245c9769c4e43456dd53d189ea2544744213551e14ea0f9dd8ec87a15c5de5544ed40e8603ea26eefde8fe8bbf96d45ef4d195515a7d5dd9b0f638abaa55c911941c32c974654430ac1cc6bba8180efdf8d38d2805fd71c619b9aecb0364e7fcb27aea21dea4c7a6f7d5e65abe378177e06a5a4e1e8a9b0af73bcace610610cfa7f02f3c36684b20c8aefe737f29507e8fa43da250d46febcbc92b1da12d7beff047f7f46d853c898974c44358600e2da48ec1429a46803f356a997dbc83908d8b5dfbd32208d4b2f50775e6a19e5035ebb8192a45eb2ee7aad2145bf583187fbe5d3d2c62370e6936e5e6e236008b2a4893f67a0d0e5c6f82b2ebf82a2018546f38c7cb0bb2b2f2287cbef5ccd3192f126059a1ffb02cb831486e60960bd2f8ccbb7c39381f4f6b458f0e519a192cd19693ca3eac088bd6935f865031103754bb1ffa5f6844742b18fd8ee050d2e9c87baa53848b4c73134446ef7acb9a5a0db3babe2ff0495afa87c131d4de587721a90798cbf56c7d62a30e2c67a4157656b54891e8c48432ebd8caa93de697404711450cdd541e88617659ebdcdb54617754ec75b33b9fa03256a1673a9265b1d83f5b166198cd143f17acbb3a104b666b93ef557e5d658c563ab904525e3881da4110a5b2545379a35ac0303a528ae90a17938264aa69444bb206188fde5574a5d69a9b443c1d7b33594a4995fb49162372c99a28c87624f4b8dc38beaa4a27450825218bf271b913b2f4d84a6d4310663ffa45d8cae1a96c7267a5be666c5032f7f49e76d29a9339d7c9694dd6c4309403f4aa11c3ed77b915fb4950a077a886f19095cb9ed8086b5899023e59389bd63c388b272d637d4369210ad2e15f5816408e1013facf37287ad13a267b67cb310f3f129a2b561c36b677ef8926446ca6e0e0c8c293e0ecb4c4632cbab114db19dbf1fd3ff2a648036e31ed0709bcbd0eaa46cfe9974c03ab87df33c94b36a3d7ffbe6ee02babcf2b17a2e6e53ad9f0327ff809b7f396770dd92b7e711877f70ce802f789c77263721ff76c5e229e076ef8b54ced6459cf376b5be00bca59d8352039c42496ee08438acc390eb8e49fbed872c79a48de4d2a793d12ad0e8c184d67a7d0eb4d9a3d9e0737026dbfb3e23df30c46dfbc224a57ad6b677d1b3ee2dcf8b5c3c926bda3bf720cda6bd725d2be993bdb7fcb9782aec8db6bca8fd0f7d291417647e337f479e9e9e66a4ed9d7b7c9a9152304c8dada9b13535b6a6c6d6d4d89a1a5b53636b6a6c4d8dada9b13535b6a6c6d6d4d89a1a5b53636b6a6c4d8dada9b1f50f686cfd010000ffff010000ffff0d1f6f41ce250000`)))
|
Loading…
Reference in a new issue