Browse Source

代码规范

master
VIVIMAN 3 years ago
parent
commit
80785e90a4
  1. 4
      gui_wails02_learn/.gitignore
  2. 3
      gui_wails02_learn/.jshint
  3. 76
      gui_wails02_learn/CODE_OF_CONDUCT.md
  4. 2
      gui_wails02_learn/LICENSE
  5. 48
      gui_wails02_learn/README.md
  6. BIN
      gui_wails02_learn/appicon--512.png
  7. BIN
      gui_wails02_learn/appicon.png
  8. 188
      gui_wails02_learn/backend/config/config.go
  9. 177
      gui_wails02_learn/backend/image/file.go
  10. 143
      gui_wails02_learn/backend/image/filemanager.go
  11. 28
      gui_wails02_learn/backend/jpeg/jpeg.go
  12. 55
      gui_wails02_learn/backend/localstore/localstore.go
  13. 39
      gui_wails02_learn/backend/png/png.go
  14. 94
      gui_wails02_learn/backend/stat/stat.go
  15. 31
      gui_wails02_learn/backend/webp/webp.go
  16. 4
      gui_wails02_learn/build.bat
  17. 19
      gui_wails02_learn/dmg-spec.json
  18. 0
      gui_wails02_learn/frontend/.gitignore
  19. 4
      gui_wails02_learn/frontend/.prettierrc
  20. 35
      gui_wails02_learn/frontend/README.md
  21. 3
      gui_wails02_learn/frontend/babel.config.js
  22. 12615
      gui_wails02_learn/frontend/package-lock.json
  23. 55
      gui_wails02_learn/frontend/package.json
  24. 1
      gui_wails02_learn/frontend/package.json.md5
  25. 6
      gui_wails02_learn/frontend/postcss.config.js
  26. 57
      gui_wails02_learn/frontend/src/App.vue
  27. 60
      gui_wails02_learn/frontend/src/assets/css/animations.css
  28. 9
      gui_wails02_learn/frontend/src/assets/css/buttons.css
  29. 81
      gui_wails02_learn/frontend/src/assets/css/input.css
  30. 37
      gui_wails02_learn/frontend/src/assets/css/main.css
  31. 11
      gui_wails02_learn/frontend/src/assets/css/tooltip.css
  32. BIN
      gui_wails02_learn/frontend/src/assets/images/optimus-logo--640x360.png
  33. 78
      gui_wails02_learn/frontend/src/components/About.vue
  34. 57
      gui_wails02_learn/frontend/src/components/BtnClose.vue
  35. 195
      gui_wails02_learn/frontend/src/components/Dropdown.vue
  36. 746
      gui_wails02_learn/frontend/src/components/Editor.vue
  37. 102
      gui_wails02_learn/frontend/src/components/Notification.vue
  38. 533
      gui_wails02_learn/frontend/src/components/Settings.vue
  39. 152
      gui_wails02_learn/frontend/src/components/Sidebar.vue
  40. 103
      gui_wails02_learn/frontend/src/components/Stats.vue
  41. 3
      gui_wails02_learn/frontend/src/lib/event-bus.js
  42. 35
      gui_wails02_learn/frontend/src/lib/file.js
  43. 23
      gui_wails02_learn/frontend/src/lib/time.js
  44. 19
      gui_wails02_learn/frontend/src/main.js
  45. 147
      gui_wails02_learn/frontend/src/store.js
  46. 40
      gui_wails02_learn/frontend/tailwind.config.js
  47. 42
      gui_wails02_learn/frontend/vue.config.js
  48. 21
      gui_wails02_learn/go.mod
  49. 111
      gui_wails02_learn/go.sum
  50. 37
      gui_wails02_learn/main.go
  51. 6
      gui_wails02_learn/optimus.AppImage.desktop
  52. 10
      gui_wails02_learn/optimus.json
  53. 22
      gui_wails02_learn/project.json
  54. 4
      gui_wails02_learn/wails.json
  55. 2
      gui_wails_learn/.gitattributes
  56. 9
      gui_wails_learn/.gitignore
  57. 67
      gui_wails_learn/Makefile
  58. BIN
      gui_wails_learn/appicon.png
  59. BIN
      gui_wails_learn/assets/appicon.png
  60. 65
      gui_wails_learn/backend/api/rpc.go
  61. 237
      gui_wails_learn/backend/app.go
  62. 210
      gui_wails_learn/backend/assemblywrapper.go
  63. 252
      gui_wails_learn/backend/buildchain.go
  64. 441
      gui_wails_learn/backend/cmd/update/main.go
  65. 393
      gui_wails_learn/backend/dashboard.go
  66. 123
      gui_wails_learn/backend/lcm.go
  67. 167
      gui_wails_learn/backend/login.go
  68. 4
      gui_wails_learn/backend/models/address.go
  69. 10
      gui_wails_learn/backend/models/path.go
  70. 19
      gui_wails_learn/backend/models/txhistory.go
  71. 41
      gui_wails_learn/backend/models/wallet.go
  72. 166
      gui_wails_learn/backend/settings.go
  73. 374
      gui_wails_learn/backend/transactions.go
  74. 188
      gui_wails_learn/backend/utils.go
  75. 542
      gui_wails_learn/backend/wallet.go
  76. 89
      gui_wails_learn/docs/faq.md
  77. 5
      gui_wails_learn/frontend/babel.config.js
  78. 20364
      gui_wails_learn/frontend/package-lock.json
  79. 82
      gui_wails_learn/frontend/package.json
  80. 1
      gui_wails_learn/frontend/package.json.md5
  81. 303
      gui_wails_learn/frontend/src/App.vue
  82. 1081
      gui_wails_learn/frontend/src/assets/css/themify-icons.css
  83. 202
      gui_wails_learn/frontend/src/assets/fonts/LICENSE.txt
  84. BIN
      gui_wails_learn/frontend/src/assets/fonts/fa-brands-400.eot
  85. 3451
      gui_wails_learn/frontend/src/assets/fonts/fa-brands-400.svg
  86. BIN
      gui_wails_learn/frontend/src/assets/fonts/fa-brands-400.ttf
  87. BIN
      gui_wails_learn/frontend/src/assets/fonts/fa-brands-400.woff
  88. BIN
      gui_wails_learn/frontend/src/assets/fonts/fa-brands-400.woff2
  89. BIN
      gui_wails_learn/frontend/src/assets/fonts/fa-regular-400.eot
  90. 803
      gui_wails_learn/frontend/src/assets/fonts/fa-regular-400.svg
  91. BIN
      gui_wails_learn/frontend/src/assets/fonts/fa-regular-400.ttf
  92. BIN
      gui_wails_learn/frontend/src/assets/fonts/fa-regular-400.woff
  93. BIN
      gui_wails_learn/frontend/src/assets/fonts/fa-regular-400.woff2
  94. BIN
      gui_wails_learn/frontend/src/assets/fonts/fa-solid-900.eot
  95. 4649
      gui_wails_learn/frontend/src/assets/fonts/fa-solid-900.svg
  96. BIN
      gui_wails_learn/frontend/src/assets/fonts/fa-solid-900.ttf
  97. BIN
      gui_wails_learn/frontend/src/assets/fonts/fa-solid-900.woff
  98. BIN
      gui_wails_learn/frontend/src/assets/fonts/fa-solid-900.woff2
  99. BIN
      gui_wails_learn/frontend/src/assets/fonts/glyphicons-halflings-regular.448c34a.woff2
  100. BIN
      gui_wails_learn/frontend/src/assets/fonts/glyphicons-halflings-regular.e18bbf6.ttf

4
gui_wails02_learn/.gitignore

@ -0,0 +1,4 @@
build
build/**/*
.idea/
.vscode/

3
gui_wails02_learn/.jshint

@ -0,0 +1,3 @@
{
"esversion": 6
}

76
gui_wails02_learn/CODE_OF_CONDUCT.md

@ -0,0 +1,76 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at flyweight@pm.me. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq

2
gui_wails_learn/LICENSE → gui_wails02_learn/LICENSE

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2019-Present Vito
Copyright (c) 2020 Christopher Murphy
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

48
gui_wails02_learn/README.md

@ -0,0 +1,48 @@
![Optimus logo](./.github/optimus-logo--960x540.png)
# Optimus
> Image compression, optimization and conversion desktop app.
## Overview
Optimus is a desktop image optimization application. It supports conversion and compression between WebP, JPEG, and PNG image formats.
## Features
- Convert to and from JPEG, PNG, and WebP formats.
- Compress JPEG, PNG (lossy), and WebP (lossy and lossless) formats.
- Resize images to various sizes in a single batch operation.
- View simple stats on session and all-time use.
![Screenshot of Optimus primary image editor view](./.github/optimus_screenshot_editor--1200x742.png)
![Screenshot of Optimus options view](./.github/optimus_screenshot_options--1200x936.png)
## Installation
### Downloads
Download the latest version from the [releases page](https://github.com/Splode/optimus/releases).
Optimus is available for Windows, macOS, and Linux.
### Scoop
```bash
scoop install https://raw.githubusercontent.com/Splode/optimus/main/optimus.json
```
## Development
Optimus is built using [Wails](https://wails.app/) and uses JavaScript on the frontend and Go on the backend.
Take the following steps to develop locally:
1. Clone the repo
2. Install Wails
3. Install go and npm dependencies
## License
MIT © 2020 Christopher Murphy

BIN
gui_wails02_learn/appicon--512.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

BIN
gui_wails02_learn/appicon.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 222 KiB

188
gui_wails02_learn/backend/config/config.go

@ -0,0 +1,188 @@
package config
import (
"encoding/json"
"fmt"
"github.com/wailsapp/wails"
"optimus/backend/jpeg"
"optimus/backend/localstore"
"optimus/backend/png"
"optimus/backend/webp"
"os"
"path"
"path/filepath"
"strconv"
)
const filename = "conf.json"
// App represents application persistent configuration values.
type App struct {
OutDir string `json:"outDir"`
Target string `json:"target"`
Prefix string `json:"prefix"`
Suffix string `json:"suffix"`
Sizes []*size `json:"sizes"`
JpegOpt *jpeg.Options `json:"jpegOpt"`
PngOpt *png.Options `json:"pngOpt"`
WebpOpt *webp.Options `json:"webpOpt"`
}
// Config represents the application settings.
type Config struct {
App *App
Runtime *wails.Runtime
Logger *wails.CustomLogger
localStore *localstore.LocalStore
}
// WailsInit performs setup when Wails is ready.
func (c *Config) WailsInit(runtime *wails.Runtime) error {
c.Runtime = runtime
c.Logger = c.Runtime.Log.New("Config")
c.Logger.Info("Config initialized...")
return nil
}
// NewConfig returns a new instance of Config.
func NewConfig() *Config {
c := &Config{}
c.localStore = localstore.NewLocalStore()
a, err := c.localStore.Load(filename)
if err != nil {
c.App, _ = defaults()
}
if err = json.Unmarshal(a, &c.App); err != nil {
fmt.Printf("error")
}
return c
}
// GetAppConfig returns the application configuration.
func (c *Config) GetAppConfig() map[string]interface{} {
return map[string]interface{}{
"outDir": c.App.OutDir,
"target": c.App.Target,
"prefix": c.App.Prefix,
"suffix": c.App.Suffix,
"sizes": c.App.Sizes,
"jpegOpt": c.App.JpegOpt,
"pngOpt": c.App.PngOpt,
"webpOpt": c.App.WebpOpt,
}
}
// OpenOutputDir opens the output directory using the native system browser.
func (c *Config) OpenOutputDir() error {
if err := c.Runtime.Browser.OpenURL(c.App.OutDir); err != nil {
return err
}
return nil
}
// RestoreDefaults sets the app configuration to defaults.
func (c *Config) RestoreDefaults() (err error) {
var a *App
a, err = defaults()
if err != nil {
return err
}
c.App = a
if err = c.store(); err != nil {
return err
}
return nil
}
// SetConfig sets and stores the given configuration.
func (c *Config) SetConfig(cfg string) error {
a := &App{}
if err := json.Unmarshal([]byte(cfg), &a); err != nil {
c.Logger.Errorf("failed to unmarshal config: %v", err)
return err
}
c.App = a
if err := c.store(); err != nil {
c.Logger.Errorf("failed to store config: %v", err)
return err
}
return nil
}
// SetOutDir opens a directory select dialog and sets the output directory to
// the chosen directory.
func (c *Config) SetOutDir() string {
dir := c.Runtime.Dialog.SelectDirectory()
if dir != "" {
c.App.OutDir = dir
c.Logger.Infof("set output directory: %s", dir)
if err := c.store(); err != nil {
c.Logger.Errorf("failed to store config: %v", err)
}
}
return c.App.OutDir
}
// defaults returns the application configuration defaults.
func defaults() (*App, error) {
a := &App{
Target: "webp",
JpegOpt: &jpeg.Options{Quality: 80},
PngOpt: &png.Options{Quality: 80},
WebpOpt: &webp.Options{Lossless: false, Quality: 80},
}
ud, err := os.UserHomeDir()
if err != nil {
fmt.Printf("failed to get user directory: %v", err)
return nil, err
}
od := path.Join(ud, "Optimus")
cp := filepath.Clean(od)
if _, err = os.Stat(od); os.IsNotExist(err) {
if err = os.Mkdir(od, 0777); err != nil {
od = "./"
fmt.Printf("failed to create default output directory: %v", err)
return nil, err
}
}
a.OutDir = cp
return a, nil
}
// store stores the configuration state to the file system.
func (c *Config) store() error {
js, err := json.Marshal(c.GetAppConfig())
if err != nil {
c.Logger.Errorf("failed to marshal config: %v", err)
return err
}
if err = c.localStore.Store(js, filename); err != nil {
c.Logger.Errorf("failed to store config: %v", err)
return err
}
return nil
}
// rect represents an image width and height size.
type rect struct {
Height int `json:"height,omitempty"`
Width int `json:"width,omitempty"`
}
// String returns a string representation of the rect.
// For example, "1280x720"
func (r *rect) String() string {
w := strconv.Itoa(r.Width)
h := strconv.Itoa(r.Height)
return fmt.Sprintf("%sx%s", w, h)
}
// size represents an image resizing. Strategy represents an image resizing
// strategy, such as cropping.
type size struct {
rect
Strategy int `json:"strategy"`
}

177
gui_wails02_learn/backend/image/file.go

@ -0,0 +1,177 @@
package image
import (
"bytes"
"errors"
"fmt"
"image"
"io/ioutil"
"optimus/backend/config"
"optimus/backend/jpeg"
"optimus/backend/png"
"optimus/backend/webp"
"os"
"path"
"path/filepath"
"github.com/disintegration/imaging"
"github.com/muesli/smartcrop"
"github.com/muesli/smartcrop/nfnt"
"github.com/wailsapp/wails"
)
const (
fill = iota
fit
smart
)
var mimes = map[string]string{
"image/.jpg": "jpg",
"image/jpg": "jpg",
"image/jpeg": "jpg",
"image/png": "png",
"image/webp": "webp",
}
// File represents an image file.
type File struct {
Data []byte `json:"data"`
Ext string `json:"ext"`
ID string `json:"id"`
MimeType string `json:"type"`
Name string `json:"name"`
Size int64 `json:"size"`
ConvertedFile string
IsConverted bool
Image image.Image
Runtime *wails.Runtime
}
// Decode decodes the file's data based on its mime type.
func (f *File) Decode() error {
mime, err := getFileType(f.MimeType)
if err != nil {
return err
}
switch mime {
case "jpg":
f.Image, err = jpeg.DecodeJPEG(bytes.NewReader(f.Data))
case "png":
f.Image, err = png.DecodePNG(bytes.NewReader(f.Data))
case "webp":
f.Image, err = webp.DecodeWebp(bytes.NewReader(f.Data))
}
if err != nil {
return err
}
return nil
}
// GetConvertedSize returns the size of the converted file.
func (f *File) GetConvertedSize() (int64, error) {
if f.ConvertedFile == "" {
return 0, errors.New("file has no converted file")
}
s, err := os.Stat(f.ConvertedFile)
if err != nil {
return 0, err
}
return s.Size(), nil
}
// GetSavings returns the delta between original and converted file size.
func (f *File) GetSavings() (int64, error) {
c, err := f.GetConvertedSize()
if err != nil {
return 0, err
}
return f.Size - c, nil
}
// Write saves a file to disk based on the encoding target.
func (f *File) Write(c *config.Config) error {
// TODO resizing should probably be in its own method
if c.App.Sizes != nil {
for _, r := range c.App.Sizes {
if r.Height <= 0 || r.Width <= 0 {
f.Runtime.Events.Emit("notify", map[string]interface{}{
"msg": fmt.Sprintf("Invalid image size: %s", r.String()),
"type": "warn",
})
continue
}
var i image.Image
var s string
switch r.Strategy {
case fill:
i = imaging.Fill(f.Image, r.Width, r.Height, imaging.Center, imaging.Lanczos)
s = r.String()
case fit:
i = imaging.Fit(f.Image, r.Width, r.Height, imaging.Lanczos)
s = fmt.Sprintf("%dx%d", i.Bounds().Max.X, i.Bounds().Max.Y)
case smart:
analyzer := smartcrop.NewAnalyzer(nfnt.NewDefaultResizer())
crop, err := analyzer.FindBestCrop(f.Image, r.Width, r.Height)
if err != nil {
return err
}
croppedImg := f.Image.(SubImager).SubImage(crop)
i = imaging.Resize(croppedImg, r.Width, r.Height, imaging.Lanczos)
s = fmt.Sprintf("%dx%d", i.Bounds().Max.X, i.Bounds().Max.Y)
}
buf, err := encToBuf(i, c.App)
dest := path.Join(c.App.OutDir, c.App.Prefix+f.Name+"--"+s+c.App.Suffix+"."+c.App.Target)
if err != nil {
return err
}
if err = ioutil.WriteFile(dest, buf.Bytes(), 0666); err != nil {
return err
}
}
}
buf, err := encToBuf(f.Image, c.App)
dest := path.Join(c.App.OutDir, c.App.Prefix+f.Name+c.App.Suffix+"."+c.App.Target)
if err != nil {
return err
}
if err = ioutil.WriteFile(dest, buf.Bytes(), 0666); err != nil {
return err
}
f.ConvertedFile = filepath.Clean(dest)
f.IsConverted = true
return nil
}
// encToBuf encodes an image to a buffer using the configured target.
func encToBuf(i image.Image, a *config.App) (*bytes.Buffer, error) {
var b bytes.Buffer
var err error
switch a.Target {
case "jpg":
b, err = jpeg.EncodeJPEG(i, a.JpegOpt)
case "png":
b, err = png.EncodePNG(i, a.PngOpt)
case "webp":
b, err = webp.EncodeWebp(i, a.WebpOpt)
}
if err != nil {
return nil, err
}
return &b, nil
}
// getFileType returns the file's type based on the given mime type.
func getFileType(t string) (string, error) {
m, prs := mimes[t]
if !prs {
_ = errors.New("unsupported file type:" + t)
}
return m, nil
}
// SubImager handles creating a subimage from an image rect.
type SubImager interface {
SubImage(r image.Rectangle) image.Image
}

143
gui_wails02_learn/backend/image/filemanager.go

@ -0,0 +1,143 @@
package image
import (
"encoding/json"
"fmt"
"github.com/wailsapp/wails"
"optimus/backend/config"
"optimus/backend/stat"
"runtime/debug"
"strings"
"sync"
"time"
)
// FileManager handles collections of Files for conversion.
type FileManager struct {
Files []*File
Runtime *wails.Runtime
Logger *wails.CustomLogger
config *config.Config
stats *stat.Stat
}
// NewFileManager creates a new FileManager.
func NewFileManager(c *config.Config, s *stat.Stat) *FileManager {
return &FileManager{
config: c,
stats: s,
}
}
// WailsInit performs setup when Wails is ready.
func (fm *FileManager) WailsInit(runtime *wails.Runtime) error {
fm.Runtime = runtime
fm.Logger = fm.Runtime.Log.New("FileManager")
fm.Logger.Info("FileManager initialized...")
return nil
}
// HandleFile processes a file from the client.
func (fm *FileManager) HandleFile(fileJson string) (err error) {
file := &File{Runtime: fm.Runtime}
if err = json.Unmarshal([]byte(fileJson), &file); err != nil {
return err
}
if err = file.Decode(); err != nil {
return err
}
fm.Files = append(fm.Files, file)
fm.Logger.Infof("added file to file manager: %s", file.Name)
return nil
}
// Clear removes the files in the FileManager.
func (fm *FileManager) Clear() {
fm.Files = nil
debug.FreeOSMemory()
}
// Convert runs the conversion on all files in the FileManager.
func (fm *FileManager) Convert() (errs []error) {
var wg sync.WaitGroup
wg.Add(fm.countUnconverted())
c := 0
var b int64
t := time.Now().UnixNano()
for _, file := range fm.Files {
file := file
if !file.IsConverted {
go func(wg *sync.WaitGroup) {
err := file.Write(fm.config)
if err != nil {
fm.Logger.Errorf("failed to convert file: %s, %v", file.ID, err)
fm.Runtime.Events.Emit("notify", map[string]interface{}{
"msg": fmt.Sprintf("Failed to convert file: %s, %s", file.Name, err.Error()),
"type": "warn",
})
errs = append(errs, fmt.Errorf("failed to convert file: %s", file.Name))
} else {
fm.Logger.Info(fmt.Sprintf("converted file: %s", file.Name))
s, err := file.GetConvertedSize()
if err != nil {
fm.Logger.Errorf("failed to read converted file size: %v", err)
}
fm.Runtime.Events.Emit("conversion:complete", map[string]interface{}{
"id": file.ID,
// TODO: standardize this path conversion
"path": strings.Replace(file.ConvertedFile, "\\", "/", -1),
"size": s,
})
c++
s, err = file.GetSavings()
if err != nil {
fm.Logger.Errorf("failed to get file conversion savings: %v", err)
}
b += s
}
wg.Done()
}(&wg)
}
}
wg.Wait()
nt := (time.Now().UnixNano() - t) / 1000000
fm.stats.SetImageCount(c)
fm.stats.SetByteCount(b)
fm.stats.SetTimeCount(nt)
fm.Runtime.Events.Emit("conversion:stat", map[string]interface{}{
"count": c,
"resizes": c * len(fm.config.App.Sizes),
"savings": b,
"time": nt,
})
fm.Clear()
return errs
}
// OpenFile opens the file at the given filepath using the file's native file
// application.
func (fm *FileManager) OpenFile(p string) error {
if err := fm.Runtime.Browser.OpenFile(p); err != nil {
fm.Logger.Errorf("failed to open file %s: %v", p, err)
return err
}
return nil
}
// countUnconverted returns the number of files in the FileManager that haven't
// been converted.
func (fm *FileManager) countUnconverted() int {
c := 0
for _, file := range fm.Files {
if !file.IsConverted {
c++
}
}
return c
}

28
gui_wails02_learn/backend/jpeg/jpeg.go

@ -0,0 +1,28 @@
package jpeg
import (
"bytes"
"image"
"image/jpeg"
"io"
)
// Options represent JPEG encoding options.
type Options struct {
Quality int `json:"quality"`
}
// DecodeJPEG decodes a JPEG file and return an image.
func DecodeJPEG(r io.Reader) (image.Image, error) {
i, err := jpeg.Decode(r)
if err != nil {
return nil, err
}
return i, nil
}
// EncodeJPEG encodes an image into JPEG and returns a buffer.
func EncodeJPEG(i image.Image, o *Options) (buf bytes.Buffer, err error) {
err = jpeg.Encode(&buf, i, &jpeg.Options{Quality: o.Quality})
return buf, err
}

55
gui_wails02_learn/backend/localstore/localstore.go

@ -0,0 +1,55 @@
package localstore
import (
"github.com/vrischmann/userdir"
"io/ioutil"
"os"
"path"
)
// LocalStore provides reading and writing application data to the user's
// configuration directory.
type LocalStore struct {
ConfDir string
}
// NewLocalStore returns a localStore instance.
func NewLocalStore() *LocalStore {
return &LocalStore{ConfDir: path.Join(userdir.GetConfigHome(), "Optimus")}
}
// Load reads the given file in the user's configuration directory and returns
// its contents.
func (l *LocalStore) Load(filename string) ([]byte, error) {
p := path.Join(l.ConfDir, filename)
d, err := ioutil.ReadFile(p)
if err != nil {
return nil, err
}
return d, err
}
// Store writes data to the user's configuration directory at the given
// filename.
func (l *LocalStore) Store(data []byte, filename string) error {
p := path.Join(l.ConfDir, filename)
if err := ensureDirExists(l.ConfDir); err != nil {
return err
}
if err := ioutil.WriteFile(p, data, 0777); err != nil {
return err
}
return nil
}
// ensureDirExists checks for the existence of the directory at the given path,
// which is created if it does not exist.
func ensureDirExists(path string) error {
_, err := os.Stat(path)
if os.IsNotExist(err) {
if err = os.Mkdir(path, 0777); err != nil {
return err
}
}
return nil
}

39
gui_wails02_learn/backend/png/png.go

@ -0,0 +1,39 @@
package png
import (
"bytes"
"github.com/foobaz/lossypng/lossypng"
"image"
"image/png"
"io"
)
const qMax = 20
// Options represent PNG encoding options.
type Options struct {
Quality int `json:"quality"`
}
// DecodePNG decodes a PNG file and return an image.
func DecodePNG(r io.Reader) (image.Image, error) {
i, err := png.Decode(r)
if err != nil {
return nil, err
}
return i, nil
}
// EncodePNG encodes an image into PNG and returns a buffer.
func EncodePNG(i image.Image, o *Options) (buf bytes.Buffer, err error) {
c := lossypng.Compress(i, 2, qualityFactor(o.Quality))
err = png.Encode(&buf, c)
return buf, err
}
// qualityFactor normalizes the PNG quality factor from a max of 20, where 0 is
// no conversion.
func qualityFactor(q int) int {
f := q / 100
return qMax - (f * qMax)
}

94
gui_wails02_learn/backend/stat/stat.go

@ -0,0 +1,94 @@
package stat
import (
"encoding/json"
"github.com/wailsapp/wails"
"optimus/backend/localstore"
)
const filename = "stats.json"
// Stat represents application statistics.
type Stat struct {
ByteCount int64 `json:"byteCount"`
ImageCount int `json:"imageCount"`
TimeCount int64 `json:"timeCount"`
Runtime *wails.Runtime
Logger *wails.CustomLogger
localStore *localstore.LocalStore
}
// NewStat returns a new Stat instance.
func NewStat() *Stat {
s := &Stat{
localStore: localstore.NewLocalStore(),
}
d, _ := s.localStore.Load(filename)
_ = json.Unmarshal(d, &s)
return s
}
// WailsInit performs setup when Wails is ready.
func (s *Stat) WailsInit(runtime *wails.Runtime) error {
s.Runtime = runtime
s.Logger = s.Runtime.Log.New("Stat")
s.Logger.Info("Stat initialized...")
return nil
}
// GetStats returns the application stats.
func (s *Stat) GetStats() map[string]interface{} {
return map[string]interface{}{
"byteCount": s.ByteCount,
"imageCount": s.ImageCount,
"timeCount": s.TimeCount,
}
}
// SetByteCount adds and persists the given byte count to the app stats.
func (s *Stat) SetByteCount(b int64) {
if b <= 0 {
return
}
s.ByteCount += b
if err := s.store(); err != nil {
s.Logger.Errorf("failed to store stats: %v", err)
}
}
// SetImageCount adds and persists the given image count to the app stats.
func (s *Stat) SetImageCount(i int) {
if i <= 0 {
return
}
s.ImageCount += i
if err := s.store(); err != nil {
s.Logger.Errorf("failed to store stats: %v", err)
}
}
// SetTimeCount adds and persists the given time count to the app stats.
func (s *Stat) SetTimeCount(t int64) {
if t < 0 {
return
}
s.TimeCount += t
if err := s.store(); err != nil {
s.Logger.Errorf("failed to store stats: %v", err)
}
}
// store stores the app stats to the file system.
func (s *Stat) store() error {
js, err := json.Marshal(s.GetStats())
if err != nil {
return err
}
if err = s.localStore.Store(js, filename); err != nil {
return err
}
return nil
}

31
gui_wails02_learn/backend/webp/webp.go

@ -0,0 +1,31 @@
package webp
import (
"bytes"
"github.com/chai2010/webp"
"image"
"io"
)
// Options represent WebP encoding options.
type Options struct {
Lossless bool `json:"lossless"`
Quality int `json:"quality"`
}
// DecodeWebp a webp file and return an image.
func DecodeWebp(r io.Reader) (image.Image, error) {
i, err := webp.Decode(r)
if err != nil {
return nil, err
}
return i, nil
}
// EncodeWebp encodes an image into webp and returns a buffer.
func EncodeWebp(i image.Image, o *Options) (buf bytes.Buffer, err error) {
if err = webp.Encode(&buf, i, &webp.Options{Lossless: o.Lossless, Quality: float32(o.Quality)}); err != nil {
return buf, err
}
return buf, nil
}

4
gui_wails_learn/build.bat → gui_wails02_learn/build.bat

@ -1,6 +1,4 @@
# go build -ldflags="-s -w -H=windowsgui" main.go
cd frontend
npm install && npm run build
cd ../
wails build
npm install && npm run build && cd ../ && wails build

19
gui_wails02_learn/dmg-spec.json

@ -0,0 +1,19 @@
{
"title": "Optimus installer",
"background-color": "#326DE6",
"icon-size": 80,
"contents": [
{
"x": 192,
"y": 344,
"type": "file",
"path": "./build/optimus.app"
},
{
"x": 448,
"y": 344,
"type": "link",
"path": "/Applications"
}
]
}

0
gui_wails_learn/frontend/.gitignore → gui_wails02_learn/frontend/.gitignore

4
gui_wails02_learn/frontend/.prettierrc

@ -0,0 +1,4 @@
{
"semi": false,
"singleQuote": true
}

35
gui_wails02_learn/frontend/README.md

@ -0,0 +1,35 @@
# vue basic
## Project setup
```
npm install
```
### Compiles and hot-reloads for development
```
npm run serve
```
### Compiles and minifies for production
```
npm run build
```
### Run your tests
```
npm run test
```
### Lints and fixes files
```
npm run lint
```
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).

3
gui_wails02_learn/frontend/babel.config.js

@ -0,0 +1,3 @@
module.exports = {
presets: [['@vue/app', { useBuiltIns: 'entry' }]]
}

12615
gui_wails02_learn/frontend/package-lock.json

File diff suppressed because it is too large

55
gui_wails02_learn/frontend/package.json

@ -0,0 +1,55 @@
{
"name": "Optimus",
"author": {
"name": "Christopher Murphy",
"email": "flyweight@protonmail.com"
},
"private": true,
"version": "0.5.0-beta",
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"dependencies": {
"@wailsapp/runtime": "^1.1.1",
"autoprefixer": "^9.8.6",
"core-js": "^3.6.4",
"regenerator-runtime": "^0.13.7",
"tailwindcss": "^1.9.1",
"v-tooltip": "^2.0.3",
"vue": "^2.6.12",
"vue-slider-component": "^3.2.6",
"vuex": "^3.5.1"
},
"devDependencies": {
"@vue/cli-plugin-babel": "^4.5.7",
"@vue/cli-plugin-eslint": "^4.5.7",
"@vue/cli-service": "^4.5.7",
"babel-eslint": "^10.1.0",
"eslint": "^7.11.0",
"eslint-plugin-vue": "^7.0.1",
"eventsource-polyfill": "^0.9.6",
"vue-template-compiler": "^2.6.12",
"webpack-hot-middleware": "^2.25.0"
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/essential",
"eslint:recommended"
],
"rules": {},
"parserOptions": {
"parser": "babel-eslint"
}
},
"browserslist": [
"> 1%",
"last 2 versions",
"not ie <= 8"
]
}

1
gui_wails02_learn/frontend/package.json.md5

@ -0,0 +1 @@
5426204eb3ec3fb253ed2169cd3fd066

6
gui_wails02_learn/frontend/postcss.config.js

@ -0,0 +1,6 @@
module.exports = {
plugins: [
require('tailwindcss')('tailwind.config.js'),
require('autoprefixer')()
]
}

57
gui_wails02_learn/frontend/src/App.vue

@ -0,0 +1,57 @@
<template>
<div id="app" class="bg-gray-900 flex relative h-full">
<Sidebar :active="currentView" v-on:select-view="handleViewSelect" />
<keep-alive>
<component :is="currentView" v-on:close-view="handleViewClose" />
</keep-alive>
<Notification />
</div>
</template>
<script>
import About from './components/About.vue'
import Editor from './components/Editor.vue'
import Notification from './components/Notification.vue'
import Settings from './components/Settings.vue'
import Sidebar from './components/Sidebar.vue'
import Stats from './components/Stats.vue'
import './assets/css/main.css'
export default {
name: 'app',
components: {
About,
Editor,
Notification,
Settings,
Sidebar,
Stats
},
data() {
return {
currentView: 'Editor'
}
},
methods: {
handleViewClose() {
this.currentView = 'Editor'
},
handleViewSelect(e) {
if (this.currentView === e) {
this.currentView = 'Editor'
} else {
this.currentView = e
}
}
},
mounted() {
this.$store.dispatch('getConfig')
this.$store.dispatch('getStats')
}
}
</script>

60
gui_wails02_learn/frontend/src/assets/css/animations.css

@ -0,0 +1,60 @@
.fade-enter-active, .fade-leave-active {
transition: opacity 600ms cubic-bezier(.07, .95, 0, 1);
}
.fade-enter, .fade-leave-to {
opacity: 0;
}
.fade-fast-enter-active, .fade-fast-leave-active {
transition: opacity 200ms ease-in-out;
}
.fade-fast-enter, .fade-fast-leave-to {
opacity: 0;
}
.fade-list-enter-active, .fade-list-leave-active {
transition: all 1.2s ease-in-out;
}
.fade-list-enter, .fade-list-leave-to {
opacity: 0;
transform: translateX(3rem);
}
.fade-list-move {
transition: all 1s ease-in-out;
}
.ta {
transition: all .3s cubic-bezier(.07, .95, 0, 1);
}
.ta-slow {
transition: all 1s cubic-bezier(.07, .95, 0, 1);
}
.ta-color {
transition: color .3s cubic-bezier(.07, .95, 0, 1);
}
.ta-color-slow {
transition: color 1s cubic-bezier(.07, .95, 0, 1);
}
.anime-txt-success {
animation: txt-success 600ms ease-in-out forwards;
}
@keyframes txt-success {
0% {
color: #b3b3b3;
}
50% {
color: #27ffa7;
}
100% {
color: #b3b3b3;
}
}

9
gui_wails02_learn/frontend/src/assets/css/buttons.css

@ -0,0 +1,9 @@
.btn {
@apply border-2 flex font-medium items-center justify-center px-8 py-2 rounded-full;
min-width: 136px;
min-height: 40px;
}
.btn--disabled {
@apply bg-gray-700 border-gray-700 cursor-default text-gray-400;
}

81
gui_wails02_learn/frontend/src/assets/css/input.css

@ -0,0 +1,81 @@
/* vue-select */
.dropdown-toggle {
background-color: #18181f;
background-image: none;
border-radius: 0.375rem;
color: #b3b3b3;
font-weight: 400;
margin: 0;
transition: color .3s cubic-bezier(.07, .95, 0, 1);
}
.dropdown-toggle:hover {
background-color: #18181f;
color: #27ffa7;
}
.dropdown-menu {
background-color: #18181f;
border: 0;
border-top: 1px solid #3a3a42;
border-top-left-radius: 0;
border-top-right-radius: 0;
}
.dropdown-menu > li > a {
color: #b3b3b3;
font-weight: 500;
transition: all .3s cubic-bezier(.07, .95, 0, 1);
}
.dropdown-menu > li > a:hover {
background-color: #27ffa7;
color: #18181f;
}
/* vue slider*/
.vue-slider-rail {
background-color: #18181f;
}
.vue-slider:hover .vue-slider-rail {
background-color: #18181f;
}
.vue-slider:hover .vue-slider-dot-handle {
border-color: transparent;
opacity: 1;
}
.vue-slider:hover .vue-slider-dot-handle:hover {
border-color: transparent;
}
.vue-slider-dot-handle {
background-color: #cbccd2;
border-color: transparent;
opacity: 0;
transition: all .6s cubic-bezier(.07, .95, 0, 1);
}
.slider-blue .vue-slider-dot-handle:hover,
.slider-pink .vue-slider-dot-handle:hover,
.slider-yellow .vue-slider-dot-handle:hover {
border-color: transparent;
}
.vue-slider-dot-handle:hover,
.vue-slider-process,
.vue-slider:hover .vue-slider-process,
.vue-slider-dot-handle {
background-color: #27ffa7;
}
.vue-slider-dot-tooltip-inner {
background-color: #18181f;
border-color: #18181f;
color: #f4f5f9;
}

37
gui_wails02_learn/frontend/src/assets/css/main.css

@ -0,0 +1,37 @@
@import url('animations.css');
@import url('buttons.css');
@import url('input.css');
@import url('tooltip.css');
@tailwind base;
@tailwind components;
@tailwind utilities;
#app {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: #b3b3b3;
}
* {
user-select: none;
}
html {
background-color: #18181f;
background-size: 20px 20px;
overflow: hidden;
height: 100%;
}
body {
scrollbar-base-color: #18181f;
scrollbar-face-color: #212128;
scrollbar-3dlight-color: #18181f;
scrollbar-highlight-color: #18181f;
scrollbar-track-color: #18181f;
scrollbar-arrow-color: #18181f;
scrollbar-shadow-color: #212128;
height: 100%;
}

11
gui_wails02_learn/frontend/src/assets/css/tooltip.css

@ -0,0 +1,11 @@
.tooltip {
background-color: #18181f;
/*border: 2px solid #3a3a42;*/
border-radius: 6px;
color: #f4f5f9;
display: block;
font-size: 14px;
max-width: 280px;
padding: 0.5rem 1rem;
z-index: 100;
}

BIN
gui_wails02_learn/frontend/src/assets/images/optimus-logo--640x360.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

78
gui_wails02_learn/frontend/src/components/About.vue

@ -0,0 +1,78 @@
<template>
<section class="bg-gray-800 overflow-y-auto p-10 w-full">
<header class="flex items-center justify-between w-full">
<h1 class="font-medium text-2xl text-purple-400">About</h1>
<BtnClose @click.native="closeView" color="purple" />
</header>
<div class="flex flex-wrap items-center justify-center pt-16 w-full">
<div class="flex items-center justify-center logo w-full h-64"></div>
<!-- info -->
<div class="text-center text-gray-100 w-full">
<p class="mb-1">{{ pkg.name }} {{ pkg.version }}</p>
<p class="mb-1">
© 2020
<span
class="cursor-pointer hover:text-purple-400 ta-color-slow"
@click="openLink('https://christophermurphy.dev')"
>{{ pkg.author.name }}</span
>
</p>
<p class="mb-1">
<span
class="cursor-pointer hover:text-purple-400 ta-color-slow"
@click="
openLink('https://github.com/Splode/optimus/blob/main/LICENSE')
"
>License</span
>
and
<span
class="cursor-pointer hover:text-purple-400 ta-color-slow"
@click="openLink('https://github.com/splode/optimus')"
>Documentation</span
>
</p>
</div>
</div>
</section>
</template>
<script>
import BtnClose from './BtnClose'
import pkg from './../../package.json'
export default {
name: 'About',
components: { BtnClose },
data() {
return {
pkg
}
},
methods: {
closeView() {
this.$emit('close-view')
},
openLink(u) {
window.wails.Browser.OpenURL(u)
}
}
}
</script>
<style scoped>
.logo {
background-image: url('./../assets/images/optimus-logo--640x360.png');
background-position: center;
background-repeat: no-repeat;
background-size: contain;
}
span {
box-shadow: 0 1px #d690ff;
}
</style>

57
gui_wails02_learn/frontend/src/components/BtnClose.vue

@ -0,0 +1,57 @@
<template>
<button
class="border-2 border-gray-800 cursor-pointer flex focus:outline-none hover:border-gray-400 hover:bg-gray-900 items-center justify-center p-2 ta-slow rounded-full w-10 h-10"
>
<svg
version="1.1"
id="x"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
x="0px"
y="0px"
viewBox="0 0 11.9 11.9"
style="enable-background:new 0 0 11.9 11.9;"
width="14"
height="14"
:class="color"
xml:space="preserve"
>
<path
fill="#b3b3b3"
d="M10.4,0L6,4.5L1.5,0L0,1.5L4.5,6L0,10.4l1.5,1.5L6,7.5l4.5,4.5l1.5-1.5L7.5,6l4.5-4.5L10.4,0z"
/>
</svg>
</button>
</template>
<script>
export default {
name: 'BtnClose',
props: {
color: {
type: String,
required: false,
default: 'green'
}
}
}
</script>
<style scoped>
button > svg > path {
transition: fill 0.6s cubic-bezier(0.07, 0.95, 0, 1);
}
button:hover > svg.blue > path {
fill: #27d1ff;
}
button:hover > svg.green > path {
fill: #27ffa7;
}
button:hover > svg.purple > path {
fill: #d690ff;
}
</style>

195
gui_wails02_learn/frontend/src/components/Dropdown.vue

@ -0,0 +1,195 @@
<template>
<div class="btn-group">
<li
@click="toggleMenu()"
class="dropdown-toggle py-2"
v-if="selectedOption.name !== undefined"
:class="showMenu ? 'rounded-bl-none rounded-br-none' : ''"
>
{{ selectedOption.name }}
<span class="caret"></span>
</li>
<li
@click="toggleMenu()"
class="dropdown-toggle"
v-if="selectedOption.name === undefined"
>
{{ placeholderText }}
<span class="caret"></span>
</li>
<ul class="dropdown-menu" v-if="showMenu">
<li v-for="(option, idx) in options" :key="idx">
<a href="javascript:void(0)" @click="updateOption(option)">
{{ option.name }}
</a>
</li>
</ul>
</div>
</template>
<script>
export default {
name: 'Dropdown',
data() {
return {
selectedOption: {
name: ''
},
showMenu: false,
placeholderText: 'Please select an item'
}
},
props: {
options: {
type: [Array, Object]
},
selected: {},
placeholder: [String],
closeOnOutsideClick: {
type: [Boolean],
default: true
}
},
watch: {
selected(o) {
this.updateOption(o)
}
},
mounted() {
this.selectedOption = this.selected
if (this.placeholder) {
this.placeholderText = this.placeholder
}
if (this.closeOnOutsideClick) {
document.addEventListener('click', this.clickHandler)
}
},
beforeDestroy() {
document.removeEventListener('click', this.clickHandler)
},
methods: {
updateOption(option) {
this.selectedOption = option
this.showMenu = false
this.$emit('update-option', this.selectedOption)
},
toggleMenu() {
this.showMenu = !this.showMenu
},
clickHandler(event) {
const { target } = event
const { $el } = this
if (!$el.contains(target)) {
this.showMenu = false
}
}
}
}
</script>
<style>
.btn-group {
min-width: 160px;
height: 40px;
position: relative;
margin: 10px 1px;
display: inline-block;
vertical-align: middle;
}
.btn-group a:hover {
text-decoration: none;
}
.dropdown-toggle {
color: #636b6f;
min-width: 160px;
padding: 10px 20px 10px 10px;
text-transform: none;
font-weight: 300;
margin-bottom: 7px;
border: 0;
background-image: linear-gradient(#009688, #009688),
linear-gradient(#d2d2d2, #d2d2d2);
background-size: 0 2px, 100% 1px;
background-repeat: no-repeat;
background-position: center bottom, center calc(100% - 1px);
background-color: transparent;
transition: background 0s ease-out;
float: none;
box-shadow: none;
border-radius: 0;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
.dropdown-toggle:hover {
background: #e1e1e1;
cursor: pointer;
}
.dropdown-menu {
position: absolute;
top: 100%;
left: 0;
z-index: 1000;
float: left;
min-width: 160px;
/*padding: 5px 0;*/
/*margin: 2px 0 0;*/
list-style: none;
font-size: 14px;
text-align: left;
background-color: #fff;
border: 1px solid #ccc;
border-radius: 4px;
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
background-clip: padding-box;
}
.dropdown-menu > li > a {
padding: 10px 30px;
display: block;
clear: both;
font-weight: normal;
line-height: 1.6;
color: #333333;
white-space: nowrap;
text-decoration: none;
}
.dropdown-menu > li > a:hover {
background: #efefef;
color: #409fcb;
}
.dropdown-menu > li {
overflow: hidden;
width: 100%;
position: relative;
margin: 0;
}
.caret {
width: 0;
position: absolute;
top: 19px;
height: 0;
margin-left: -24px;
vertical-align: middle;
border-top: 4px dashed;
border-right: 4px solid transparent;
border-left: 4px solid transparent;
right: 10px;
}
li {
list-style: none;
}
</style>

746
gui_wails02_learn/frontend/src/components/Editor.vue

@ -0,0 +1,746 @@
<template>
<section class="p-10 w-full">
<header class="border-b-2 border-gray-800 flex flex-wrap" ref="header">
<div class="w-full md:w-1/2 lg:w-5/12">
<div
class="bg-gray-800 border-2 border-dashed cursor-pointer drop-zone flex flex-col items-center justify-center py-6 md:py-10 ta-slow rounded-sm h-full"
:class="isDragging ? 'border-green' : 'border-gray-400'"
ref="dropZone"
>
<svg
version="1.1"
id="dropZone-plus"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
x="0px"
y="0px"
viewBox="0 0 48 48"
enable-background="new 0 0 48 48"
width="48px"
height="48px"
xml:space="preserve"
>
<path
fill="#808080"
d="M47,20.6H28.4c-0.6,0-1-0.4-1-1V1c0-0.6-0.4-1-1-1h-4.9c-0.6,0-1,0.4-1,1v18.6c0,0.6-0.4,1-1,1H1
c-0.6,0-1,0.4-1,1v4.9c0,0.6,0.4,1,1,1h18.6c0.6,0,1,0.4,1,1V47c0,0.6,0.4,1,1,1h4.9c0.6,0,1-0.4,1-1V28.4c0-0.6,0.4-1,1-1H47
c0.6,0,1-0.4,1-1v-4.9C48,21,47.6,20.6,47,20.6z"
/>
</svg>
<p class="mt-6 text-gray-200">Drag and drop or select images</p>
</div>
</div>
<div class="lg:w-7/12 md:pl-6 md:w-1/2 my-3 w-full">
<transition name="fade" mode="out-in">
<div
v-if="!session.count"
key="intro"
class="flex h-full items-center justify-center"
>
<h2 class="leading-none text-4xl text-center text-yellow">
Add image files<br />to get started
</h2>
</div>
<div v-else key="stats" class="flex flex-wrap items-end h-full">
<div class="px-3 w-full lg:w-5/12">
<div v-if="session.hasSavings">
<h2
class="font-bold leading-none text-4xl md:text-5xl text-green tracking-tight"
>
{{ session.savings }}
</h2>
<p class="font-medium text-gray-300 tracking-wider uppercase">
Saved
</p>
</div>
<div v-else>
<h2
class="font-bold leading-none text-5xl text-pink tracking-tight uppercase"
>
No Savings
</h2>
<p class="font-medium text-gray-300 tracking-wider uppercase">
Try adjusting options
</p>
</div>
</div>
<div class="flex lg:flex-col justify-between px-3 w-full lg:w-3/12">
<div class="w-1/2 lg:w-full">
<p class="font-bold text-xl lg:text-2xl text-blue">
{{ session.count }}
</p>
<p
class="font-medium text-gray-300 text-sm tracking-wider uppercase"
>
{{ session.count > 1 ? 'Images' : 'Image' }}
</p>
</div>
<div class="w-1/2 lg:w-full">
<p class="font-bold text-xl lg:text-2xl text-yellow">
{{ session.time[0] }}
</p>
<p
class="font-medium text-gray-300 text-sm tracking-wider uppercase"
>
{{ session.time[1] }}
</p>
</div>
</div>
<div class="flex lg:flex-col justify-between px-3 w-full lg:w-4/12">
<div class="w-1/2 lg:w-full">
<p class="font-bold text-xl lg:text-2xl text-pink">
{{ totalStats.byteCount }}
</p>
<p
class="font-medium text-gray-300 text-sm tracking-wider uppercase"
>
All time Savings
</p>
</div>
<div class="w-1/2 lg:w-full">
<p class="font-bold text-xl lg:text-2xl text-purple">
{{ totalStats.imageCount }}
</p>
<p
class="font-medium text-gray-300 text-sm tracking-wider uppercase"
>
All time Images
</p>
</div>
</div>
</div>
</transition>
</div>
<footer class="w-full">
<section
class="flex justify-between lg:w-5/12 md:w-1/2 py-3 md:py-6 w-full"
>
<button
class="btn focus:outline-none ta-slow"
:class="
canConvert
? 'border-green hover:bg-green hover:text-gray-900 text-gray-200'
: 'btn--disabled'
"
@click="convert"
:disabled="!canConvert"
>
{{ optBtnTxt }}
</button>
<button
class="btn focus:outline-none ta-slow"
:class="
canClear
? 'border-gray-400 hover:bg-gray-800 hover:text-green'
: 'btn--disabled'
"
@click="clear"
:disabled="!canClear"
>
Clear
</button>
</section>
</footer>
</header>
<input
type="file"
accept="image/jpeg, image/jpg, image/png, image/webp"
multiple
class="hidden"
@input="handleFileInput"
ref="fileInput"
/>
<!-- file table -->
<transition name="fade" mode="out-in">
<div
v-if="files.length > 0"
class="table-wrapper"
:style="{ height: `calc(100vh - ${headerHeight + 80}px)` }"
>
<table class="table-auto w-full text-left whitespace-no-wrap">
<thead>
<tr>
<th
class="font-medium pl-3 pt-6 text-gray-400 text-left text-sm tracking-wider uppercase"
>
File
</th>
<th
class="font-medium pl-3 pt-6 text-gray-400 text-left text-sm tracking-wider uppercase"
>
Size
</th>
<th
class="font-medium pl-3 pt-6 text-gray-400 text-left text-sm tracking-wider uppercase"
>
Compressed
</th>
<th
class="font-medium pl-3 pt-6 text-gray-400 text-left text-sm tracking-wider uppercase"
>
Ratio
</th>
<!-- <th-->
<!-- class="font-medium pl-3 pt-6 text-gray-400 text-left text-sm tracking-wider uppercase"-->
<!-- >-->
<!-- Result-->
<!-- </th>-->
<th
class="font-medium pl-3 pt-6 text-center text-gray-400 text-left text-sm tracking-wider uppercase"
>
Status
</th>
</tr>
</thead>
<tbody>
<tr v-for="(file, i) in files" :key="`${i}-${file.name}`">
<td>
<p
class="cell-l ta"
:class="{
'text-gray-400': !file.isProcessed,
'text-gray-200': file.isProcessed,
'anime-txt-success': file.isConverted
}"
>
{{ file.filename }}
</p>
</td>
<td>
<p
class="ta"
:class="{
'text-gray-400': !file.isProcessed,
'text-gray-200': file.isProcessed,
'anime-txt-success': file.isConverted
}"
>
{{ getPrettySize(file.size) }}
</p>
</td>
<td>
<p :class="{ 'anime-txt-success': file.isConverted }">
{{ getPrettySize(file.convertedSize) }}
</p>
</td>
<td>
<p :class="{ 'anime-txt-success': file.isConverted }">
{{ getSavings(file) }}
</p>
</td>
<!-- <td @click="openFile(file)"><p>{{ file.convertedPath }}</p>-->
<!-- </td>-->
<td>
<p
v-if="file.isConverted"
class="cell-r flex items-center justify-center"
>
<svg
version="1.1"
:id="`${i}-check`"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
x="0px"
y="0px"
viewBox="0 0 20 20"
enable-background="new 0 0 20 20"
width="20px"
height="20px"
xml:space="preserve"
>
<path
fill="#27ffa7"
d="M10,0C4.5,0,0,4.5,0,10s4.5,10,10,10s10-4.5,10-10S15.5,0,10,0z M8,14.4l-3.7-3.7l1.4-1.4L8,11.6l5.3-5.3
l1.4,1.4L8,14.4z"
/>
</svg>
</p>
<p v-else class="cell-r"></p>
</td>
</tr>
</tbody>
</table>
</div>
</transition>
</section>
</template>
<script>
import { fExt, fName, fSize } from '@/lib/file'
import { EventBus } from '@/lib/event-bus'
import Wails from '@wailsapp/runtime'
import { prettyTime } from '@/lib/time'
export default {
name: 'Editor',
data() {
return {
files: [],
headerHeight: 0,
isConverting: false,
isDragging: false
}
},
computed: {
/**
* canClear returns true if the file list can be cleared.
* @returns {boolean}
*/
canClear() {
if (this.files.length === 0) return false
return !this.filesPending
},
/**
* canConvert returns true if the file list satisfies conditions for
* conversion.
* @returns {boolean}
*/
canConvert() {
if (this.files.length === 0 || this.isConverting) return false
return this.filesConverted && !this.filesPending
},
/**
* filesConverted returns true if all files have been converted.
* @returns {boolean}
*/
filesConverted() {
if (this.files.length === 0) return false
return this.files.some(f => !f.isConverted)
},
/**
* filesPending returns true if there are files not processed.
* @returns {boolean}
*/
filesPending() {
if (this.files.length === 0) return false
return this.files.some(f => !f.isProcessed)
},
/**
* optBtnTxt returns the text string for the Optimize button per the
* current app state.
* @returns {string}
*/
optBtnTxt() {
const d = 'Optimize'
if (this.files.length === 0) return d
if (this.filesPending) return 'Processing...'
return d
},
/**
* session returns the current session stats.
* @returns {object}
*/
session() {
return this.$store.getters.session
},
/**
* totalStats returns the all-time stats.
*/
totalStats() {
return this.$store.getters.stats
}
},
methods: {
/**
* clear removes the files from the file list and the FileManager.
*/
clear() {
this.files = []
this.$refs['fileInput'].value = null
window.backend.FileManager.Clear()
.then(res => {
console.log(res)
})
.catch(err => {
console.error(err)
})
},
/**
* convert calls the Convert method on the FileManager.
*/
convert() {
this.isConverting = true
window.backend.FileManager.Convert()
.then(res => {
console.log(res)
})
.catch(err => {
console.error(err)
})
},
/**
* createFileId creates a file ID based on its name and size.
* @param {string} name - The filename without extension.
* @param {number} size - The file size in bytes.
* @returns {string} - The file ID.
*/
createFileId(name, size) {
return name + size.toString()
},
/**
* getFileById returns the file from the file list with the given
* ID.
* @param {string} id - The file ID.
* @returns {object} - The matched file.
*/
getFileById(id) {
if (this.files.length === 0) return
return this.files.find(f => {
return f.id === id
})
},
/**
* getPrettySize returns a pretty string of file size given bytes.
* @param {number} size - Size in bytes.
* @returns {string} - The pretty size.
*/
getPrettySize(size) {
if (size === 0) return ''
return fSize(size)
},
/**
* getSavings returns the percentage difference between a file's
* original and converted sizes as a pretty string.
* @param {object} file - A file in the file list.
* @returns {string} - The file savings as string.
*/
getSavings(file) {
if (file.convertedSize === 0) return ''
const p = Math.floor(100 - (file.convertedSize / file.size) * 100)
return `${p.toString()}%`
},
/**
* getFileType attempts to determine the file image type based on the
* given file type and extension. This method exists due to IE's lack of
* support for WebP.
* @param {string} type - The file type.
* @param {string} ext - The file extension.
* @returns {string}
*/
getFileType(type, ext) {
if (this.isValidType(type)) return type
if (this.isValidExt(ext)) return `image/${ext}`
return ''
},
/**
* handleFileInput handles the file submission via form input.
* @param {Event} e
*/
handleFileInput(e) {
const f = e.target.files
this.processFileInput(f)
},
/**
* hasFile returns true if the file is in the file list.
* @param {string} id - The file ID.
* @returns {boolean}
*/
hasFile(id) {
if (this.files.length === 0) return false
let e = false
this.files.forEach(f => {
if (f.id === id) {
e = true
}
})
return e
},
/**
* handleWinResize calculates the height of the table based on the height
* of the header.
*/
handleWinResize() {
const hh = this.$refs['header'].clientHeight
if (hh === this.headerHeight) return
this.headerHeight = hh
},
/**
* isValidExt returns true if the given file extension is of an accepted
* set of extensions.
* @param {string} ext - A file extension
* @returns {boolean}
*/
isValidExt(ext) {
const v = ['jpg', 'jpeg', 'png', 'webp']
return v.indexOf(ext) >= 0
},
/**
* isValidType returns true if the given type is one of an accepted
* set of mime types.
* @param {string} type - A file's mime type.
* @return {boolean}
*/
isValidType(type) {
const v = [
'image/jpg',
'image/jpeg',
'image/.jpg',
'image/png',
'image/webp'
]
return v.indexOf(type) >= 0
},
/**
* openFile opens the file at the given file path.
*/
openFile(file) {
// TODO: can this be called directly from Wails?
window.backend.FileManager.OpenFile(file.convertedPath)
.then(res => {
console.log(res)
})
.catch(err => {
console.error(err)
})
},
/**
* processFileInput processes a list of submitted files.
* @param {FileList} f - A FileList array of files.
*/
processFileInput(f) {
f.forEach(f => {
const name = fName(f.name)
const ext = fExt(f.name)
const type = this.getFileType(f.type, ext)
const size = f.size
const id = this.createFileId(name, size)
// reject if wrong mime or already exists
if (!type) {
EventBus.$emit('notify', {
msg: `File type not supported. Valid file types include JPG, PNG, and WebP.`,
type: 'warn'
})
return
}
if (this.hasFile(id)) {
EventBus.$emit('notify', {
msg: `Image file has already been added to the file list.`,
type: 'warn'
})
return
}
this.processFile(f, id, type)
this.files.push({
convertedPath: '',
convertedSize: 0,
filename: f.name,
id,
isConverted: false,
isProcessed: false,
name,
size
})
})
this.$refs['fileInput'].value = null
},
/**
* processFile encodes a file and sends it to the backend for further
* processing.
* @param {File} file
* @param {string} id
* @param {string} type
*/
processFile(file, id, type) {
const reader = new FileReader()
reader.onload = () => {
const name = file.name
window.backend.FileManager.HandleFile(
JSON.stringify({
data: reader.result.split(',')[1],
ext: fExt(name),
id,
name: fName(name),
size: file.size,
type
})
)
.then(() => {
const f = this.getFileById(id)
f.isProcessed = true
})
.catch(err => {
this.removeFileById(id)
EventBus.$emit('notify', {
msg: err,
type: 'warn'
})
console.error(err)
})
}
reader.readAsDataURL(file)
},
/**
* removeFileById removes a file from the file list by the given ID.
* @param {string} id
*/
removeFileById(id) {
const i = this.files.findIndex(f => f.id === id)
if (i < 0) return
this.files.splice(i, 1)
}
},
mounted() {
Wails.Events.On('conversion:complete', e => {
const f = this.getFileById(e.id)
if (!f) return
f.convertedPath = e.path
f.isConverted = true
f.convertedSize = e.size
})
Wails.Events.On('conversion:stat', e => {
const c = e.count
const r = e.resizes
const s = e.savings
const t = e.time
this.$store.dispatch('setSessionProp', { key: 'count', value: c })
this.$store.dispatch('setSessionProp', { key: 'time', value: t })
this.$store.dispatch('getStats')
if (s > 0) {
this.$store.dispatch('setSessionProp', { key: 'savings', value: s })
}
if (r > 0) {
EventBus.$emit('notify', {
msg: `Optimized ${c} ${c > 1 ? 'images' : 'image'} and made ${r} ${
r > 1 ? 'resizes' : 'resize'
} in ${prettyTime(t)[0]} ${prettyTime(t)[1].toLowerCase()}.`
})
} else {
EventBus.$emit('notify', {
msg: `Optimized ${c} ${c > 1 ? 'images' : 'image'} in ${
prettyTime(t)[0]
} ${prettyTime(t)[1].toLowerCase()}.`
})
}
this.isConverting = false
})
const dz = this.$refs['dropZone']
dz.addEventListener(
'click',
() => {
this.$refs['fileInput'].click()
},
false
)
dz.addEventListener(
'dragenter',
e => {
e.stopPropagation()
e.preventDefault()
},
false
)
dz.addEventListener(
'dragover',
e => {
e.stopPropagation()
e.preventDefault()
if (this.isDragging) return
this.isDragging = true
},
false
)
dz.addEventListener(
'dragend',
e => {
e.stopPropagation()
e.preventDefault()
this.isDragging = false
},
false
)
dz.addEventListener(
'dragleave',
e => {
e.stopPropagation()
e.preventDefault()
this.isDragging = false
},
false
)
dz.addEventListener(
'drop',
e => {
e.stopPropagation()
e.preventDefault()
const dt = e.dataTransfer
const f = dt.files
this.isDragging = false
this.processFileInput(f)
},
false
)
window.addEventListener('resize', this.handleWinResize)
this.handleWinResize()
}
}
</script>
<style scoped>
.table-wrapper {
max-width: calc(100vw - 160px);
min-height: 80px;
overflow: auto;
}
td {
margin: 0;
padding: 0;
}
table tr:nth-child(odd) p {
@apply bg-gray-800;
}
td p {
@apply my-1 pl-3 py-2;
min-height: 40px;
}
td p.cell-l {
border-top-left-radius: 6px;
border-bottom-left-radius: 6px;
}
td p.cell-r {
border-top-right-radius: 6px;
border-bottom-right-radius: 6px;
margin-right: 1rem;
}
.drop-zone > svg path {
transition: fill 600ms cubic-bezier(0.07, 0.95, 0, 1);
}
.drop-zone:hover > svg path {
fill: #27ffa7;
}
</style>

102
gui_wails02_learn/frontend/src/components/Notification.vue

@ -0,0 +1,102 @@
<template>
<section class="absolute bottom-0 right-0">
<transition name="fade">
<div v-if="notifications.length > 0" class="p-10">
<transition-group name="fade-list">
<div
v-for="(n, i) in notifications"
:key="`${i}-notification`"
class="bg-gray-700 border-l-2 flex mt-2 p-4 pr-6 ta rounded-br-md rounded-tr-md"
:class="n.type === 'warn' ? 'border-red' : 'border-green'"
>
<div class="mr-3">
<!-- warn -->
<svg
v-if="n.type === 'warn'"
version="1.1"
id="warn-icon"
xmlns="http://www.w3.org/2000/svg"
x="0px"
y="0px"
viewBox="0 0 20 19"
style="enable-background:new 0 0 20 19;"
width="24"
height="24"
xml:space="preserve"
>
<path
fill="#F84D53"
d="M10.9,0.5c-0.3-0.7-1.4-0.7-1.8,0l-9,17c-0.2,0.3-0.2,0.7,0,1C0.3,18.8,0.6,19,1,19h18c0.4,0,0.7-0.2,0.9-0.5
c0.2-0.3,0.2-0.7,0-1L10.9,0.5z M11,16H9v-2h2V16z M9,12V7h2l0,5H9z"
/>
</svg>
<!-- success -->
<svg
v-else
version="1.1"
id="check-icon"
xmlns="http://www.w3.org/2000/svg"
x="0px"
y="0px"
viewBox="0 0 24 24"
style="enable-background:new 0 0 24 24;"
width="24"
height="24"
xml:space="preserve"
>
<path
fill="#27ffa7"
d="M10,15.6l-3.3-3.3l-1.4,1.4l4.7,4.7l9.7-9.7l-1.4-1.4L10,15.6z"
/>
</svg>
</div>
<p class="text-gray-100">{{ n.msg }}</p>
</div>
</transition-group>
</div>
</transition>
</section>
</template>
<script>
import { EventBus } from '@/lib/event-bus'
import Wails from '@wailsapp/runtime'
export default {
name: 'Notification',
data() {
return {
isClearing: false,
notifications: []
}
},
methods: {
clear() {
if (this.notifications.length <= 0) {
this.isClearing = false
return
}
this.isClearing = true
setTimeout(() => {
this.notifications.pop()
this.clear()
}, 6000)
},
notify(n) {
this.notifications.unshift({ msg: n.msg, type: n.type })
if (!this.isClearing) {
this.clear()
}
}
},
mounted() {
EventBus.$on('notify', this.notify)
Wails.Events.On('notify', this.notify)
}
}
</script>
<style scoped></style>

533
gui_wails02_learn/frontend/src/components/Settings.vue

@ -0,0 +1,533 @@
<template>
<section
class="bg-gray-800 overflow-y-auto p-10 w-full"
id="settings"
ref="section"
>
<header class="flex items-center justify-between w-full">
<h1 class="font-medium text-2xl text-green">Options</h1>
<BtnClose @click.native="closeView" />
</header>
<div
class="border-2 border-gray-700 flex flex-wrap my-4 p-4 rounded-md w-full"
>
<h2 class="mb-3 text-gray-200 text-xl w-full">General</h2>
<div class="flex items-center mr-6 my-2 px-4 text-gray-100">
<p class="mr-4" v-tooltip.right-end="'Image conversion file target.'">
Target
</p>
<Dropdown
:options="targets"
:selected="target"
class="m-0 text-gray-200"
v-on:update-option="selectTarget"
></Dropdown>
</div>
<div class="flex flex-wrap items-center mr-8 my-2 px-4 text-gray-100">
<p v-tooltip="'The output directory for converted images.'">
Destination
</p>
<p
@click="selectOutDir"
class="bg-gray-900 cursor-pointer font-mono hover:text-green mx-4 px-4 py-2 rounded-md ta-color-slow"
>
{{ config.outDir }}
</p>
<button
@click="openDir"
class="cursor-pointer"
v-tooltip.right-end="'Open destination directory'"
>
<svg
height="18"
id="Layer_1"
style="enable-background:new 0 0 24 24;"
version="1.1"
viewBox="0 0 24 24"
width="20"
x="0px"
xml:space="preserve"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
y="0px"
>
<path
d="M20,3H4C2.9,3,2,3.9,2,5v14c0,1.1,0.9,2,2,2h5v-2H4V7h16v12h-5v2h5c1.1,0,2-0.9,2-2V5C22,3.9,21.1,3,20,3z"
fill="#b3b3b3"
/>
<path d="M13,21v-5h3l-4-5l-4,5h3v5H13z" fill="#b3b3b3" />
</svg>
</button>
</div>
<div class="flex flex-wrap items-center my-2 px-4 text-gray-100">
<label
for="prefix"
v-tooltip.right-end="{
content: 'A string to prepend to the converted image filename.'
}"
>Prefix</label
>
<input
@change="setConfig"
class="bg-gray-900 cursor-pointer focus:outline-none hover:text-green mx-4 px-4 py-2 rounded-md ta-color-slow"
id="prefix"
maxlength="16"
type="text"
v-model="config.prefix"
/>
</div>
<div class="flex flex-wrap items-center my-2 px-4 text-gray-100">
<label
for="suffix"
v-tooltip.right-end="{
content: 'A string to append to the converted image filename.'
}"
>Suffix</label
>
<input
@change="setConfig"
class="bg-gray-900 cursor-pointer focus:outline-none hover:text-green mx-4 px-4 py-2 rounded-md ta-color-slow"
id="suffix"
maxlength="16"
type="text"
v-model="config.suffix"
/>
</div>
</div>
<div
class="border-2 border-gray-700 flex flex-wrap my-4 px-2 py-4 rounded-md w-full"
>
<header class="flex px-2 w-full">
<h2 class="text-gray-200 text-xl">Resizing</h2>
<button
@click="addSize"
class="border-0 focus:outline-none hover:green hover:text-green ml-8 ta-slow"
v-tooltip.right-end="{
content: 'Add an image size to convert.',
delay: 600
}"
>
Add Size +
</button>
</header>
<div
:key="i"
class="flex flex-wrap items-center my-2 px-4 text-gray-100 w-full"
v-for="(s, i) in config.sizes"
>
<div class="mb-2 lg:mb-0 px-2 w-full sm:w-1/2 lg:w-auto">
<label :for="`width-${i}`" class="block xl:inline-block mb-2 mr-4"
><span
v-tooltip.right-end="
'Width in pixels. Required and must be a positive integer.'
"
>Width</span
></label
>
<input
:id="`width-${i}`"
@blur="setConfig"
@input="handleNumber"
class="bg-gray-900 cursor-pointer focus:outline-none hover:text-green px-4 py-2 rounded-md ta-color-slow w-full lg:w-auto"
type="text"
v-model.number="s.width"
/>
</div>
<div class="mb-2 lg:mb-0 px-2 w-full sm:w-1/2 lg:w-auto">
<label :for="`height-${i}`" class="block xl:inline-block mb-2 mr-4"
><span
v-tooltip.right-end="
'Height in pixels. Required and must be a positive integer.'
"
>Height</span
></label
>
<input
:id="`height-${i}`"
@blur="setConfig"
@input="handleNumber"
class="bg-gray-900 cursor-pointer focus:outline-none hover:text-green px-4 py-2 rounded-md ta-color-slow w-full lg:w-auto"
type="text"
v-model.number="s.height"
/>
</div>
<div class="mb-2 lg:mb-0 px-2 w-full sm:w-1/2 lg:w-auto">
<label class="block xl:inline-block mb-2 mr-4">Strategy</label>
<div class="flex lg:inline-flex w-full lg:w-auto">
<Dropdown
:options="strategies"
:selected="strategy(s)"
@click.native="handleSelectStrategy(i)"
class="my-0 mr-4 text-gray-200 w-full"
v-on:update-option="selectStrategy"
v-tooltip="strategyTooltip(s)"
></Dropdown>
<button @click="removeSize(i)">
<svg
height="10"
id="x"
style="enable-background:new 0 0 11.9 11.9;"
version="1.1"
viewBox="0 0 11.9 11.9"
width="10"
x="0px"
xml:space="preserve"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
y="0px"
>
<path
d="M10.4,0L6,4.5L1.5,0L0,1.5L4.5,6L0,10.4l1.5,1.5L6,7.5l4.5,4.5l1.5-1.5L7.5,6l4.5-4.5L10.4,0z"
fill="#b3b3b3"
/>
</svg>
</button>
</div>
</div>
</div>
</div>
<div
class="border-2 border-gray-700 flex flex-wrap my-4 p-4 rounded-md w-full"
>
<h2 class="mb-3 text-gray-200 text-xl w-full">WebP</h2>
<div class="px-4 text-gray-100 w-1/2">
<div class="flex items-center w-full">
<p class="mr-6" v-tooltip.right-end="{ content: tooltips.quality }">
Quality
</p>
<div class="w-full">
<vue-slider @change="setConfig" v-model="config.webpOpt.quality" />
</div>
</div>
</div>
<div class="px-4 text-gray-100 w-1/2">
<div class="flex items-center w-full">
<p class="mr-4">Lossless</p>
<div
@click="toggleWebpLossless"
class="bg-gray-900 check-wrapper flex items-center justify-center rounded-md"
>
<transition mode="out-in" name="fade">
<svg
height="24"
id="check-icon"
style="enable-background:new 0 0 24 24;"
v-if="config.webpOpt.lossless"
version="1.1"
viewBox="0 0 24 24"
width="24"
x="0px"
xml:space="preserve"
xmlns="http://www.w3.org/2000/svg"
y="0px"
>
<path
d="M10,15.6l-3.3-3.3l-1.4,1.4l4.7,4.7l9.7-9.7l-1.4-1.4L10,15.6z"
fill="#27ffa7"
/>
</svg>
</transition>
</div>
</div>
</div>
</div>
<div
class="border-2 border-gray-700 flex flex-wrap my-4 p-4 rounded-md w-full"
>
<h2 class="mb-3 text-gray-200 text-xl w-full">JPEG</h2>
<div class="px-4 text-gray-100 w-1/2">
<div class="flex items-center w-full">
<p class="mr-6" v-tooltip.right-end="{ content: tooltips.quality }">
Quality
</p>
<div class="w-full">
<vue-slider @change="setConfig" v-model="config.jpegOpt.quality" />
</div>
</div>
</div>
</div>
<div
class="border-2 border-gray-700 flex flex-wrap my-4 p-4 rounded-md w-full"
>
<h2 class="mb-3 text-gray-200 text-xl w-full">PNG</h2>
<div class="px-4 text-gray-100 w-1/2">
<div class="flex items-center w-full">
<p
class="mr-6"
title="Specify the image conversion quality."
v-tooltip.right-end="{ content: tooltips.quality }"
>
Quality
</p>
<div class="w-full">
<vue-slider @change="setConfig" v-model="config.pngOpt.quality" />
</div>
</div>
</div>
</div>
<div class="mb-4 w-full">
<button
@click="restoreDefaults"
class="border-gray-400 btn focus:outline-none hover:bg-green hover:border-green hover:text-gray-900 ml-auto ta-slow"
>
Restore Defaults
</button>
</div>
</section>
</template>
<script>
import BtnClose from './BtnClose'
import Dropdown from './Dropdown'
import VueSlider from 'vue-slider-component'
import 'vue-slider-component/theme/antd.css'
import { EventBus } from '@/lib/event-bus'
export default {
name: 'Settings',
components: { BtnClose, Dropdown, VueSlider },
data() {
return {
activeStrategy: 0,
strategies: [
{ name: 'Fill', value: 0 },
{ name: 'Fit', value: 1 },
{ name: 'Smart Crop', value: 2 }
],
targets: [
{ name: 'WebP', value: 'webp' },
{
name: 'JPEG',
value: 'jpg'
},
{ name: 'PNG', value: 'png' }
],
tooltips: {
quality:
'Image quality of the converted image where 0 is the lowest and 100 is the highest.'
}
}
},
computed: {
/**
* config returns the app configuration from state.
* @returns {object}
*/
config() {
return this.$store.getters.config
},
target() {
return (
this.targets.find(t => this.config.target === t.value) || {
name: '',
value: ''
}
)
}
},
methods: {
/**
* addSize adds a blank size to the size config.
*/
addSize() {
this.$store.dispatch('addSize')
},
/**
* removeSize removes the selected size rect from the sizes config.
* @param {number} i - The size index.
*/
removeSize(i) {
this.$store.dispatch('removeSize', i)
this.setConfig()
},
/**
* handleSelectStrategy records the index of the currently active size input.
* @param {number} i - The size index.
*/
handleSelectStrategy(i) {
this.activeStrategy = i
},
/**
* selectStrategy updates the configured size with the selected strategy.
* @param {object} e - The selected strategy.
*/
selectStrategy(e) {
this.$store
.dispatch('setSizeStrategy', {
index: this.activeStrategy,
value: e.value
})
.then(() => {
this.setConfig()
})
.catch(err => {
console.log(err)
})
},
/**
* strategy returns the strategy from the list of strategies matching the
* current strategy.
* @param {object} s - The current size.
* @returns {object} The matching strategy.
*/
strategy(s) {
return this.strategies.find(o => s.strategy === o.value)
},
/**
* strategyTooltip returns a tooltip content string based on the selected
* strategy.
* @param {object} s - Current size.
* @returns string The tooltip content.
*/
strategyTooltip(s) {
switch (s.strategy) {
case 0: {
return 'Crop resizes the image to the specified dimensions, cropping excess from the center.'
}
case 1: {
return "Fit resizes the image to fit within the specified dimensions, maintaining the image's aspect ratio."
}
default: {
return ''
}
}
},
/**
* closeView closes the current view.
*/
closeView() {
this.$emit('close-view')
},
/**
* handleNumber validates a number input and displays an error message if
* the input cannot be parsed.
* @param {InputEvent} e
*/
handleNumber(e) {
if (!e.data) return
const n = parseInt(e.data, 10)
if (isNaN(n)) {
EventBus.$emit('notify', {
msg: `Image size must be a number.`,
type: 'warn'
})
}
},
/**
* openDir opens the configured output directory.
*/
openDir() {
window.backend.Config.OpenOutputDir()
.then(res => {
console.log(res)
})
.catch(err => {
console.error(err)
})
},
/**
* restoreDefaults resets the app configuration to defaults.
*/
restoreDefaults() {
window.backend.Config.RestoreDefaults()
.then(() => {
this.$store.dispatch('getConfig')
})
.catch(err => {
console.error(err)
})
},
/**
* selectOutDir selects an output directory in the config.
*/
selectOutDir() {
window.backend.Config.SetOutDir()
.then(res => {
console.log(res)
this.$store.dispatch('getConfig')
})
.catch(err => {
console.error(err)
})
},
/**
* selectTarget updates the config target.
* @param {object} e - The selected target.
*/
selectTarget(e) {
this.$store
.dispatch('setConfigProp', { key: 'target', value: e.value })
.then(() => {
this.setConfig()
})
.catch(err => {
console.error(err)
})
},
/**
* setConfig updates the configuration with the current state.
*/
setConfig() {
this.$store.dispatch('setConfig', this.config)
},
/**
* toggleWebpLossless toggles the lossless property of the WebP config.
*/
toggleWebpLossless() {
this.$store
.dispatch('toggleWebpLossless')
.then(() => {
this.setConfig()
})
.catch(err => {
console.error(err)
})
}
}
}
</script>
<style scoped>
input:focus {
color: #27ffa7;
}
button > svg > path {
transition: fill 0.6s cubic-bezier(0.07, 0.95, 0, 1);
}
button:hover > svg > path {
fill: #27ffa7;
}
.check-wrapper {
border: 2px solid transparent;
cursor: pointer;
width: 24px;
height: 24px;
}
</style>

152
gui_wails02_learn/frontend/src/components/Sidebar.vue

@ -0,0 +1,152 @@
<template>
<nav class="bg-gray-900 flex flex-col items-center pt-10 w-24 z-40">
<div
@click="selectView('Settings')"
class="cursor-pointer mb-2 p-2 rounded-full w-10 h-10"
>
<svg
version="1.1"
id="cog"
xmlns="http://www.w3.org/2000/svg"
:class="{ active: active === 'Settings' }"
xmlns:xlink="http://www.w3.org/1999/xlink"
x="0px"
y="0px"
viewBox="0 0 19.6 20"
enable-background="new 0 0 19.6 20"
xml:space="preserve"
>
<path
fill="#b3b3b3"
d="M0.1,13.3l2,3.5c0.3,0.5,0.9,0.6,1.4,0.4l1.4-0.8c0.6,0.5,1.2,0.8,1.9,1.1V19c0,0.6,0.4,1,1,1h4
c0.6,0,1-0.4,1-1v-1.6c0.7-0.3,1.3-0.7,1.9-1.1l1.4,0.8c0.5,0.3,1.1,0.1,1.4-0.4l2-3.5c0.3-0.5,0.1-1.1-0.4-1.4l-1.4-0.8
c0.1-0.4,0.1-0.7,0.1-1.1s0-0.7-0.1-1.1l1.4-0.8c0.5-0.3,0.6-0.9,0.4-1.4l-2-3.5c-0.3-0.5-0.9-0.6-1.4-0.4l-1.4,0.8
c-0.6-0.5-1.2-0.8-1.9-1.1V1c0-0.6-0.4-1-1-1h-4c-0.6,0-1,0.4-1,1v1.6C6.1,2.9,5.5,3.3,4.9,3.7L3.5,2.9C3,2.6,2.4,2.8,2.1,3.3
l-2,3.5C-0.1,7.2,0,7.8,0.5,8.1l1.4,0.8C1.8,9.3,1.8,9.6,1.8,10s0,0.7,0.1,1.1l-1.4,0.8C0,12.2-0.1,12.8,0.1,13.3z M9.8,6
c2.2,0,4,1.8,4,4s-1.8,4-4,4s-4-1.8-4-4S7.6,6,9.8,6z"
/>
</svg>
</div>
<div
@click="selectView('Stats')"
class="cursor-pointer mb-4 p-2 rounded-full w-10 h-10"
>
<svg
version="1.1"
id="graph"
xmlns="http://www.w3.org/2000/svg"
:class="{ active: active === 'Stats' }"
xmlns:xlink="http://www.w3.org/1999/xlink"
x="0px"
y="0px"
viewBox="0 0 17 20"
style="enable-background:new 0 0 17 20;"
xml:space="preserve"
>
<rect y="10" fill="#b3b3b3" width="2" height="10" />
<rect x="5" y="2" fill="#b3b3b3" width="2" height="18" />
<rect x="10" y="5" fill="#b3b3b3" width="2" height="15" />
<rect x="15" fill="#b3b3b3" width="2" height="20" />
</svg>
</div>
<div
@click="selectView('About')"
class="cursor-pointer mb-2 p-2 rounded-full w-10 h-10"
>
<svg
version="1.1"
id="info"
:class="{ active: active === 'About' }"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
x="0px"
y="0px"
viewBox="0 0 20 20"
style="enable-background:new 0 0 20 20;"
xml:space="preserve"
>
<g>
<path
fill="#b3b3b3"
d="M10,0C4.5,0,0,4.5,0,10s4.5,10,10,10s10-4.5,10-10S15.5,0,10,0z M10,18c-4.4,0-8-3.6-8-8s3.6-8,8-8s8,3.6,8,8
S14.4,18,10,18z"
/>
<path fill="#b3b3b3" d="M9,9h2v6H9V9z M9,5h2v2H9V5z" />
</g>
</svg>
</div>
</nav>
</template>
<script>
export default {
name: 'Sidebar',
props: {
active: {
type: String,
required: true
}
},
methods: {
selectView(v) {
this.$emit('select-view', v)
}
}
}
</script>
<style scoped>
#cog,
#info {
transition: transform 0.3s cubic-bezier(0.07, 0.95, 0, 1);
}
#cog path,
#graph rect,
#info path {
transition: fill 1s cubic-bezier(0.07, 0.95, 0, 1);
}
#cog.active {
transform: rotate(270deg);
}
#cog.active path,
#info.active path {
fill: #27ffa7;
}
#info.active path {
fill: #d690ff;
}
#graph:hover rect:first-of-type,
#graph.active rect:first-of-type {
fill: #27d1ff;
}
#graph:hover rect:nth-of-type(2),
#graph.active rect:nth-of-type(2) {
fill: #27ffa7;
}
#graph:hover rect:nth-of-type(3),
#graph.active rect:nth-of-type(3) {
fill: #ffe027;
}
#graph:hover rect:nth-of-type(4),
#graph.active rect:nth-of-type(4) {
fill: #ff9b45;
}
div:hover > #cog path {
fill: #27ffa7;
}
div:hover > #info path {
fill: #d690ff;
}
</style>

103
gui_wails02_learn/frontend/src/components/Stats.vue

@ -0,0 +1,103 @@
<template>
<section class="bg-gray-800 overflow-y-auto p-10 w-full">
<header class="flex items-center justify-between w-full">
<h1 class="font-medium text-2xl text-blue">Stats</h1>
<BtnClose @click.native="closeView" color="blue" />
</header>
<div class="mt-6 w-full">
<h2 class="mb-3 text-gray-300 text-xl w-full">Current Session</h2>
<div class="flex flex-wrap w-full">
<div class="w-full sm:w-1/3 xl:w-64">
<p
class="font-bold leading-none text-4xl md:text-5xl text-green tracking-tight"
>
{{ session.savings }}
</p>
<p class="font-medium text-gray-200 tracking-wider uppercase">
Saved
</p>
</div>
<div class="w-full sm:w-1/3 xl:w-64">
<p
class="font-bold leading-none text-4xl md:text-5xl text-blue tracking-tight"
>
{{ session.count }}
</p>
<p class="font-medium text-gray-200 tracking-wider uppercase">
Images Optimized
</p>
</div>
<div class="w-full sm:w-1/3 xl:w-64">
<p
class="font-bold leading-none text-4xl md:text-5xl text-purple tracking-tight"
>
{{ session.time[0] }}
</p>
<p class="font-medium text-gray-200 tracking-wider uppercase">
{{ session.time[1] }}
</p>
</div>
</div>
</div>
<div class="mt-8 w-full">
<h2 class="mb-3 text-gray-300 text-xl w-full">All Time</h2>
<div class="flex flex-wrap w-full">
<div class="w-full sm:w-1/3 xl:w-64">
<p
class="font-bold leading-none text-4xl md:text-5xl text-orange tracking-tight"
>
{{ stats.byteCount }}
</p>
<p class="font-medium text-gray-200 tracking-wider uppercase">
Saved
</p>
</div>
<div class="w-full sm:w-1/3 xl:w-64">
<p
class="font-bold leading-none text-4xl md:text-5xl text-yellow tracking-tight"
>
{{ stats.imageCount }}
</p>
<p class="font-medium text-gray-200 tracking-wider uppercase">
Images Optimized
</p>
</div>
<div class="w-full sm:w-1/3 xl:w-64">
<p
class="font-bold leading-none text-4xl md:text-5xl text-pink tracking-tight"
>
{{ stats.timeCount[0] }}
</p>
<p class="font-medium text-gray-200 tracking-wider uppercase">
{{ stats.timeCount[1] }}
</p>
</div>
</div>
</div>
</section>
</template>
<script>
import BtnClose from './BtnClose'
export default {
name: 'Stats',
components: { BtnClose },
computed: {
session() {
return this.$store.getters.session
},
stats() {
return this.$store.getters.stats
}
},
methods: {
closeView() {
this.$emit('close-view')
}
}
}
</script>

3
gui_wails02_learn/frontend/src/lib/event-bus.js

@ -0,0 +1,3 @@
import Vue from 'vue'
export const EventBus = new Vue()

35
gui_wails02_learn/frontend/src/lib/file.js

@ -0,0 +1,35 @@
/**
* fExt returns the extension of a given file.
* @param {string} filename - The filename.
* @returns {string}
*/
export function fExt(filename) {
return filename.split('.').pop()
}
/**
* fName returns the name of a given file without its extension.
* @param {string} filename - The filename.
* @returns {string}
*/
export function fName(filename) {
filename = filename.replace(/\\/g, '/')
return filename.substring(
filename.lastIndexOf('/') + 1,
filename.lastIndexOf('.')
)
}
/**
* fSize returns a pretty string from a number of bytes.
* For example, 1024 converts to "1 MB"
* @param {number} bytes - File size in bytes.
* @returns {string}
*/
export function fSize(bytes) {
if (bytes === 0) {
return '0.00 B'
}
const e = Math.floor(Math.log(bytes) / Math.log(1024))
return (bytes / Math.pow(1024, e)).toFixed(2) + ' ' + ' KMGTP'.charAt(e) + 'B'
}

23
gui_wails02_learn/frontend/src/lib/time.js

@ -0,0 +1,23 @@
/**
* prettyTime returns a human-friendly time representation from milliseconds.
* @param {number} ms
* @returns {(string)[]}
*/
export function prettyTime(ms) {
const seconds = (ms / 1000).toFixed(1)
const minutes = (ms / (1000 * 60)).toFixed(1)
const hours = (ms / (1000 * 60 * 60)).toFixed(1)
const days = (ms / (1000 * 60 * 60 * 24)).toFixed(1)
if (ms < 1000) {
return [ms, 'Milliseconds']
} else if (seconds < 60) {
return [seconds, 'Seconds']
} else if (minutes < 60) {
return [minutes, 'Minutes']
} else if (hours < 24) {
return [hours, 'Hours']
} else {
return [days, 'Days']
}
}

19
gui_wails02_learn/frontend/src/main.js

@ -0,0 +1,19 @@
import 'core-js/stable'
import 'regenerator-runtime/runtime'
import Vue from 'vue'
import App from './App.vue'
import store from './store'
import * as Wails from '@wailsapp/runtime'
import VTooltip from 'v-tooltip'
Vue.use(VTooltip, { defaultDelay: 600, defaultOffset: 16 })
Vue.config.productionTip = false
Vue.config.devtools = true
Wails.Init(() => {
new Vue({
render: h => h(App),
store
}).$mount('#app')
})

147
gui_wails02_learn/frontend/src/store.js

@ -0,0 +1,147 @@
import Vue from 'vue'
import Vuex from 'vuex'
import { fSize } from './lib/file'
import { prettyTime } from './lib/time'
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
config: {
outDir: '',
target: '',
prefix: '',
suffix: '',
sizes: [],
jpegOpt: { quality: 0 },
pngOpt: { quality: 0 },
webpOpt: { lossless: false, quality: 0 }
},
stats: {
byteCount: 0,
imageCount: 0,
timeCount: 0
},
session: {
count: 0,
savings: 0,
time: 0
}
},
getters: {
config(state) {
return state.config
},
session(state) {
return {
count: state.session.count,
hasSavings: state.session.savings > 0,
savings: fSize(state.session.savings),
time: prettyTime(state.session.time)
}
},
stats(state) {
return {
byteCount: fSize(state.stats.byteCount),
imageCount: state.stats.imageCount,
timeCount: prettyTime(state.stats.timeCount)
}
}
},
actions: {
addSize(context) {
context.commit('addSize')
},
removeSize(context, index) {
context.commit('removeSize', index)
},
setSizeStrategy(context, payload) {
context.commit('setSizeStrategy', payload)
},
getConfig(context) {
window.backend.Config.GetAppConfig()
.then(cfg => {
context.commit('setConfig', cfg)
})
.catch(err => {
console.error(err)
})
},
setConfig(context, c) {
window.backend.Config.SetConfig(JSON.stringify(c))
.then(() => {
context.dispatch('getConfig')
})
.catch(err => {
console.error(err)
})
},
setConfigProp(context, payload) {
context.commit('setConfigProp', payload)
},
setSessionProp(context, payload) {
context.commit('setSessionProp', payload)
},
toggleWebpLossless(context) {
context.commit('toggleWebpLossless')
},
getStats(context) {
window.backend.Stat.GetStats()
.then(s => {
context.commit('setStats', s)
})
.catch(err => {
console.error(err)
})
},
setStats(context, s) {
context.commit('setStats', s)
}
},
mutations: {
addSize(state) {
const s = { height: null, width: null, strategy: 0 }
if (!state.config.sizes) {
state.config.sizes = [s]
} else {
state.config.sizes.push(s)
}
},
removeSize(state, index) {
state.config.sizes.splice(index, 1)
},
setSizeStrategy(state, payload) {
state.config.sizes[payload.index].strategy = payload.value
},
setConfig(state, c) {
state.config = c
},
setConfigProp(state, payload) {
state.config[payload.key] = payload.value
},
setSessionProp(state, payload) {
state.session[payload.key] += payload.value
},
setStats(state, s) {
state.stats = s
},
toggleWebpLossless(state) {
state.config.webpOpt.lossless = !state.config.webpOpt.lossless
}
}
})
export default store

40
gui_wails02_learn/frontend/tailwind.config.js

@ -0,0 +1,40 @@
module.exports = {
purge: ['./src/**/*.vue'],
theme: {
colors: {
blue: {
default: '#27d1ff'
},
gray: {
100: '#f4f5f9',
200: '#b3b3b3',
300: '#808080',
400: '#666666',
700: '#3a3a42',
800: '#212128',
900: '#18181f'
},
green: {
default: '#27ffa7'
},
orange: {
default: '#ff9b45'
},
pink: {
default: '#ff45bd'
},
purple: {
400: '#d690ff',
default: '#ba45ff'
},
red: {
default: '#f84d53'
},
yellow: {
default: '#ffe027'
}
}
},
variants: {},
plugins: []
}

42
gui_wails02_learn/frontend/vue.config.js

@ -0,0 +1,42 @@
let cssConfig = {}
if (process.env.NODE_ENV === 'production') {
cssConfig = {
extract: {
filename: '[name].css',
chunkFilename: '[name].css'
}
}
}
module.exports = {
chainWebpack: config => {
let limit = 9999999999999999
config.module
.rule('images')
.test(/\.(png|gif|jpg)(\?.*)?$/i)
.use('url-loader')
.loader('url-loader')
.tap(options => Object.assign(options, { limit: limit }))
config.module
.rule('fonts')
.test(/\.(woff2?|eot|ttf|otf|svg)(\?.*)?$/i)
.use('url-loader')
.loader('url-loader')
.options({
limit: limit
})
},
css: cssConfig,
configureWebpack: {
output: {
filename: '[name].js'
},
optimization: {
splitChunks: false
}
},
devServer: {
disableHostCheck: true
}
}

21
gui_wails02_learn/go.mod

@ -0,0 +1,21 @@
module optimus
go 1.16
require (
github.com/Masterminds/semver v1.5.0 // indirect
github.com/chai2010/webp v1.1.0
github.com/disintegration/imaging v1.6.2
github.com/fatih/color v1.9.0 // indirect
github.com/foobaz/lossypng v0.0.0-20200814224715-48fa8819852a
github.com/leaanthony/slicer v1.4.1 // indirect
github.com/mattn/go-colorable v0.1.7 // indirect
github.com/muesli/smartcrop v0.3.0
github.com/pkg/errors v0.9.1 // indirect
github.com/vrischmann/userdir v0.0.0-20151206171402-20f291cebd68
github.com/wailsapp/wails v1.16.5
golang.org/x/image v0.0.0-20200927104501-e162460cd6b5 // indirect
golang.org/x/net v0.0.0-20200930145003-4acb6c075d10 // indirect
golang.org/x/sys v0.0.0-20200929083018-4d22bbb62b3c // indirect
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 // indirect
)

111
gui_wails02_learn/go.sum

@ -0,0 +1,111 @@
github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8/go.mod h1:oX5x61PbNXchhh0oikYAH+4Pcfw5LKv21+Jnpr6r6Pc=
github.com/abadojack/whatlanggo v1.0.1 h1:19N6YogDnf71CTHm3Mp2qhYfkRdyvbgwWdd2EPxJRG4=
github.com/abadojack/whatlanggo v1.0.1/go.mod h1:66WiQbSbJBIlOZMsvbKe5m6pzQovxCH9B/K8tQB2uoc=
github.com/chai2010/webp v1.1.0 h1:4Ei0/BRroMF9FaXDG2e4OxwFcuW2vcXd+A6tyqTJUQQ=
github.com/chai2010/webp v1.1.0/go.mod h1:LP12PG5IFmLGHUU26tBiCBKnghxx3toZFwDjOYvd3Ow=
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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/foobaz/lossypng v0.0.0-20200814224715-48fa8819852a h1:0TYY/syyvt/+y5PWAkybgG2o6zHY+UrI3fuixaSeRoI=
github.com/foobaz/lossypng v0.0.0-20200814224715-48fa8819852a/go.mod h1:wRxTcIExb9GZAgOr1wrQuOZBkyoZNQi7znUmeyKTciA=
github.com/go-playground/colors v1.2.0 h1:0EdjTXKrr2g1L/LQTYtIqabeHpZuGZz1U4osS1T8+5M=
github.com/go-playground/colors v1.2.0/go.mod h1:miw1R2JIE19cclPxsXqNdzLZsk4DP4iF+m88bRc7kfM=
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A=
github.com/jackmordaunt/icns v1.0.0 h1:RYSxplerf/l/DUd09AHtITwckkv/mqjVv4DjYdPmAMQ=
github.com/jackmordaunt/icns v1.0.0/go.mod h1:7TTQVEuGzVVfOPPlLNHJIkzA6CoV7aH1Dv9dW351oOo=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/kennygrant/sanitize v1.2.4 h1:gN25/otpP5vAsO2djbMhF/LQX6R7+O1TB4yv8NzpJ3o=
github.com/kennygrant/sanitize v1.2.4/go.mod h1:LGsjYYtgxbetdg5owWB2mpgUL6e2nfw2eObZ0u0qvak=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/leaanthony/slicer v1.4.0/go.mod h1:FwrApmf8gOrpzEWM2J/9Lh79tyq8KTX5AzRtwV7m4AY=
github.com/leaanthony/slicer v1.4.1 h1:X/SmRIDhkUAolP79mSTO0jTcVX1k504PJBqvV6TwP0w=
github.com/leaanthony/slicer v1.4.1/go.mod h1:FwrApmf8gOrpzEWM2J/9Lh79tyq8KTX5AzRtwV7m4AY=
github.com/leaanthony/spinner v0.5.3 h1:IMTvgdQCec5QA4qRy0wil4XsRP+QcG1OwLWVK/LPZ5Y=
github.com/leaanthony/spinner v0.5.3/go.mod h1:oHlrvWicr++CVV7ALWYi+qHk/XNA91D9IJ48IqmpVUo=
github.com/leaanthony/synx v0.1.0 h1:R0lmg2w6VMb8XcotOwAe5DLyzwjLrskNkwU7LLWsyL8=
github.com/leaanthony/synx v0.1.0/go.mod h1:Iz7eybeeG8bdq640iR+CwYb8p+9EOsgMWghkSRyZcqs=
github.com/leaanthony/wincursor v0.1.0 h1:Dsyp68QcF5cCs65AMBmxoYNEm0n8K7mMchG6a8fYxf8=
github.com/leaanthony/wincursor v0.1.0/go.mod h1:7TVwwrzSH/2Y9gLOGH+VhA+bZhoWXBRgbGNTMk+yimE=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.7 h1:bQGKb3vps/j0E9GfJQ03JyhRuxsvdAanXlT9BTw3mdw=
github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/muesli/smartcrop v0.3.0 h1:JTlSkmxWg/oQ1TcLDoypuirdE8Y/jzNirQeLkxpA6Oc=
github.com/muesli/smartcrop v0.3.0/go.mod h1:i2fCI/UorTfgEpPPLWiFBv4pye+YAG78RwcQLUkocpI=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4 h1:49lOXmGaUpV9Fz3gd7TFZY106KVlPVa5jcYD1gaQf98=
github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
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/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sirupsen/logrus v1.4.1 h1:GL2rEmy6nsikmW0r8opw9JIRScdMF5hA8cOYLH7In1k=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/syossan27/tebata v0.0.0-20180602121909-b283fe4bc5ba h1:2DHfQOxcpWdGf5q5IzCUFPNvRX9Icf+09RvQK2VnJq0=
github.com/syossan27/tebata v0.0.0-20180602121909-b283fe4bc5ba/go.mod h1:iLnlXG2Pakcii2CU0cbY07DRCSvpWNa7nFxtevhOChk=
github.com/vrischmann/userdir v0.0.0-20151206171402-20f291cebd68 h1:Ah2/69Z24rwD6OByyOdpJDmttftz0FTF8Q4QZ/SF1E4=
github.com/vrischmann/userdir v0.0.0-20151206171402-20f291cebd68/go.mod h1:EqKqAeKddSL9XSGnfXd/7iLncccKhR16HBKVva7ENw8=
github.com/wailsapp/wails v1.16.5 h1:6kGXCeiTwQsm/vkKqtr/StzH2BRXV/uBZe6afUSuWbg=
github.com/wailsapp/wails v1.16.5/go.mod h1:aADbAvTzZrKGd4Td7d1l4Dp5Hx7lLJEvVH7guIHoDf8=
golang.org/x/crypto v0.0.0-20190123085648-057139ce5d2b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20200927104501-e162460cd6b5 h1:QelT11PB4FXiDEXucrfNckHoFxwt8USGY1ajP1ZF5lM=
golang.org/x/image v0.0.0-20200927104501-e162460cd6b5/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200930145003-4acb6c075d10 h1:YfxMZzv3PjGonQYNUaeU2+DhAdqOxerQ30JFB6WgAXo=
golang.org/x/net v0.0.0-20200930145003-4acb6c075d10/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/sys v0.0.0-20180606202747-9527bec2660b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb/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-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200929083018-4d22bbb62b3c h1:/h0vtH0PyU0xAoZJVcRw1k0Ng+U0JAy3QDiFmppIlIE=
golang.org/x/sys v0.0.0-20200929083018-4d22bbb62b3c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
gopkg.in/AlecAivazis/survey.v1 v1.8.4/go.mod h1:iBNOmqKz/NUbZx3bA+4hAGLRC7fSK7tgtVDT4tB22XA=
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/yaml.v3 v3.0.0-20190709130402-674ba3eaed22/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

37
gui_wails02_learn/main.go

@ -0,0 +1,37 @@
package main
import (
_ "embed"
"optimus/backend/config"
"optimus/backend/image"
"optimus/backend/stat"
"github.com/wailsapp/wails"
)
//go:embed frontend/dist/app.js
var js string
//go:embed frontend/dist/app.css
var css string
func main() {
app := wails.CreateApp(&wails.AppConfig{
Width: 1200,
Height: 742,
Title: "Optimus",
JS: js,
CSS: css,
Colour: "#18181f",
Resizable: true,
})
cfg := config.NewConfig()
st := stat.NewStat()
fm := image.NewFileManager(cfg, st)
app.Bind(cfg)
app.Bind(st)
app.Bind(fm)
_ = app.Run()
}

6
gui_wails02_learn/optimus.AppImage.desktop

@ -0,0 +1,6 @@
[Desktop Entry]
Name=Optimus
Exec=optimus
Icon=appicon--512
Type=Application
Categories=GTK;GNOME;Utility;Development;

10
gui_wails02_learn/optimus.json

@ -0,0 +1,10 @@
{
"bin": "optimus.exe",
"description": "Optimus is an image conversion and optimization application. It supports conversion and compression between WebP, JPEG, and PNG image formats.",
"hash": "9e90cf09e659f7ecf5144d05d00fbb7315739dabd2dea14bb6294c5d26b3e761",
"homepage": "https://github.com/splode/optimus",
"license": "MIT",
"shortcuts": [["optimus.exe", "Optimus"]],
"url": "https://github.com/Splode/optimus/releases/download/v0.5.0-beta/optimus_v0.5.0-beta_windows_x86_64.zip",
"version": "0.5.0-beta"
}

22
gui_wails02_learn/project.json

@ -0,0 +1,22 @@
{
"name": "Optimus",
"description": "Image conversion and compression. Supports WebP, JPEG, and PNG.",
"author": {
"name": "Christopher Murphy",
"email": "flyweight@protonmail.com"
},
"version": "0.5.0-beta",
"binaryname": "optimus",
"frontend": {
"dir": "frontend",
"install": "npm install",
"build": "npm run build",
"bridge": "src",
"serve": "npm run serve"
},
"WailsVersion": "1.7.1",
"CrossCompile": false,
"Platform": "",
"Architecture": "",
"LdFlags": ""
}

4
gui_wails02_learn/wails.json

@ -0,0 +1,4 @@
{
"email": "flyweight@pm.me",
"name": "Christopher Murphy"
}

2
gui_wails_learn/.gitattributes

@ -1,2 +0,0 @@
*.css linguist-vendored
*.scss linguist-vendored

9
gui_wails_learn/.gitignore

@ -1,9 +0,0 @@
mollywallet
mollywallet.exe
nohup.out
mollywallet-res.syso
mollywallet.exe.manifest
mollywallet.rc
build
.DS_Store
/.idea/

67
gui_wails_learn/Makefile

@ -1,67 +0,0 @@
bold := $(shell tput bold)
sgr0 := $(shell tput sgr0)
help:
@printf "\nBelow commands have been made available to ease up Molly Wallet development.\n\n"
@printf "Molly Wallet App: \n"
@printf " $(bold)run_dev_env$(sgr0) - Starts up the dev enviornment on http://localhost:8080\n"
@printf " $(bold)build_app$(sgr0) - Compiles the Molly Wallet to the build folder\n"
@printf " $(bold)buid_app_production$(sgr0) - Builds a production build for the OS it's running on\n"
@printf " $(bold)cross_compile_app$(sgr0) - Compiling for every OS and Platform to the build directory\n\n"
@printf "Update Module: \n"
@printf " $(bold)build_update_module$(sgr0) - Builds the update module to the ~/.dag folder\n"
@printf " $(bold)run_update_module$(sgr0) - Builds and runs the update module\n"
@printf " $(bold)cross_compile_update_module$(sgr0) - Compiling for every OS and Platform to the build directory\n\n"
@printf "General: \n"
@printf " $(bold)all$(sgr0) - Builds and compiles both the wallet and the Update Module\n"
@printf " $(bold)cross_compile_all$(sgr0) - Builds and compiles both the wallet and the Update Module for all Platforms\n\n"
@printf " $(bold)clean$(sgr0) - Cleans up old builds from the build directory\n\n"
run_dev_env:
@echo "Starting up frontend dev env on http://localhost:8080..."
$(shell wails serve) \
$(shell cd frontend && npm run serve)
build_app:
@echo "Building Molly Wallet binary to build folder..."
wails build
buid_app_production:
@echo "Building app for production..."
wails build -f -p
cross_compile_app:
@echo "Compiling for every OS and Platform..."
wails build -x darwin/amd64
wails build -x windows/amd64
wails build -x linux/amd64
build_update_module:
@echo "Building update module binary to ~/.dag"
go build -o ~/.dag/update backend/cmd/update/main.go
run_update_module:
@echo "Compiling and running the update module"
go run backend/cmd/update/main.go
cross_compile_update_module:
@echo "Compiling for every OS and Platform..."
GOOS=linux GOARCH=amd64 go build -o build/update-linux-amd backend/cmd/update/main.go
GOOS=windows GOARCH=amd64 go build -o build/update-windows-amd64 backend/cmd/update/main.go
GOOS=darwin GOARCH=amd64 go build -o build/main-darwin-amd64 backend/cmd/update/main.go
clean:
@echo "Cleaning up build directory..."
@rm -rf build
all: build_app build_update_module
cross_compile_all: cross_compile_app cross_compile_update_module

BIN
gui_wails_learn/appicon.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

BIN
gui_wails_learn/assets/appicon.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

65
gui_wails_learn/backend/api/rpc.go

@ -1,65 +0,0 @@
package api
import (
"fmt"
"net"
"net/http"
"net/rpc"
"os"
)
type RPCEndpoints int
type Signal struct {
PID int
Msg string
}
// InitRPCServer initializes the RPC server that listens to incoming LCM tasks
// by the RPC clients
func InitRPCServer() error {
endpoints := new(RPCEndpoints)
// Publish the receivers methods
err := rpc.Register(endpoints)
if err != nil {
return fmt.Errorf("format of service Task isn't correct. Reason: %v", err)
}
// Register a HTTP handler
rpc.HandleHTTP()
// Listen to TPC connections on port 36866
listener, err := net.Listen("tcp", ":36866")
if err != nil {
return fmt.Errorf("Listen error: %v", err)
}
errs := make(chan error)
// Start accept incoming HTTP connections
go func() {
err = http.Serve(listener, nil)
if err != nil {
errs <- fmt.Errorf("Error serving: %v", err)
return
}
}()
select {
case err := <-errs:
if err != nil {
return err
}
default:
return nil
}
return nil
}
func (rpc *RPCEndpoints) ShutDown(sig Signal, response *Signal) error {
pid := os.Getpid()
*response = Signal{pid, "Shutting down application"}
// time.Sleep(3 * time.Second)
return nil
}

237
gui_wails_learn/backend/app.go

@ -1,237 +0,0 @@
package app
import (
"encoding/json"
"io"
"os"
"os/user"
"github.com/jinzhu/gorm"
"github.com/sirupsen/logrus"
log "github.com/sirupsen/logrus"
"github.com/wailsapp/wails"
"github.com/grvlle/constellation_wallet/backend/api"
"github.com/grvlle/constellation_wallet/backend/models"
)
// WalletApplication holds all application specific objects
// such as the Client/Server event bus and logger
type WalletApplication struct {
Version string
RT *wails.Runtime
log *logrus.Logger
wallet models.Wallet
DB *gorm.DB
killSignal chan struct{}
Network struct {
URL string
Handles struct {
Send string // Takes TX Object, returns TX Hash (200)
Transaction string // Takes TX Object, returns TX Hash (200)
Balance string // Polls the wallets available balance
}
BlockExplorer struct {
URL string
Handles struct {
Transactions string // Takes TX Hash, returns TX info
Checkpoints string // Takes Checkpoint block hash, returns checkpoint block info
Snapshots string // Takes SnapshotHash, returns info
CollectTX string // Takes DAG address, returns tx objects
}
}
}
paths struct {
HomeDir string
DAGDir string
TMPDir string
EncryptedDir string
EmptyTXFile string
PrevTXFile string
LastTXFile string
AddressFile string
ImageDir string
Java string
}
KeyStoreAccess bool
TransactionFinished bool
TransactionFailed bool
UserLoggedIn bool
NewUser bool
WalletImported bool
FirstTX bool
SecondTX bool
WidgetRunning struct {
PassKeysToFrontend bool
DashboardWidgets bool
}
WalletCLI struct {
URL string
Version string
}
}
// WailsShutdown is called when the application is closed
func (a *WalletApplication) WailsShutdown() {
a.wallet = models.Wallet{}
close(a.killSignal) // Kills the Go Routines
a.DB.Close()
}
// WailsInit initializes the Client and Server side bindings
func (a *WalletApplication) WailsInit(runtime *wails.Runtime) error {
var err error
a.log = logrus.New()
err = a.initDirectoryStructure()
if err != nil {
a.log.Errorln("Unable to set up directory structure. Reason: ", err)
}
a.initLogger()
err = api.InitRPCServer()
if err != nil {
a.log.Panicf("Unable to initialize RPC Server. Reason: %v", err)
}
a.log.Infoln("RPC Server initialized.")
a.UserLoggedIn = false
a.NewUser = false
a.TransactionFinished = true
a.RT = runtime
a.killSignal = make(chan struct{}) // Used to kill go routines and hand back system resources
a.wallet.Currency = "USD" // Set default currency
a.WalletCLI.URL = "https://github.com/Constellation-Labs/constellation/releases/download"
a.WalletCLI.Version = "2.6.0"
a.Version = "1.2.0"
a.DB, err = gorm.Open("sqlite3", a.paths.DAGDir+"/store.db")
if err != nil {
a.log.Panicln("failed to connect database", err)
}
// Migrate the schema
a.DB.AutoMigrate(&models.Wallet{}, &models.TXHistory{}, &models.Path{})
a.detectJavaPath()
a.initMainnetConnection()
a.newReleaseAvailable()
return nil
}
// initLogger writes logs to STDOUT and a.paths.DAGDir/wallet.log
func (a *WalletApplication) initLogger() {
logFile, err := os.OpenFile(a.paths.DAGDir+"/wallet.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0664)
if err != nil {
a.log.Fatal("Unable to create log file.")
}
mw := io.MultiWriter(os.Stdout, logFile)
a.log.SetOutput(mw)
a.log.SetFormatter(&log.TextFormatter{
ForceColors: true,
FullTimestamp: true,
})
}
// Initializes the Directory Structure and stores the paths to the WalletApplication struct.
func (a *WalletApplication) initDirectoryStructure() error {
user, err := user.Current()
if err != nil {
return err
}
a.paths.HomeDir = user.HomeDir // Home directory of the user
a.paths.DAGDir = a.paths.HomeDir + "/.dag" // DAG directory for configuration files and wallet specific data
a.paths.TMPDir = a.paths.DAGDir + "/tmp"
a.paths.LastTXFile = a.paths.TMPDir + "/last_tx"
a.paths.PrevTXFile = a.paths.TMPDir + "/prev_tx"
a.paths.EmptyTXFile = a.paths.TMPDir + "/genesis_tx"
a.paths.ImageDir = "./frontend/src/assets/img/" // Image Folder
a.log.Info("DAG Directory: ", a.paths.DAGDir)
err = a.directoryCreator(a.paths.DAGDir, a.paths.TMPDir)
if err != nil {
return err
}
return nil
}
// initMainnetConnection populates the WalletApplication struct with mainnet data
func (a *WalletApplication) initMainnetConnection() {
a.Network.URL = "http://lb.constellationnetwork.io:9000" // Temp
a.Network.Handles.Send = "/send"
a.Network.Handles.Transaction = "/transaction"
a.Network.Handles.Balance = "/address/"
a.Network.BlockExplorer.URL = "https://xju69fets2.execute-api.us-west-1.amazonaws.com/cl-block-explorer-mainnet"
a.Network.BlockExplorer.Handles.Transactions = "/transactions/"
a.Network.BlockExplorer.Handles.Checkpoints = "/checkpoints/"
a.Network.BlockExplorer.Handles.Snapshots = "/snapshots/"
a.Network.BlockExplorer.Handles.CollectTX = "/transactions?sender="
}
// APIError reported by the blockexplerer/loadbalancer are reported in the following format
// {"error": "Cannot find transactions for sender"}
type APIError struct {
Error string
}
// verifyAPIResponse takes API response converted to a byte array and checks if the API returned
// an error. If it did, it'll return the error message.
func (a *WalletApplication) verifyAPIResponse(r []byte) (bool, string) {
APIErr := APIError{}
if string(r[3:8]) == "error" {
err := json.Unmarshal(r, &APIErr)
if err != nil {
a.log.Errorln("Unable to parse API error. Reason: ", err)
}
return false, APIErr.Error
}
return true, ""
}
func (a *WalletApplication) sendSuccess(msg string) {
if len(msg) > 200 {
msg = string(msg[:200]) // Restrict error size for frontend
a.RT.Events.Emit("success", msg)
return
}
a.RT.Events.Emit("success", msg)
}
func (a *WalletApplication) sendWarning(msg string) {
if len(msg) > 200 {
msg = string(msg[:200]) // Restrict error size for frontend
a.RT.Events.Emit("warning", msg)
return
}
a.RT.Events.Emit("warning", msg)
}
func (a *WalletApplication) sendError(msg string, err error) {
var errStr string
if err != nil {
b := []byte(err.Error())
if len(b) > 80 {
errStr = string(b[:80]) // Restrict error size for frontend
} else if len(b) < 80 {
errStr = string(b)
} else {
errStr = ""
}
a.RT.Events.Emit("error_handling", msg, errStr+" ...")
} else {
a.RT.Events.Emit("error_handling", msg+" ...")
}
}

210
gui_wails_learn/backend/assemblywrapper.go

@ -1,210 +0,0 @@
package app
import (
"bytes"
"errors"
"fmt"
"io/ioutil"
"os"
"os/exec"
"runtime"
"strconv"
"time"
)
/* AssemblyWrapper contains the code that is interacting with the wallet assembly provided
by the Constellation Engineering team. The code is interfacing with the wallet assembly using
the CLI */
func (a *WalletApplication) runWalletCMD(tool string, scalaFunc string, scalaArgs ...string) error {
var main string
if runtime.GOOS == "windows" {
main = a.paths.Java
} else {
main = "java"
}
cmds := []string{"-jar", a.paths.DAGDir + "/cl-" + tool + ".jar", scalaFunc}
args := append(cmds, scalaArgs...)
cmd := exec.Command(main, args...)
a.log.Infoln("Running command: ", cmd)
var out bytes.Buffer
var stderr bytes.Buffer
cmd.Stdout = &out // Captures STDOUT
cmd.Stderr = &stderr // Captures STDERR
err := cmd.Run()
if err != nil {
errFormatted := fmt.Sprint(err) + ": " + stderr.String()
return errors.New(errFormatted)
}
fmt.Println(out.String())
a.log.Debugln(cmd)
return nil
}
// WalletKeystoreAccess is true if the user can unlock the .p12 keystore
// and key using storepass and keypass
func (a *WalletApplication) WalletKeystoreAccess() bool {
a.log.Infoln("Checking Keystore Access...")
rescueStdout := os.Stdout
r, w, err := os.Pipe()
if err != nil {
a.log.Errorln("Unable to pipe STDOUT, Reason: ", err)
a.sendError("Unable to pipe STDOUT, Reason: ", err)
}
os.Stdout = w
err = a.runWalletCMD("wallet", "show-address", "--keystore="+a.wallet.KeyStorePath, "--alias="+a.wallet.WalletAlias, "--env_args=true")
if err != nil {
a.log.Warn("KeyStore Access Rejected!")
a.LoginError("Access Denied. Please make sure that you have typed in the correct credentials.")
a.KeyStoreAccess = false
return a.KeyStoreAccess
}
// STDOUT is captured here
w.Close()
dagAddress, err := ioutil.ReadAll(r)
if err != nil {
a.log.Errorln("Unable to read address from STDOUT", err)
a.sendError("Unable to read address from STDOUT", err)
}
// if STDOUT prefix of show-address output isn't DAG
if err == nil && a.wallet.Address != "" && string(dagAddress[:40]) == a.wallet.Address {
a.KeyStoreAccess = true
a.log.Info("KeyStore Access Granted!")
return a.KeyStoreAccess
}
os.Stdout = rescueStdout
a.KeyStoreAccess = false
a.log.Warn("KeyStore Access Rejected!")
a.LoginError("Access Denied. Please make sure that you have typed in the correct credentials.")
return a.KeyStoreAccess
}
// GenerateDAGAddress generates a new wallet address and stores it in memory
// java -cp constellation-assembly-1.0.12.jar org.constellation.util.wallet.GenerateAddress --pub_key_str=<base64 hash of pubkey> --store_path=<path to file where address will be stored>
func (a *WalletApplication) GenerateDAGAddress() string {
a.log.Infoln("Creating DAG Address from Public Key...")
rescueStdout := os.Stdout
r, w, err := os.Pipe()
if err != nil {
a.log.Errorln("Unable to pipe STDOUT, Reason: ", err)
a.sendError("Unable to pipe STDOUT, Reason: ", err)
return ""
}
os.Stdout = w
err = a.runWalletCMD("wallet", "show-address", "--keystore="+a.wallet.KeyStorePath, "--alias="+a.wallet.WalletAlias, "--env_args=true")
if err != nil {
a.sendError("Unable to generate wallet address. Reason:", err)
a.log.Errorf("Unable to generate wallet address. Reason: %s", err.Error())
return ""
}
w.Close()
dagAddress, err := ioutil.ReadAll(r)
if err != nil {
a.log.Errorln("Unable to read address from STDOUT", err)
a.sendError("Unable to read address from STDOUT", err)
}
os.Stdout = rescueStdout
a.wallet.Address = string(dagAddress[:40])
return a.wallet.Address
}
// CheckAndFetchWalletCLI will download the cl-wallet dependencies from
// the official Constellation Repo
func (a *WalletApplication) CheckAndFetchWalletCLI() bool {
keytoolPath := a.paths.DAGDir + "/cl-keytool.jar"
walletPath := a.paths.DAGDir + "/cl-wallet.jar"
keytoolExists := a.fileExists(keytoolPath)
walletExists := a.fileExists(walletPath)
if keytoolExists && walletExists {
return true
}
if keytoolExists {
a.log.Info(keytoolPath + " file exists. Skipping downloading")
} else {
if err := a.fetchWalletJar("cl-keytool.jar", keytoolPath); err != nil {
a.log.Errorln("Unable to fetch or store cl-keytool.jar", err)
return false
}
}
if walletExists {
a.log.Info(walletPath + " file exists. Skipping downloading")
} else {
if err := a.fetchWalletJar("cl-wallet.jar", walletPath); err != nil {
a.log.Errorln("Unable to fetch or store cl-wallet.jar", err)
return false
}
}
if a.fileExists(keytoolPath) && a.fileExists(walletPath) {
return true
} else {
return false
}
}
// produceTXObject will put an actual transaction on the network. This is called from the
// transactions.go file, more specifically the sendTransaction func. This in turn is triggered
// from the frontend (Transactions.vue) and the tx func. note you can either pass a priv key like
// or pass in a path to an encrypted .p12 file
// java -jar cl-wallet.jar create-transaction --keystore testkey.p12 --alias alias --storepass storepass --keypass keypass -d DAG6o9dcxo2QXCuJS8wnrR944YhFBpwc2jsh5j8f -p prev_tx -f new_tx --fee 0 --amount 1
func (a *WalletApplication) produceTXObject(amount int64, fee int64, address, newTX, prevTX string) {
// Convert to string
amountStr := strconv.FormatInt(amount, 10)
// feeStr := strconv.FormatInt(fee, 10)
// amountNorm, err := normalizeAmounts(amount)
// if err != nil {
// a.log.Errorln("Unable to normalize amounts when producing tx object. Reason: ", err)
// a.sendError("Unable to send transaction. Don't worry, your funds are safe. Please report this issue. Reason: ", err)
// return
// }
feeNorm, err := normalizeAmounts(fee)
if err != nil {
a.log.Errorln("Unable to normalize amounts when producing tx object. Reason: ", err)
a.sendError("Unable to send transaction. Don't worry, your funds are safe. Please report this issue. Reason: ", err)
return
}
// newTX is the full command to sign a new transaction
err = a.runWalletCMD("wallet", "create-transaction", "--keystore="+a.wallet.KeyStorePath, "--normalized", "--alias="+a.wallet.WalletAlias, "--amount="+amountStr, "--fee="+feeNorm, "-d="+address, "-f="+newTX, "-p="+prevTX, "--env_args=true")
if err != nil {
a.sendError("Unable to send transaction. Don't worry, your funds are safe. Please report this issue. Reason: ", err)
a.log.Errorln("Unable to send transaction. Reason: ", err)
return
}
time.Sleep(1 * time.Second) // Will sleep for 1 sec between TXs to prevent spamming.
}
// CreateEncryptedKeyStore is called ONLY when a NEW wallet is created. This
// will create a new password protected encrypted keypair stored in user selected location
// java -jar cl-keytool.jar --keystore testkey.p12 --alias alias --storepass storepass --keypass keypass
func (a *WalletApplication) CreateEncryptedKeyStore() error {
err := a.runWalletCMD("keytool", "--keystore="+a.wallet.KeyStorePath, "--alias="+a.wallet.WalletAlias, "--env_args=true")
if err != nil {
a.LoginError("Unable to write encrypted keys to filesystem.")
a.log.Errorf("Unable to write encrypted keys to filesystem. Reason: %s", err.Error())
return err
}
return nil
}

252
gui_wails_learn/backend/buildchain.go

@ -1,252 +0,0 @@
package app
import (
"encoding/json"
"io/ioutil"
"net/http"
"github.com/grvlle/constellation_wallet/backend/models"
)
// formTXChain retains the order of the blockchain across all accounts.
// Also calls the methods to create block objects (write json to file),
// and the method that pushes them to the network(HTTP POST).
// To retain order, formTXChain will poll the last sent TX for it's Failed state.
// if the last TX failed, it'll switch up the order to account for that not to break the chain.
// This means that all failed attempts at creating a block is also stored in the DB with
// a Failed state.
func (a *WalletApplication) formTXChain(amount int64, fee int64, address string, ptxObj *Transaction, ltxObj *Transaction) {
statusLastTX := models.TXHistory{}
if err := a.DB.Last(&statusLastTX).Error; err != nil {
a.log.Warnln("No previous TX detected for this wallet. Reason: ", err)
}
if statusLastTX.Failed {
a.log.Warnln("Previous Transaction has a failed state. Adapting...", statusLastTX.Failed)
}
// Queries the number of previous transactions for this wallet.
numberOfTX := a.DB.Model(&a.wallet).Association("TXHistory").Count()
// First TX does not contain a TXref
if numberOfTX == 0 {
a.log.Infoln("Detected that this is the first TX sent from this key.")
a.produceTXObject(amount, fee, address, a.paths.LastTXFile, a.paths.EmptyTXFile)
a.sendTransaction(a.paths.LastTXFile)
return
}
a.log.Infoln("Number of previous TX's detected: ", numberOfTX)
// Manually control the second TX, to ensure the following order
if numberOfTX == 1 {
// If the first transaction failed, enforce the order.
if statusLastTX.Failed {
a.log.Warnln("Detected that the previous transaction failed.")
a.produceTXObject(amount, fee, address, a.paths.LastTXFile, a.paths.EmptyTXFile)
a.sendTransaction(a.paths.LastTXFile)
return
}
// Check for edge case where PrevTXFile has already been written and needs to be referenced.
// This occurs when a wallet with 1 previous tx has been imported.
prevTXFileContents := a.loadTXFromFile(a.paths.PrevTXFile)
if a.WalletImported && prevTXFileContents != "" {
a.log.Warnln("One previous transaction has been imported. Using that as reference.")
a.produceTXObject(amount, fee, address, a.paths.LastTXFile, a.paths.PrevTXFile)
a.sendTransaction(a.paths.LastTXFile)
return
}
a.produceTXObject(amount, fee, address, a.paths.PrevTXFile, a.paths.LastTXFile)
a.sendTransaction(a.paths.PrevTXFile)
return
}
// Returns the TX object that has the highest ordinal (the highest determines if it's to be referenced or reference the other tx)
newTX := a.determineBlockOrder(ptxObj, ltxObj)
// If the last TX is in failed state, we reset the order.
if newTX == a.paths.PrevTXFile && statusLastTX.Failed {
a.produceTXObject(amount, fee, address, a.paths.LastTXFile, a.paths.PrevTXFile)
a.sendTransaction(a.paths.LastTXFile)
return
}
if newTX != a.paths.PrevTXFile && !statusLastTX.Failed {
a.produceTXObject(amount, fee, address, a.paths.LastTXFile, a.paths.PrevTXFile)
a.sendTransaction(a.paths.LastTXFile)
return
}
a.produceTXObject(amount, fee, address, a.paths.PrevTXFile, a.paths.LastTXFile)
a.sendTransaction(a.paths.PrevTXFile)
}
func (a *WalletApplication) determineBlockOrder(ptxObj, ltxObj *Transaction) string {
// The higher ordinal will always be the TX carrying the TX Ref.
a.log.Info("Last TX Ordinal: ", ltxObj.LastTxRef.Ordinal, " Previous TX Ordinal: ", ptxObj.LastTxRef.Ordinal)
if ltxObj.LastTxRef.Ordinal > ptxObj.LastTxRef.Ordinal {
return a.paths.PrevTXFile
}
return a.paths.LastTXFile
}
// convertToTXObject takes the Path to the prev_tx and last_tx files and returns a
// pointer to two workable objects.
func (a *WalletApplication) convertToTXObject(ptx, ltx string) (*Transaction, *Transaction) {
var ptxObj Transaction
var ltxObj Transaction
rbytes := []byte(ptx)
lbytes := []byte(ltx)
err := json.Unmarshal(rbytes, &ptxObj)
if err != nil {
a.log.Warnln("TX Object: ", string(rbytes), err)
}
err = json.Unmarshal(lbytes, &ltxObj)
if err != nil {
a.log.Warnln("TX Object: ", string(rbytes), err)
}
return &ptxObj, &ltxObj
}
/* Called upon wallet import */
// TXReference is used to parse the previous tx of an imported wallet.
type TXReference struct {
Hash string `json:"hash"`
Amount int64 `json:"amount"`
Receiver string `json:"receiver"`
Sender string `json:"sender"`
Fee int `json:"fee"`
IsDummy bool `json:"isDummy"`
LastTransactionRef struct {
PrevHash string `json:"prevHash"`
Ordinal int `json:"ordinal"`
} `json:"lastTransactionRef"`
SnapshotHash string `json:"snapshotHash"`
CheckpointBlock string `json:"checkpointBlock"`
TransactionOriginal struct {
Edge struct {
ObservationEdge struct {
Parents []struct {
HashReference string `json:"hashReference"`
HashType string `json:"hashType"`
BaseHash string `json:"baseHash"`
} `json:"parents"`
Data struct {
HashReference string `json:"hashReference"`
HashType string `json:"hashType"`
BaseHash string `json:"baseHash"`
} `json:"data"`
} `json:"observationEdge"`
SignedObservationEdge struct {
SignatureBatch struct {
Hash string `json:"hash"`
Signatures []struct {
Signature string `json:"signature"`
ID struct {
Hex string `json:"hex"`
} `json:"id"`
} `json:"signatures"`
} `json:"signatureBatch"`
} `json:"signedObservationEdge"`
Data struct {
Amount int64 `json:"amount"`
LastTxRef struct {
PrevHash string `json:"prevHash"`
Ordinal int `json:"ordinal"`
} `json:"lastTxRef"`
Fee interface{} `json:"fee"`
Salt int64 `json:"salt"`
} `json:"data"`
} `json:"edge"`
LastTxRef struct {
PrevHash string `json:"prevHash"`
Ordinal int `json:"ordinal"`
} `json:"lastTxRef"`
IsDummy bool `json:"isDummy"`
IsTest bool `json:"isTest"`
} `json:"transactionOriginal"`
}
// rebuildTxChainState will query the blockexplorer for a transacion and write it to a.paths.PrevTXFile.
// This will allow an imported wallet to reference the last transaction sent.
func (a *WalletApplication) rebuildTxChainState(lastTXHash string) error {
a.log.Info("Sending API call to block explorer on: " + a.Network.BlockExplorer.Handles.Transactions)
resp, err := http.Get(a.Network.BlockExplorer.URL + a.Network.BlockExplorer.Handles.Transactions + lastTXHash)
if err != nil {
a.log.Errorln("Failed to send HTTP request. Reason: ", err)
if err := a.DB.Model(&a.wallet).Where("wallet_alias = ?", a.wallet.WalletAlias).Delete(&a.wallet).Error; err != nil {
a.log.Errorln("Unable to delete wallet upon failed import. Reason: ", err)
return err
}
a.LoginError("Unable to collect previous TX's from blockexplorer. Please check your internet connection.")
return err
}
defer resp.Body.Close()
if resp.Body != nil {
bodyBytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
a.LoginError("Unable to collect previous TX's from blockexplorer. Please try again later.")
a.log.Errorln("Unable to collect previous TX's from blockexplorer. Reason: ", err)
}
ok, error := a.verifyAPIResponse(bodyBytes)
// Blockexplorer returns below string when no previous transactions are found
if !ok && error != "Cannot find transaction" {
a.log.Errorln("API returned the following error", error)
// If unable to import last transaction, remove wallet from DB and logout.
if err := a.DB.Model(&a.wallet).Where("wallet_alias = ?", a.wallet.WalletAlias).Delete(&a.wallet).Error; err != nil {
a.log.Errorln("Unable to delete wallet upon failed import. Reason: ", err)
return err
}
a.log.Panicln("Unable to import previous transactions") // TODO: logout user from wallet
a.LoginError("The wallet import failed. Please check your internet connection and try again.")
return err
}
// Parsing JSON object to TXReference ->
lastTX := TXReference{}
err = json.Unmarshal(bodyBytes, &lastTX)
if err != nil {
a.log.Errorln("Unable to fetch TX history from block explorer. Reason: ", err)
a.sendError("Unable to fetch TX history from block explorer. Reason: ", err)
return err
}
// Marshal so that we can unmarshat into tx object ->
b, err := json.Marshal(lastTX.TransactionOriginal)
if err != nil {
a.log.Errorln("Unable to parse last transaction hash. Reason: ", err)
a.sendError("Unable to fetch TX history from block explorer. Reason: ", err)
return err
}
// Populating tx object ->
tx := Transaction{}
err = json.Unmarshal(b, &tx)
if err != nil {
a.log.Errorln("Unable to parse last transaction hash. Reason: ", err)
a.sendError("Unable to fetch TX history from block explorer. Reason: ", err)
return err
}
// Converting to json
txBytes, err := json.Marshal(tx)
if err != nil {
a.log.Errorln("Unable to parse last transaction hash. Reason: ", err)
a.sendError("Unable to fetch TX history from block explorer. Reason: ", err)
return err
}
err = WriteToFile(a.paths.PrevTXFile, txBytes)
if err != nil {
return err
}
}
return nil
}

441
gui_wails_learn/backend/cmd/update/main.go

@ -1,441 +0,0 @@
package main
import (
"crypto/sha256"
"encoding/hex"
"flag"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/rpc"
"os"
"os/exec"
"path"
"runtime"
"strings"
"time"
"github.com/artdarek/go-unzip"
log "github.com/sirupsen/logrus"
)
func init() {
// initialize update.log file and set log output to file
file, err := os.OpenFile(path.Join(getDefaultDagFolderPath(), "update.log"), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err == nil {
log.SetOutput(file)
} else {
log.Info("Failed to log to file, using default stderr")
}
// Only log the warning severity or above.
log.SetLevel(log.InfoLevel)
}
// Update type contains the update processes mandatory data
type Update struct {
clientRPC *rpc.Client
downloadURL string
dagFolderPath *string
oldMollyBinaryPath *string
newVersion *string
triggerUpdate *bool
}
// Signal is used for IPC with MollyWallet
type Signal struct {
PID int
Msg string
}
type unzippedContents struct {
newMollyBinaryPath string
updateBinaryPath string
}
func main() {
var update Update
update.downloadURL = "https://github.com/grvlle/constellation_wallet/releases/download"
// MollyWallet provides the below data when an update is triggered
update.dagFolderPath = flag.String("init_dag_path", getDefaultDagFolderPath(), "Enter the directory path to dag folder")
update.oldMollyBinaryPath = flag.String("init_molly_path", "", "Enter the directory path where the molly binary is located")
update.newVersion = flag.String("new_version", "", "Enter the new semantic version. E.g 1.2.3")
update.triggerUpdate = flag.Bool("upgrade", false, "Upgrade molly wallet binary")
flag.Parse()
update.Run()
}
// Run is the main method that runs the full update.
func (u *Update) Run() {
var err error
// Clean up old update artifacts
err = u.CleanUp()
if err != nil {
log.Fatalf("Unable to clear previous local state: %v", err)
}
// Create a TCP connection to localhost on port 36866
u.clientRPC, err = rpc.DialHTTP("tcp", "localhost:36866")
if err != nil {
log.Fatal("Connection error: ", err)
}
log.Infof("Successfully established RPC connection with Molly Wallet")
defer u.clientRPC.Close()
zippedArchive, err := u.DownloadAppBinary()
if err != nil {
log.Fatalf("Unable to download v%s of Molly Wallet: %v", *u.newVersion, err)
}
ok, err := u.VerifyChecksum(zippedArchive)
if err != nil || !ok {
log.Fatalf("Checksum missmatch. Corrupted download: %v", err)
}
contents, err := unzipArchive(zippedArchive, *u.dagFolderPath)
if err != nil {
log.Fatalf("Unable to unzip contents: %v", err)
}
err = u.BackupApp()
if err != nil {
log.Fatalf("Unable to Backup Molly Wallet: %v", err)
}
err = u.TerminateAppService()
if err != nil {
log.Errorf("Unable to terminate Molly Wallet: %v", err)
err = u.RestoreBackup()
if err != nil {
log.Fatal("Unable to restore backup: %v", err)
}
}
err = u.ReplaceAppBinary(contents)
if err != nil {
log.Errorf("Unable to overwrite old installation: %v", err)
err = u.RestoreBackup()
if err != nil {
log.Fatalf("Unable to restore backup: %v", err)
}
}
err = u.LaunchAppBinary()
if err != nil {
log.Errorf("Unable to start up Molly after update: %v", err)
err = u.RestoreBackup()
if err != nil {
log.Fatalf("Unable to restore backup: %v", err)
}
}
err = u.CleanUp()
if err != nil {
log.Fatalf("Unable to clear previous local state: %v", err)
}
}
// DownloadAppBinary downloads the latest Molly Wallet zip from github releases and returns the path to it
func (u *Update) DownloadAppBinary() (string, error) {
filename := "mollywallet.zip"
osBuild, _ := getUserOS() // returns linux, windows, darwin or unsupported as well as the file extension (e.g ".exe")
if osBuild == "unsupported" {
return "", fmt.Errorf("the OS is not supported")
}
url := u.downloadURL + "/v" + *u.newVersion + "-" + osBuild + "/" + filename
// e.g https://github.com/grvlle/constellation_wallet/releases/download/v1.1.9-linux/mollywallet.zip
log.Infof("Constructed the following URL: %s", url)
filePath := path.Join(*u.dagFolderPath, filename)
err := downloadFile(url, filePath)
if err != nil {
return "", fmt.Errorf("unable to download remote checksum: %v", err)
}
return filePath, nil
}
// VerifyChecksum takes a file path and will check the file sha256 checksum against the checksum included
// in the downlaod returns false if there's a missmatch
func (u *Update) VerifyChecksum(filePathZip string) (bool, error) {
// Download checksum
filename := "checksum.sha256"
osBuild, _ := getUserOS() // returns linux, windows, darwin or unsupported as well as the file extension (e.g ".exe")
if osBuild == "unsupported" {
return false, fmt.Errorf("the OS is not supported")
}
url := u.downloadURL + "/v" + *u.newVersion + "-" + osBuild + "/" + filename
// e.g https://github.com/grvlle/constellation_wallet/releases/download/v1.1.9-linux/checksum.sha256
log.Infof("Constructed the following URL: %s", url)
filePath := path.Join(*u.dagFolderPath, filename)
err := downloadFile(url, filePath)
if err != nil {
return false, fmt.Errorf("unable to download remote checksum: %v", err)
}
// Read the contents of the downloaded file (remoteChecksum)
content, err := ioutil.ReadFile(filePath)
if err != nil {
return false, err
}
lines := strings.Split(string(content), "\n")
remoteChecksum := lines[0]
log.Infof("Remote file checksum: %v", remoteChecksum)
// Collect the checksum of the downloaded zip (localChecksum)
f, err := os.Open(filePathZip)
if err != nil {
return false, err
}
defer f.Close()
hasher := sha256.New()
if _, err := io.Copy(hasher, f); err != nil {
return false, err
}
localChecksum := hex.EncodeToString(hasher.Sum(nil))
log.Infof("Local file checksum: %v", localChecksum)
return remoteChecksum == localChecksum, nil
}
// TerminateAppService will send an RPC to mollywallet to terminate the application
func (u *Update) TerminateAppService() error {
sig := Signal{0, "Terminate Molly Wallet. Begining Update..."}
var response Signal
err := u.clientRPC.Call("RPCEndpoints.ShutDown", sig, &response)
if err != nil {
return err
}
proc, err := os.FindProcess(response.PID)
if err != nil {
log.Panicf("%v", err)
}
err = proc.Kill()
if err != nil {
return err
}
return nil
}
// BackupApp backs up the old binary in case of a failed update.
func (u *Update) BackupApp() error {
_, fileExt := getUserOS()
// Create backup folder in ~/.dag
err := os.Mkdir(*u.dagFolderPath+"/backup", 0755)
if err != nil {
return fmt.Errorf("unable to create backup folder: %v", err)
}
// Copy the old Molly Wallet binary into ~/.dag/backup/
err = copy(*u.oldMollyBinaryPath, *u.dagFolderPath+"/backup/mollywallet"+fileExt)
if err != nil {
return fmt.Errorf("unable to backup old Molly installation: %v", err)
}
return nil
}
// ReplaceAppBinary replaces the old update module and molly binary with the newly downloaded ones.
func (u *Update) ReplaceAppBinary(contents *unzippedContents) error {
// Replace old molly binary with the new one
_, fileExt := getUserOS()
err := copy(contents.newMollyBinaryPath, *u.oldMollyBinaryPath)
if err != nil {
for i := 5; i > 0; i-- {
time.Sleep(time.Duration(i) * time.Second)
err = copy(contents.newMollyBinaryPath, *u.oldMollyBinaryPath)
if err == nil {
break
} else if err != nil && i == 0 {
return fmt.Errorf("unable to overwrite old molly binary: %v", err)
}
}
}
// Replace old update binary with the new one
if fileExists(contents.updateBinaryPath) {
err = copy(contents.updateBinaryPath, *u.dagFolderPath+"/update"+fileExt)
if err != nil {
return fmt.Errorf("unable to copy update binary to .dag folder: %v", err)
}
}
return nil
}
// LaunchAppBinary executes the new molly binary
func (u *Update) LaunchAppBinary() error {
cmd := exec.Command(*u.oldMollyBinaryPath)
err := cmd.Start()
if err != nil {
return fmt.Errorf("unable to execute run command for Molly Wallet: %v", err)
}
return nil
}
// RestoreBackup restores the backed up files
func (u *Update) RestoreBackup() error {
log.Infoln("Restoring Backup...")
// Copy the old Molly Wallet binary from ~/.dag/backup/ to the old path
_, fileExt := getUserOS()
err := copy(*u.dagFolderPath+"/backup/mollywallet"+fileExt, *u.oldMollyBinaryPath)
if err != nil {
return fmt.Errorf("unable to overwrite old molly binary: %v", err)
}
// Copy update binary from ~/.dag/backup/update -> ~/.dag/update
if fileExists(*u.dagFolderPath + "/backup/update" + fileExt) {
err = copy(*u.dagFolderPath+"/backup/update"+fileExt, *u.dagFolderPath+"/update"+fileExt)
if err != nil {
return fmt.Errorf("unable to copy update binary to .dag folder: %v", err)
}
}
log.Infoln("Backup successfully restored.")
return nil
}
// CleanUp removes uneccesary artifacts from the update process
func (u *Update) CleanUp() error {
if fileExists(*u.dagFolderPath + "/mollywallet.zip") {
err := os.Remove(*u.dagFolderPath + "/mollywallet.zip")
if err != nil {
return err
}
}
if fileExists(*u.dagFolderPath + "/backup") {
err := os.RemoveAll(*u.dagFolderPath + "/backup")
if err != nil {
return err
}
}
if fileExists(*u.dagFolderPath + "/new_build") {
err := os.RemoveAll(*u.dagFolderPath + "/new_build")
if err != nil {
return err
}
}
return nil
}
func downloadFile(url, filePath string) error {
tmpFilePath := filePath + ".tmp"
out, err := os.Create(tmpFilePath)
if err != nil {
return err
}
resp, err := http.Get(url)
if err != nil {
return err
}
defer resp.Body.Close()
if _, err = io.Copy(out, resp.Body); err != nil {
return err
}
out.Close() // Close file to rename
if err = os.Rename(tmpFilePath, filePath); err != nil {
return err
}
return nil
}
func getDefaultDagFolderPath() string {
userDir, err := os.UserHomeDir()
if err != nil {
log.Errorf("Unable to detect UserHomeDir: %v", err)
return ""
}
return userDir + "/.dag"
}
func fileExists(path string) bool {
_, err := os.Stat(path)
if os.IsNotExist(err) {
return false
}
return !os.IsNotExist(err)
}
func copy(src string, dst string) error {
// Read all content of src to data
data, err := ioutil.ReadFile(src)
if err != nil {
return err
}
// Write data to dst
err = ioutil.WriteFile(dst, data, 0755)
if err != nil {
return err
}
return nil
}
// Unzips archive to dstPath, returns path to wallet binary
func unzipArchive(zippedArchive, dstPath string) (*unzippedContents, error) {
uz := unzip.New(zippedArchive, dstPath+"/"+"new_build/")
err := uz.Extract()
if err != nil {
return nil, err
}
_, fileExt := getUserOS()
contents := &unzippedContents{
newMollyBinaryPath: dstPath + "/" + "new_build/mollywallet" + fileExt,
updateBinaryPath: dstPath + "/" + "new_build/update" + fileExt,
}
return contents, err
}
// getUserOS returns the users OS as well as the file extension of executables for said OS
func getUserOS() (string, string) {
var osBuild string
var fileExt string
switch os := runtime.GOOS; os {
case "darwin":
osBuild = "darwin"
fileExt = ""
case "linux":
osBuild = "linux"
fileExt = ""
case "windows":
osBuild = "windows"
fileExt = ".exe"
default:
osBuild = "unsupported"
fileExt = ""
}
return osBuild, fileExt
}

393
gui_wails_learn/backend/dashboard.go

@ -1,393 +0,0 @@
package app
import (
"encoding/json"
"fmt"
"io/ioutil"
"math/rand"
"net/http"
"strconv"
"time"
"github.com/wailsapp/wails"
)
const (
dummyValue = 1000
updateIntervalToken = 30 // Seconds
updateIntervalCurrency = 50 // Seconds
updateIntervalBlocks = 5 // Seconds
updateIntervalPieChart = 60 // Seconds
)
// ChartData contains all the datapoints for the Charts
// on the Dashboard.
type ChartData struct {
NodesOnline struct {
Labels []string `json:"labels"`
Series []int `json:"series"`
} `json:"nodes_online"`
Transactions struct {
Labels []string `json:"labels"`
SeriesOne []int `json:"series_one"`
SeriesTwo []int `json:"series_two"`
} `json:"transactions"`
Throughput struct {
Labels []string `json:"labels"`
SeriesOne []int `json:"series_one"`
SeriesTwo []int `json:"series_two"`
} `json:"throughput"`
}
// ChartDataInit initializes the ChartData struct with datapoints for
// the charts in the wallet. These are stored on the fs as chart_data.json
func (a *WalletApplication) ChartDataInit() *ChartData {
cd := &ChartData{}
cd.NodesOnline.Labels = []string{"30%", "20%", "50%"}
cd.NodesOnline.Series = []int{30, 20, 50}
cd.Transactions.Labels = []string{
"Jan ",
"Feb ",
"Mar ",
"Apr ",
"Mai ",
"Jun ",
"Jul ",
"Aug ",
"Sep ",
"Oct ",
"Nov ",
"Dec "}
cd.Transactions.SeriesOne = []int{542, 543, 520, 680, 653, 753, 326, 434, 568, 610, 756, 895}
cd.Transactions.SeriesTwo = []int{230, 293, 380, 480, 503, 553, 600, 664, 698, 710, 736, 795}
cd.Throughput.Labels = []string{
"9AM",
"12AM",
"3PM",
"6PM",
"9PM",
"12PM",
"3AM",
"6AM"}
cd.Throughput.SeriesOne = []int{287, 385, 490, 562, 594, 626, 698, 895, 952}
cd.Throughput.SeriesTwo = []int{67, 152, 193, 240, 387, 435, 535, 642, 744}
// Init chart widgets with data.
go func() {
select {
case <-a.killSignal:
return
default:
for i := 0; i < 2; i++ {
a.RT.Events.Emit("tx_stats", cd.Transactions.SeriesOne, cd.Transactions.SeriesTwo, cd.Transactions.Labels)
a.RT.Events.Emit("node_stats", cd.NodesOnline.Series, cd.NodesOnline.Labels)
a.RT.Events.Emit("network_stats", cd.Throughput.SeriesOne, cd.Throughput.SeriesTwo, cd.Throughput.Labels)
time.Sleep(1 * time.Second)
}
}
}()
return cd
}
// Populates the Nodes Online pie chart with data from the block explorer.
func (a *WalletApplication) nodeStats(cd *ChartData) {
go func() {
for {
select {
case <-a.killSignal:
return
default:
// Will populate the chart with random data
for i := range cd.NodesOnline.Series {
cd.NodesOnline.Series[i] = rand.Intn(dummyValue)
}
a.RT.Events.Emit("node_stats", cd.NodesOnline.Series, cd.NodesOnline.Labels)
UpdateCounter(updateIntervalPieChart, "chart_counter", time.Second, a.RT)
time.Sleep(updateIntervalPieChart * time.Second)
}
}
}()
}
func (a *WalletApplication) txStats(cd *ChartData) {
go func() {
for {
select {
case <-a.killSignal:
return
default:
// Will populate the chart with random data
for i := range cd.Transactions.SeriesOne {
cd.Transactions.SeriesOne[i] = rand.Intn(dummyValue)
}
for i := range cd.Transactions.SeriesTwo {
cd.Transactions.SeriesTwo[i] = rand.Intn(dummyValue)
}
a.RT.Events.Emit("tx_stats", cd.Transactions.SeriesOne, cd.Transactions.SeriesTwo, cd.Transactions.Labels)
//UpdateCounter(updateIntervalPieChart, "chart_counter", time.Second, a.RT)
time.Sleep(updateIntervalPieChart * time.Second)
}
}
}()
}
func (a *WalletApplication) networkStats(cd *ChartData) {
go func() {
for {
select {
case <-a.killSignal:
return
default:
// Will populate the chart with random data
for i := range cd.Throughput.SeriesOne {
cd.Throughput.SeriesOne[i] = rand.Intn(dummyValue)
}
for i := range cd.Throughput.SeriesTwo {
cd.Throughput.SeriesTwo[i] = rand.Intn(dummyValue)
}
a.RT.Events.Emit("network_stats", cd.Throughput.SeriesOne, cd.Throughput.SeriesTwo, cd.Throughput.Labels)
//UpdateCounter(updateIntervalPieChart, "chart_counter", time.Second, a.RT)
time.Sleep(updateIntervalPieChart * time.Second)
}
}
}()
}
// BlockAmount is a temporary function
func (a *WalletApplication) blockAmount() {
var randomNumber int
go func() {
for {
select {
case <-a.killSignal:
return
default:
randomNumber = rand.Intn(dummyValue)
a.RT.Events.Emit("blocks", randomNumber)
UpdateCounter(updateIntervalBlocks, "block_counter", time.Second, a.RT)
time.Sleep(updateIntervalBlocks * time.Second)
}
}
}()
}
func (a *WalletApplication) pollTokenBalance() {
go func() {
retryCounter := 1
for {
select {
case <-a.killSignal:
return
default:
time.Sleep(time.Duration(retryCounter) * time.Second) // Incremental backoff
for retryCounter <= 20 && a.wallet.Address != "" {
balance, err := a.GetTokenBalance()
if err != nil {
retryCounter++
break
}
a.log.Infoln("Current Balance: ", balance)
a.wallet.Balance, a.wallet.AvailableBalance, a.wallet.TotalBalance = balance, balance, balance
a.RT.Events.Emit("token", a.wallet.Balance, a.wallet.AvailableBalance, a.wallet.TotalBalance)
UpdateCounter(updateIntervalToken, "token_counter", time.Second, a.RT)
time.Sleep(updateIntervalToken * time.Second)
}
}
}
}()
}
// pricePoller polls the min-api.cryptocompare REST API for DAG token value.
// Once polled, it'll Emit the token value to Dashboard.vue for full token
// balance evaluation against USD.
func (a *WalletApplication) pricePoller() {
const (
apiKey string = "17b10afdddc411087e2140ec91bd73d88d0c20294541838b192255fc574b1cb7"
ticker string = "DAG"
url string = "https://min-api.cryptocompare.com/data/pricemulti?fsyms=" + ticker + "&tsyms=BTC,USD,EUR&api_key=" + apiKey
)
go func() {
retryCounter := 1
time.Sleep(3 * time.Second) // Give some space to pollTokenBalance
for {
select {
case <-a.killSignal:
return
default:
a.wallet.TokenPrice.DAG.USD = 0
a.wallet.TokenPrice.DAG.EUR = 0
a.wallet.TokenPrice.DAG.BTC = 0
time.Sleep(time.Duration(retryCounter) * time.Second) // Incremental backoff
for retryCounter <= 20 && a.wallet.Balance != 0 {
a.log.Infoln("Contacting token evaluation API on: " + url + ticker)
resp, err := http.Get(url)
if err != nil {
a.log.Warnln("Unable to poll token evaluation. Reason: ", err) // Log this
retryCounter++
break
}
if resp == nil {
retryCounter++
a.log.Errorln("Killing pollTokenBalance after 10 failed attempts to get balance from mainnet, Reason: ", err)
a.sendWarning("Unable to showcase token USD evaluation. Please check your internet connectivity and restart the wallet application.")
break
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
retryCounter++
a.sendError("Unable to read HTTP resonse from Token API. Reason: ", err)
a.log.Warnln("Unable to read HTTP resonse from Token API. Reason: ", err)
break
}
err = json.Unmarshal([]byte(body), &a.wallet.TokenPrice)
if err != nil {
retryCounter++
a.sendError("Unable to display token price. Reason: ", err)
a.log.Warnln("Unable to display token price. Reason:", err)
break
}
if a.wallet.Balance != 0 && a.wallet.TokenPrice.DAG.USD == 0 {
a.log.Infoln("Contacting alternate token evaluation API")
a.wallet.TokenPrice.DAG.USD, a.wallet.TokenPrice.DAG.BTC, err = getTokenPriceAlternateRoute()
if err != nil {
a.log.Errorln("Failed to fetch token metrics using alternate endpoint. Reason: ", err)
if retryCounter == 10 || retryCounter == 15 || retryCounter == 20 {
warn := fmt.Sprintf("No data recieved from Token Price API. Will try again in %v seconds.", retryCounter)
a.sendWarning(warn)
}
retryCounter++
break
}
}
a.log.Infof("Collected token price in USD: %v", a.wallet.TokenPrice.DAG.USD)
a.log.Infof("Collected token price in EUR: %v", a.wallet.TokenPrice.DAG.EUR)
a.log.Infof("Collected token price in BTC: %v", a.wallet.TokenPrice.DAG.BTC)
totalCurrencyBalance := 0.0
if a.wallet.Currency == "USD" {
totalCurrencyBalance = float64(a.wallet.Balance) * a.wallet.TokenPrice.DAG.USD
} else if a.wallet.Currency == "EUR" {
totalCurrencyBalance = float64(a.wallet.Balance) * a.wallet.TokenPrice.DAG.EUR
} else if a.wallet.Currency == "BTC" {
totalCurrencyBalance = float64(a.wallet.Balance) * a.wallet.TokenPrice.DAG.BTC
}
a.RT.Events.Emit("totalValue", a.wallet.Currency, totalCurrencyBalance)
UpdateCounter(updateIntervalCurrency, "value_counter", time.Second, a.RT)
time.Sleep(updateIntervalCurrency * time.Second)
}
}
}
}()
}
// StoreTermsOfServiceStateDB stores the Terms of Service state in the user DB
func (a *WalletApplication) StoreTermsOfServiceStateDB(termsOfService bool) bool {
if err := a.DB.Model(&a.wallet).Where("wallet_alias = ?", a.wallet.WalletAlias).Update("TermsOfService", termsOfService).Error; err != nil {
a.log.Errorln("Unable to store termsOfService state. Reason: ", err)
a.sendError("Unable to store termsOfService state persistently. Reason: ", err)
return false
}
return true
}
type tokenPriceAlt struct {
Code string `json:"code"`
Data struct {
Sequence string `json:"sequence"`
BestAsk string `json:"bestAsk"`
Size string `json:"size"`
Price string `json:"price"`
BestBidSize string `json:"bestBidSize"`
Time int64 `json:"time"`
BestBid string `json:"bestBid"`
BestAskSize string `json:"bestAskSize"`
} `json:"data"`
}
// getTokenPriceAlternateRoute will kick in in case the main token poller API will return
// broken payload.
func getTokenPriceAlternateRoute() (float64, float64, error) {
var tokenpriceUSD, tokenpriceBTC float64
var err error
tpa := new(tokenPriceAlt)
const (
url = "https://api.kucoin.com/api/v1/market/orderbook/level1?symbol="
usdTicker = "DAG-USDT"
btcTicker = "DAG-BTC"
)
tickers := []string{usdTicker, btcTicker}
for _, tick := range tickers {
resp, err := http.Get(url + tick)
if err != nil {
return 0, 0, err
}
// Example resp: {"code":"200000","data":
// {"sequence":"1583079038860","bestAsk":"0.01058",
// "size":"73908.9903","price":"0.01058",
// "bestBidSize":"403.157","time":1589605888009,
// "bestBid":"0.010539","bestAskSize":"79091.0097"}}
if resp == nil {
return 0, 0, err
}
defer resp.Body.Close()
bodyBytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
return 0, 0, err
}
err = json.Unmarshal(bodyBytes, &tpa)
if err != nil {
return 0, 0, err
}
s := tpa.Data.Price
balance, err := strconv.ParseFloat(s, 64)
if err != nil {
return 0, 0, err
}
switch {
case tick == usdTicker:
tokenpriceUSD = balance
case tick == btcTicker:
tokenpriceBTC = balance
}
}
return tokenpriceUSD, tokenpriceBTC, err
}
// UpdateCounter will count up from the last time a card was updated.
func UpdateCounter(countFrom int, counter string, unit time.Duration, runtime *wails.Runtime) {
go func() {
for i := countFrom; i > 0; i-- {
runtime.Events.Emit(counter, i)
time.Sleep(unit)
continue
}
}()
}

123
gui_wails_learn/backend/lcm.go

@ -1,123 +0,0 @@
package app
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"os"
"os/exec"
"runtime"
)
type UpdateWallet struct {
currentVersion string
newVersion string
mollyBinaryPath string
dagFolderPath string
app *WalletApplication
}
func (u *UpdateWallet) Run() {
var err error
u.currentVersion = u.app.Version
u.newVersion = u.GetLatestRelease()
u.mollyBinaryPath, err = os.Executable()
if err != nil {
u.app.log.Errorln("Unable to collect the path of the molly wallet binary. Reason: ", err)
}
u.dagFolderPath = u.app.paths.DAGDir
err = u.TriggerUpdate()
if err != nil {
u.app.log.Errorln("Unable to Update Molly Wallet. Reason: ", err)
u.app.sendError("Unable to Update Molly Wallet. Reason: ", err)
}
}
func (u *UpdateWallet) TriggerUpdate() error {
_, fileExt := getUserOS()
main := u.dagFolderPath + "/update" + fileExt
args := []string{"-init_dag_path=" + u.dagFolderPath, "-init_molly_path=" + u.mollyBinaryPath, "-new_version=" + u.newVersion, "-upgrade=" + "true"}
cmd := exec.Command(main, args...)
u.app.log.Infoln("Running command: ", cmd)
var stderr bytes.Buffer
cmd.Stderr = &stderr // Captures STDERR
err := cmd.Run()
if err != nil {
errFormatted := fmt.Sprint(err) + ": " + stderr.String()
return errors.New(errFormatted)
}
return nil
}
func (u *UpdateWallet) GetLatestRelease() string {
const (
url = "https://api.github.com/repos/grvlle/constellation_wallet/releases/latest"
)
resp, err := http.Get(url)
if err != nil {
u.app.log.Errorln("Failed to send HTTP request. Reason: ", err)
return ""
}
if resp == nil {
u.app.log.Errorln("Killing pollTokenBalance after 10 failed attempts to get balance from mainnet, Reason: ", err)
u.app.sendWarning("Unable to showcase current balance. Please check your internet connectivity and restart the wallet application.")
return ""
}
defer resp.Body.Close()
bodyBytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
u.app.log.Warn("Unable to update token balance. Reason: ", err)
return ""
}
var result map[string]interface{}
// Unmarshal or Decode the JSON to the interface.
err = json.Unmarshal(bodyBytes, &result)
if err != nil {
return ""
}
release := result["tag_name"]
bytes := []byte(release.(string))
version := string(bytes[1:6])
return version
}
// getUserOS returns the users OS as well as the file extension of executables for said OS
func getUserOS() (string, string) {
var osBuild string
var fileExt string
switch os := runtime.GOOS; os {
case "darwin":
osBuild = "darwin"
fileExt = ""
case "linux":
osBuild = "linux"
fileExt = ""
case "windows":
osBuild = "windows"
fileExt = ".exe"
default:
osBuild = "unsupported"
fileExt = ""
}
return osBuild, fileExt
}

167
gui_wails_learn/backend/login.go

@ -1,167 +0,0 @@
package app
import (
"os"
"runtime"
"strings"
"github.com/grvlle/constellation_wallet/backend/models"
"golang.org/x/crypto/bcrypt"
)
// LoginError takes a string and pushes it to the login screen as an errror
func (a *WalletApplication) LoginError(errMsg string) {
if errMsg != "" {
a.RT.Events.Emit("login_error", errMsg, true)
}
}
// Login is called from the FE when a user logs in with a wallet object
// already in the DB
func (a *WalletApplication) Login(keystorePath, keystorePassword, keyPassword, alias string) bool {
alias = strings.ToLower(alias)
a.wallet = models.Wallet{
KeyStorePath: keystorePath,
WalletAlias: alias}
if runtime.GOOS == "windows" && !a.javaInstalled() {
a.LoginError("Unable to detect your Java path. Please make sure that Java has been installed.")
return false
}
if !a.TransactionFinished {
a.log.Warn("Cannot login to another wallet during a pending transaction.")
a.LoginError("Cannot login to another wallet during a pending transaction.")
return false
}
if keystorePath == "" {
a.LoginError("Please provide a path to the KeyStore file.")
return false
}
if !a.passwordsProvided(keystorePassword, keyPassword, alias) {
a.log.Warnln("One or more passwords were not provided.")
return false
}
os.Setenv("CL_STOREPASS", keystorePassword)
os.Setenv("CL_KEYPASS", keyPassword)
if err := a.DB.First(&a.wallet, "wallet_alias = ?", alias).Error; err != nil {
a.log.Errorln("Unable to query database object for existing wallet. Reason: ", err)
return a.ImportWallet(keystorePath, keystorePassword, keyPassword, alias)
}
if !a.WalletKeystoreAccess() {
a.LoginError("Access Denied. Please make sure that you have typed in the correct credentials.")
return false
}
if !a.NewUser {
a.DB.Model(&a.wallet).Update("KeystorePath", keystorePath)
a.log.Infoln("PrivateKey path: ", keystorePath)
}
// Check password strings against salted hashes stored in DB. Also make sure KeyStore has been accessed.
if a.CheckAccess(keystorePassword, a.wallet.KeystorePasswordHash) && a.CheckAccess(keyPassword, a.wallet.KeyPasswordHash) && a.KeyStoreAccess {
a.UserLoggedIn = true
// os.Setenv("CL_STOREPASS", keystorePassword)
// os.Setenv("CL_KEYPASS", keyPassword)
} else {
a.UserLoggedIn = false
a.LoginError("Access Denied. Please make sure that you have typed in the correct credentials.")
}
if a.UserLoggedIn && a.KeyStoreAccess && !a.NewUser {
err := a.initWallet(keystorePath)
if err != nil {
a.UserLoggedIn = false
}
}
a.NewUser = false
return a.UserLoggedIn
}
// CheckTermsOfService is called from the FE to check the termsOfService has been accepted
func (a *WalletApplication) CheckTermsOfService() bool {
return a.wallet.TermsOfService
}
// LogOut will reset the wallet UI and clear the wallet objects
func (a *WalletApplication) LogOut() bool {
if a.TransactionFinished {
a.UserLoggedIn = false
a.wallet = models.Wallet{}
return true
}
a.sendWarning("Cannot log out while transaction is processing. Please try again.")
return false
}
// ImportKey is called from the frontend when browsing the fs for a keyfile
func (a *WalletApplication) ImportKey() string {
var keyfile = a.RT.Dialog.SelectFile()
if keyfile == "" {
a.LoginError("Access Denied. No key path detected.")
return ""
}
if keyfile[len(keyfile)-4:] != ".p12" {
a.LoginError("Access Denied. Not a key file.")
return ""
}
a.log.Info("Path to imported key: " + keyfile)
return keyfile
}
// SelectDirToStoreKey is called from the FE when creating a new keyfile
func (a *WalletApplication) SelectDirToStoreKey() string {
var keyfile = a.RT.Dialog.SelectSaveFile()
if len(keyfile) <= 0 {
a.LoginError("No valid path were provided. Please try again.")
return ""
}
if keyfile[len(keyfile)-4:] != ".p12" {
keyfile = keyfile + ".p12"
return keyfile
}
return keyfile
}
// GenerateSaltedHash converts plain text to a salted hash
func (a *WalletApplication) GenerateSaltedHash(s string) (string, error) {
saltedBytes := []byte(s)
hashedBytes, err := bcrypt.GenerateFromPassword(saltedBytes, bcrypt.DefaultCost)
if err != nil {
return "", err
}
hash := string(hashedBytes[:])
return hash, nil
}
// CheckAccess verifies that the user has entered the correct password
func (a *WalletApplication) CheckAccess(password, passwordHash string) bool {
err := a.Compare(password, passwordHash)
if err != nil {
a.log.Warnln("User tried to login with the wrong credentials!")
return false
}
a.log.Infoln("Password check OK")
return true
}
// Compare compares a string with a salted hash
func (a *WalletApplication) Compare(s, hash string) error {
incoming := []byte(s)
existing := []byte(hash)
return bcrypt.CompareHashAndPassword(existing, incoming)
}

4
gui_wails_learn/backend/models/address.go

@ -1,4 +0,0 @@
package models
// Address holds the DAG address
type Address string

10
gui_wails_learn/backend/models/path.go

@ -1,10 +0,0 @@
package models
// Path carries the file paths
type Path struct {
ID uint `json:"id"`
Alias string `json:"alias"`
LastTXFile string
PrevTXFile string
EmptyTXFile string
}

19
gui_wails_learn/backend/models/txhistory.go

@ -1,19 +0,0 @@
package models
// TXHistory stores inidividual transactions
type TXHistory struct {
ID uint `json:"id"`
Alias string `json:"alias"`
Amount int64 `json:"amount"`
Sender string `json:"sender"`
Receiver string `json:"receiver"`
Fee int64 `json:"fee"`
Hash string `json:"hash"`
LastTransactionRef struct {
Hash string `json:"prevHash"`
Ordinal int `json:"ordinal"`
} `json:"lastTransactionRef"`
TS string `json:"date"`
Status string `json:"status"`
Failed bool
}

41
gui_wails_learn/backend/models/wallet.go

@ -1,41 +0,0 @@
package models
import (
"time"
_ "github.com/jinzhu/gorm/dialects/sqlite"
)
// Wallet holds all wallet information.
type Wallet struct {
ID uint `gorm:"AUTO_INCREMENT"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt *time.Time
Path Path `gorm:"foreignkey:Alias"`
KeystorePasswordHash string
KeyPasswordHash string
KeyStorePath string
WalletAlias string `gorm:"primary_key;unique"`
Addresses []Address `sql:"-"`
TXHistory []TXHistory `gorm:"foreignkey:Alias"`
ProfilePicture string
WalletTag string
DarkMode bool
Currency string
TermsOfService bool
Balance float64 `json:"balance"`
AvailableBalance float64 `json:"available_balance"`
Nonce float64 `json:"nonce"`
TotalBalance float64 `json:"total_balance"`
Delegated float64 `json:"delegated"`
Deposit float64 `json:"deposit"`
Address string `json:"address"`
TokenPrice struct {
DAG struct {
BTC float64 `json:"BTC,omitempty"`
USD float64 `json:"USD,omitempty"`
EUR float64 `json:"EUR,omitempty"`
} `json:"DAG"`
} `json:"token_price"`
}

166
gui_wails_learn/backend/settings.go

@ -1,166 +0,0 @@
package app
import (
"image"
"image/jpeg"
"io"
"os"
"strings"
)
// UploadImage will forward the image path of the selected image.
func (a *WalletApplication) UploadImage() string {
filePath := a.RT.Dialog.SelectFile()
splitPath := strings.Split(filePath, "/")
filename := splitPath[len(splitPath)-1]
a.log.Info("Path to user uploaded image: " + filePath)
err := CopyFile(filePath, a.paths.ImageDir+filename)
if err != nil && filePath != "" {
a.log.Errorln("Unable to copy image. ", err)
a.sendError("Unable to change Image. ", err)
return "None"
}
file, err := os.Open(filePath)
if err != nil && filePath != "" {
a.log.Errorln("Unable to open image. ", err)
a.sendError("Unable to find Image on the path provided. ", err)
return "None"
}
defer file.Close()
img, _, err := image.DecodeConfig(file)
if err != nil {
a.log.Info("Attempting to decode as JPEG")
img, err = jpeg.DecodeConfig(file)
if err != nil {
a.log.Errorln("Unable to decode image configuration", err)
a.sendError("Unable to change Image. ", err)
return "None"
}
}
a.log.Info("Uploaded image resolution is set to ", img.Height, "x", img.Width)
if img.Height >= 201 || img.Width >= 201 {
a.log.Warnf("Image resolution is too big. Cannot be bigger than 200x200 ")
return "None"
}
a.StoreImagePathInDB(filename)
return filename
}
// GetImagePath is called from the Login.Vue. It'll query the DB for the user's profile picture
// and return it to the FE to be displayed.
func (a *WalletApplication) GetImagePath() string {
if err := a.DB.Model(&a.wallet).Where("wallet_alias = ?", a.wallet.WalletAlias).Error; err != nil {
a.log.Errorln("Unable to query the DB record for the Image path. Reason: ", err)
a.sendError("Unable to query the DB record for the Image path. Reason: ", err)
return ""
}
a.log.Infoln("Profile Picture selected: ", a.wallet.ProfilePicture)
return a.wallet.ProfilePicture
}
// StoreImagePathInDB stores the path to where the profile picture is located in the database
func (a *WalletApplication) StoreImagePathInDB(path string) bool {
if err := a.DB.Model(&a.wallet).Where("wallet_alias = ?", a.wallet.WalletAlias).Update("ProfilePicture", path).Error; err != nil {
a.log.Errorln("Unable to update the DB record with the Image path. Reason: ", err)
a.sendError("Unable to update the DB record with the Image path. Reason: ", err)
return false
}
return true
}
// GetWalletTag is called from the Login.Vue
func (a *WalletApplication) GetWalletTag() string {
if err := a.DB.Model(&a.wallet).Where("wallet_alias = ?", a.wallet.WalletAlias).Error; err != nil {
a.log.Errorln("Unable to query the DB record for the Image path. Reason: ", err)
a.sendError("Unable to query the DB record for the Image path. Reason: ", err)
}
a.log.Infoln("Wallet Tag selected: ", a.wallet.WalletTag)
return a.wallet.WalletTag
}
// StoreWalletLabelInDB takes a wallet label string entered by a user and stores it in the database
func (a *WalletApplication) StoreWalletLabelInDB(walletTag string) bool {
if err := a.DB.Model(&a.wallet).Where("wallet_alias = ?", a.wallet.WalletAlias).Update("WalletTag", walletTag).Error; err != nil {
a.log.Errorln("Unable to update the DB record with the wallet tag. Reason: ", err)
a.sendError("Unable to update the DB record with the wallet tag. Reason: ", err)
return false
}
return true
}
// GetUserTheme is called from the Login.Vue
func (a *WalletApplication) GetUserTheme() bool {
if err := a.DB.Model(&a.wallet).Where("wallet_alias = ?", a.wallet.WalletAlias).Error; err != nil {
a.log.Errorln("Unable to query the DB record for the Image path. Reason: ", err)
a.sendError("Unable to query the DB record for the Image path. Reason: ", err)
}
if a.wallet.DarkMode {
a.log.Infoln("Dark mode enabled")
}
return a.wallet.DarkMode
}
// StoreDarkModeStateDB stores the darkmode state in the user DB
func (a *WalletApplication) StoreDarkModeStateDB(darkMode bool) bool {
if err := a.DB.Model(&a.wallet).Where("wallet_alias = ?", a.wallet.WalletAlias).Update("DarkMode", darkMode).Error; err != nil {
a.log.Errorln("Unable to store darkmode state. Reason: ", err)
a.sendError("Unable to store darkmode state persistently. Reason: ", err)
return false
}
return true
}
// StoreCurrencyStateDB stores the currency state in the user DB
func (a *WalletApplication) StoreCurrencyStateDB(currency string) bool {
if err := a.DB.Model(&a.wallet).Where("wallet_alias = ?", a.wallet.WalletAlias).Update("Currency", currency).Error; err != nil {
a.log.Errorln("Unable to store currency state. Reason: ", err)
a.sendError("Unable to store currency state persistently. Reason: ", err)
return false
}
totalCurrencyBalance := 0.0
if a.wallet.Currency == "USD" {
totalCurrencyBalance = float64(a.wallet.Balance) * a.wallet.TokenPrice.DAG.USD
} else if a.wallet.Currency == "EUR" {
totalCurrencyBalance = float64(a.wallet.Balance) * a.wallet.TokenPrice.DAG.EUR
} else if a.wallet.Currency == "BTC" {
totalCurrencyBalance = float64(a.wallet.Balance) * a.wallet.TokenPrice.DAG.BTC
}
a.RT.Events.Emit("totalValue", a.wallet.Currency, totalCurrencyBalance)
return true
}
// UpdateMolly is called from the frontend and triggers the application update
func (a *WalletApplication) UpdateMolly() {
update := new(UpdateWallet)
update.app = a
update.Run()
}
// CopyFile the src file to dst. Any existing file will be overwritten and will not
// copy file attributes.
func CopyFile(src, dst string) error {
in, err := os.Open(src)
if err != nil {
return err
}
defer in.Close()
out, err := os.Create(dst)
if err != nil {
return err
}
defer out.Close()
_, err = io.Copy(out, in)
if err != nil {
return err
}
return out.Close()
}

374
gui_wails_learn/backend/transactions.go

@ -1,374 +0,0 @@
package app
import (
"bufio"
"bytes"
"encoding/json"
"io/ioutil"
"net/http"
"os"
"time"
"github.com/grvlle/constellation_wallet/backend/models"
)
// Transaction contains all tx information
type Transaction struct {
Edge struct {
ObservationEdge struct {
Parents []struct {
HashReference string `json:"hashReference"`
HashType string `json:"hashType"`
BaseHash string `json:"baseHash"`
} `json:"parents"`
Data struct {
HashReference string `json:"hashReference"`
HashType string `json:"hashType"`
BaseHash string `json:"baseHash"`
} `json:"data"`
} `json:"observationEdge"`
SignedObservationEdge struct {
SignatureBatch struct {
Hash string `json:"hash"`
Signatures []struct {
Signature string `json:"signature"`
ID struct {
Hex string `json:"hex"`
} `json:"id"`
} `json:"signatures"`
} `json:"signatureBatch"`
} `json:"signedObservationEdge"`
Data struct {
Amount int64 `json:"amount"`
LastTxRef struct {
PrevHash string `json:"prevHash"`
Ordinal int `json:"ordinal"`
} `json:"lastTxRef"`
Fee int64 `json:"fee,omitempty"`
Salt int64 `json:"salt"`
} `json:"data"`
} `json:"edge"`
LastTxRef struct {
PrevHash string `json:"prevHash"`
Ordinal int `json:"ordinal"`
} `json:"lastTxRef"`
IsDummy bool `json:"isDummy"`
IsTest bool `json:"isTest"`
}
/* Send a transaction */
// TriggerTXFromFE will initate a new transaction triggered from the frontend.
func (a *WalletApplication) TriggerTXFromFE(amount float64, fee float64, address string) bool {
amountConverted := int64(amount * 1e8)
feeConverted := int64(fee * 1e8)
a.PrepareTransaction(amountConverted, feeConverted, address)
for !a.TransactionFinished {
time.Sleep(1 * time.Second)
}
return a.TransactionFailed
}
// PrepareTransaction is triggered from the frontend (Transaction.vue) and will initialize a new tx.
// methods called are defined in buildchain.go
func (a *WalletApplication) PrepareTransaction(amount int64, fee int64, address string) {
balance, err := a.GetTokenBalance()
if err != nil {
a.log.Errorln("Error when querying wallet balance. Reason: ", err)
a.sendWarning("Unable to poll balance for wallet. Please try again later.")
a.TransactionFailed = true
return
}
if amount+fee > int64(balance*1e8) {
a.log.Warnf("Trying to send: %d", amount+fee)
a.log.Warnf("Insufficient Balance: %d", int64(balance*1e8))
a.sendWarning("Insufficent Balance.")
a.TransactionFailed = true
return
}
if a.TransactionFinished {
a.TransactionFinished = false
// Asynchronously inform FE of TX state in wallet.
go func() {
for !a.TransactionFinished {
a.RT.Events.Emit("tx_in_transit", a.TransactionFinished)
time.Sleep(1 * time.Second)
}
a.RT.Events.Emit("tx_in_transit", a.TransactionFinished)
}()
ptx := a.loadTXFromFile(a.paths.PrevTXFile)
ltx := a.loadTXFromFile(a.paths.LastTXFile)
ptxObj, ltxObj := a.convertToTXObject(ptx, ltx)
a.formTXChain(amount, fee, address, ptxObj, ltxObj)
}
}
func (a *WalletApplication) putTXOnNetwork(tx *Transaction) (bool, string) {
a.log.Info("Attempting to communicate with mainnet on: " + a.Network.URL + a.Network.Handles.Transaction)
/* TEMPORARILY COMMENTED OUT */
a.log.Warnln("TX Ordinal:", tx.Edge.Data.LastTxRef.Ordinal)
bytesRepresentation, err := json.Marshal(tx)
if err != nil {
a.log.Errorln("Unable to parse JSON data for transaction", err)
a.sendError("Unable to parse JSON data for transaction", err)
return false, ""
}
resp, err := http.Post(a.Network.URL+a.Network.Handles.Transaction, "application/json", bytes.NewBuffer(bytesRepresentation))
if err != nil {
a.log.Errorln("Failed to send HTTP request. Reason: ", err)
a.sendError("Unable to send request to mainnet. Please check your internet connection. Reason: ", err)
return false, ""
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusOK {
bodyBytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
a.log.Errorln(string(bodyBytes))
a.log.Errorln("Failed to read the response body. Reason: ", err)
}
bodyString := string(bodyBytes[1:65])
a.log.Infoln("The bytesize of the request body: ", len(bodyBytes))
if len(bodyBytes) == 66 {
a.log.Info("Transaction Hash: ", bodyString)
a.TxPending(bodyString)
a.log.Infoln("Transaction has been successfully sent to the network.")
a.sendSuccess("Transaction successfully sent!")
return true, bodyString
}
a.log.Warn(bodyString)
a.sendWarning("Unable to put transaction on the network. Reason: " + bodyString)
return false, ""
}
bodyBytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
a.log.Errorln(err)
}
bodyString := string(bodyBytes)
a.sendError("Unable to communicate with mainnet. Reason: "+bodyString, err)
a.log.Errorln("Unable to put TX on the network. HTTP Code: " + string(resp.StatusCode) + " - " + bodyString)
return false, ""
}
func (a *WalletApplication) sendTransaction(txFile string) *models.TXHistory {
txObject := a.loadTXFromFile(txFile)
tx := &Transaction{}
bytes := []byte(txObject)
err := json.Unmarshal(bytes, &tx)
if err != nil {
a.sendError("Unable to parse the last transaction. Reason:", err)
a.log.Errorf("Unable to parse contents of last_tx. Reason: %s", err)
return nil
}
// Put TX object on network
TXSuccessfullyPutOnNetwork, hash := a.putTXOnNetwork(tx)
if TXSuccessfullyPutOnNetwork {
txData := &models.TXHistory{
Amount: tx.Edge.Data.Amount,
Receiver: tx.Edge.ObservationEdge.Parents[1].HashReference,
Fee: tx.Edge.Data.Fee,
Hash: hash,
TS: time.Now().Format("Jan _2 15:04:05"),
Status: "Pending",
Failed: false,
}
a.storeTX(txData)
a.RT.Events.Emit("new_transaction", txData) // Pass the tx to the frontend as a new transaction.
a.TransactionFinished = true
a.TransactionFailed = false
return txData
}
txData := &models.TXHistory{
Amount: tx.Edge.Data.Amount,
Receiver: tx.Edge.ObservationEdge.Parents[1].HashReference,
Fee: tx.Edge.Data.Fee,
Hash: hash,
TS: time.Now().Format("Jan _2 15:04:05"),
Status: "Error",
Failed: true,
}
a.log.Errorln("TX Failed, storing with failed state.")
a.storeTX(txData)
a.TransactionFinished = true
a.TransactionFailed = true
return txData
}
func (a *WalletApplication) storeTX(txData *models.TXHistory) {
if txData == nil {
return
}
if err := a.DB.Model(&a.wallet).Where("wallet_alias = ?", a.wallet.WalletAlias).Association("TXHistory").Append(txData).Error; err != nil {
a.log.Errorln("Unable to update the DB record with the new TX. Reason: ", err)
a.sendError("Unable to update the DB record with the new TX. Reason: ", err)
}
a.log.Infoln("Successfully stored tx in DB")
}
// loadTXFromFile takes a file, scans it and returns it in an object
func (a *WalletApplication) loadTXFromFile(txFile string) string {
var txObjects string
fi, err := os.Stat(txFile)
if err != nil {
a.log.Errorln("Unable to stat last_tx. Reason: ", err)
a.sendError("Unable to stat last_tx. Reason: ", err)
return ""
}
// get the size
size := fi.Size()
if size <= 0 {
a.log.Info("TX file is empty.")
return ""
}
file, err := os.Open(txFile) // acct
if err != nil {
a.log.Errorln("Unable to open TX file. Reason: ", err)
a.sendError("Unable to read last tx. Aborting... Reason: ", err)
return ""
}
scanner := bufio.NewScanner(file)
scanner.Split(bufio.ScanLines)
for scanner.Scan() {
txObjects = scanner.Text()
}
defer file.Close()
return txObjects
}
/* Query TX */
// TxProcessed will query the last transaction. If no answer is returned, it means it's processed and the
// method will return true.
func (a *WalletApplication) TxProcessed(TXHash string) bool {
a.log.Info("Communicating with mainnet on: " + a.Network.URL + a.Network.Handles.Transaction + "/" + TXHash)
resp, err := http.Get(a.Network.URL + a.Network.Handles.Transaction + "/" + TXHash)
if err != nil {
a.log.Errorln("Failed to send HTTP request. Reason: ", err)
if err := a.DB.Model(&a.wallet).Where("wallet_alias = ?", a.wallet.WalletAlias).Delete(&a.wallet).Error; err != nil {
a.log.Errorln("Unable to delete wallet upon failed import. Reason: ", err)
return false
}
a.log.Errorln("Unable to verify transaction status. Please check your internet connection.")
return false
}
defer resp.Body.Close()
if resp.Body == nil {
return false
}
bodyBytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
return false
}
// Declared an empty interface
var result map[string]interface{}
// Unmarshal or Decode the JSON to the interface.
err = json.Unmarshal(bodyBytes, &result)
if err != nil {
return false
}
if result["cbBaseHash"] != nil {
a.log.Infoln("CheckPoint Hash :", result["cbBaseHash"])
return true
}
// null response means it's snapshotted
return string(bodyBytes) == "null"
}
type txStatus struct {
Complete string
Pending string
Error string
}
// TxPending takes a TX Hash and updates the frontend with the current status (Pending/Error/Complete)
func (a *WalletApplication) TxPending(TXHash string) {
status := &txStatus{
Complete: "Complete",
Pending: "Pending",
Error: "Error",
}
consensus := 0
select {
case <-a.killSignal:
return
default:
go func() bool {
for retryCounter := 0; retryCounter < 30; retryCounter++ {
processed := a.TxProcessed(TXHash)
if !processed {
a.log.Warnf("Transaction %v pending", TXHash)
a.RT.Events.Emit("tx_pending", status.Pending)
time.Sleep(time.Duration(retryCounter) * time.Second) // Increase polling interval
if retryCounter == 29 {
// Register failed transaction
a.sendWarning("Unable to get verification of processed transaction from the network. Please try again later.")
a.log.Errorf("Unable to get status from the network on transaction: %s", TXHash)
a.RT.Events.Emit("tx_pending", status.Error)
if err := a.DB.Table("tx_histories").Where("hash = ?", TXHash).Updates(map[string]interface{}{"status": status.Error, "failed": true}).Error; err != nil {
a.log.Errorln("Unable to query database object for the imported wallet. Reason: ", err)
a.LoginError("Unable to query database object for the imported wallet.")
return false
}
a.RT.Events.Emit("update_tx_history", []models.TXHistory{}) // Clear TX history
a.initTXFromDB()
return false
}
consensus = 0 // Reset consensus
}
if processed && consensus != 3 {
consensus++
a.log.Infof("TX status check has reached consensus %v/3", consensus)
time.Sleep(1 * time.Second)
}
if processed && consensus == 3 { // Need five consecetive confirmations that TX has been processed.
break
}
}
a.log.Infof("Transaction %v has been successfully processed", TXHash)
a.sendSuccess("Transaction " + TXHash[:30] + "... has been successfully processed")
if err := a.DB.Table("tx_histories").Where("hash = ?", TXHash).UpdateColumn("status", status.Complete).Error; err != nil {
a.log.Errorln("Unable to query database object for the imported wallet. Reason: ", err)
a.LoginError("Unable to query database object for the imported wallet.")
return false
}
a.RT.Events.Emit("tx_pending", status.Complete)
a.RT.Events.Emit("update_tx_history", []models.TXHistory{}) // Clear TX history
a.initTXFromDB()
return true
}()
}
}

188
gui_wails_learn/backend/utils.go

@ -1,188 +0,0 @@
package app
import (
"bytes"
"encoding/hex"
"fmt"
"io"
"io/ioutil"
"math/rand"
"net/http"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"time"
"github.com/dustin/go-humanize"
)
// newReleaseAvailable generates a notification to FE everytime a new release on
// GitHub doesn't match a.Version.
func (a *WalletApplication) newReleaseAvailable() {
update := new(UpdateWallet)
update.app = a
currentRelease := a.Version
a.log.Infoln("Checking for new releases...")
go func() {
for i := 200; i > 0; i-- {
newRelease := update.GetLatestRelease()
if currentRelease != newRelease {
a.log.Infoln("There's a newer release available")
a.RT.Events.Emit("new_release", newRelease)
}
time.Sleep(time.Duration(i) * time.Second)
}
}()
}
func (a *WalletApplication) javaInstalled() bool {
var javaInstalled bool
if a.paths.Java[len(a.paths.Java)-9:] != "javaw.exe" {
javaInstalled = false
} else {
javaInstalled = true
}
return javaInstalled
}
func (a *WalletApplication) detectJavaPath() {
if runtime.GOOS == "windows" {
var jwPath string
cmd := exec.Command("cmd", "/c", "where", "java")
a.log.Infoln("Running command: ", cmd)
var out bytes.Buffer
var stderr bytes.Buffer
cmd.Stdout = &out // Captures STDOUT
cmd.Stderr = &stderr // Captures STDERR
err := cmd.Run()
if err != nil {
errFormatted := fmt.Sprint(err) + ": " + stderr.String()
a.log.Errorf(errFormatted)
a.LoginError("Unable to find Java Installation")
a.paths.Java = "No valid path detected"
return
}
jPath := out.String() // May contain multiple
if jPath == "" {
a.LoginError("Unable to find Java Installation")
a.paths.Java = "No valid path detected"
return
}
s := strings.Split(strings.Replace(jPath, "\r\n", "\n", -1), "\n")
jwPath = string(s[0][:len(s[0])-4]) + "w.exe" // Shifting to javaw.exe
if s[1] != "" {
jwPath = string(s[1][:len(s[1])-4]) + "w.exe" // Shifting to javaw.exe
a.log.Info("Detected a secondary java path. Using that over the first one.")
}
a.log.Infoln("Java path selected: " + jwPath)
a.log.Debugln(cmd)
a.paths.Java = jwPath
}
}
//normalizeAmounts takes amount/fee in int64 and normalizes it. Example: passing 821500000000 will return 8215
func normalizeAmounts(i int64) (string, error) {
f := fmt.Sprintf("%.8f", float64(i)/1e8)
return f, nil
}
// TempFileName creates temporary file names for the transaction files
func (a *WalletApplication) TempFileName(prefix string) string {
randBytes := make([]byte, 16)
rand.Read(randBytes)
return filepath.Join(a.paths.TMPDir, prefix+hex.EncodeToString(randBytes))
}
// WriteCounter stores dl state of the cl binaries
type WriteCounter struct {
Total uint64
LastEmit uint64
Filename string
a *WalletApplication
}
// Write emits the download progress of the CL binaries to the frontend
func (wc *WriteCounter) Write(p []byte) (int, error) {
n := len(p)
wc.Total += uint64(n)
if (wc.Total - wc.LastEmit) > uint64(800) {
wc.a.RT.Events.Emit("downloading", wc.Filename, humanize.Bytes(wc.Total))
wc.LastEmit = wc.Total
}
return n, nil
}
func (a *WalletApplication) fetchWalletJar(filename string, filepath string) error {
url := a.WalletCLI.URL + "/v" + a.WalletCLI.Version + "/" + filename
a.log.Info(url)
out, err := os.Create(filepath + ".tmp")
if err != nil {
return err
}
resp, err := http.Get(url)
if err != nil {
out.Close()
return err
}
defer resp.Body.Close()
counter := &WriteCounter{}
counter.a = a
counter.Filename = filename
counter.LastEmit = uint64(0)
if _, err = io.Copy(out, io.TeeReader(resp.Body, counter)); err != nil {
out.Close()
return err
}
out.Close()
if err = os.Rename(filepath+".tmp", filepath); err != nil {
return err
}
return nil
}
func (a *WalletApplication) directoryCreator(directories ...string) error {
for _, d := range directories {
err := os.MkdirAll(d, os.ModePerm)
if err != nil {
return err
}
}
return nil
}
func (a *WalletApplication) fileExists(path string) bool {
info, err := os.Stat(path)
if os.IsNotExist(err) {
return false
}
return !info.IsDir()
}
// WriteToFile will print any string of text to a file safely by
// checking for errors and syncing at the end.
func WriteToFile(filename string, data []byte) error {
err := ioutil.WriteFile(filename, data, 0666)
if err != nil {
return err
}
return nil
}

542
gui_wails_learn/backend/wallet.go

@ -1,542 +0,0 @@
package app
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"os"
"runtime"
"strconv"
"strings"
"time"
"github.com/grvlle/constellation_wallet/backend/models"
)
// ImportWallet is triggered when a user logs into a new Molly wallet for the first time
func (a *WalletApplication) ImportWallet(keystorePath, keystorePassword, keyPassword, alias string) bool {
alias = strings.ToLower(alias)
a.wallet = models.Wallet{
KeyStorePath: keystorePath,
WalletAlias: alias,
Currency: "USD"}
if runtime.GOOS == "windows" && !a.javaInstalled() {
a.LoginError("Unable to detect your Java path. Please make sure that Java has been installed.")
return false
}
if !a.TransactionFinished {
a.log.Warn("Cannot Import wallet in a pending transaction.")
a.LoginError("Cannot import a new wallet while there's a pending transaction.")
return false
}
if keystorePath == "" {
a.LoginError("Please provide a path to the KeyStore file.")
return false
}
if !a.passwordsProvided(keystorePassword, keyPassword, alias) {
a.log.Warnln("One or more passwords were not provided.")
return false
}
os.Setenv("CL_STOREPASS", keystorePassword)
os.Setenv("CL_KEYPASS", keyPassword)
a.wallet.Address = a.GenerateDAGAddress()
a.KeyStoreAccess = a.WalletKeystoreAccess()
if a.KeyStoreAccess {
if !a.DB.NewRecord(&a.wallet) {
keystorePasswordHashed, err := a.GenerateSaltedHash(keystorePassword)
if err != nil {
a.log.Errorln("Unable to generate password hash. Reason: ", err)
a.LoginError("Unable to generate password hash.")
return false
}
keyPasswordHashed, err := a.GenerateSaltedHash(keyPassword)
if err != nil {
a.log.Errorln("Unable to generate password hash. Reason: ", err)
a.LoginError("Unable to generate password hash.")
return false
}
if err := a.DB.Create(&a.wallet).Error; err != nil {
a.log.Errorln("Unable to create database object for the imported wallet. Reason: ", err)
a.LoginError("Unable to create database object for the imported wallet. Maybe it has already been imported? Try to login.")
return false
}
a.paths.LastTXFile = a.TempFileName("tx-")
a.paths.PrevTXFile = a.TempFileName("tx-")
a.paths.EmptyTXFile = a.TempFileName("tx-")
err = a.createTXFiles()
if err != nil {
a.log.Fatalln("Unable to create TX files. Check fs permissions. Reason: ", err)
a.sendError("Unable to create TX files. Check fs permissions. Reason: ", err)
}
if err := a.DB.Model(&a.wallet).Where("wallet_alias = ?", a.wallet.WalletAlias).Update("Path", models.Path{LastTXFile: a.paths.LastTXFile, PrevTXFile: a.paths.PrevTXFile, EmptyTXFile: a.paths.EmptyTXFile}).Error; err != nil {
a.log.Errorln("Unable to update the DB record with the tmp tx-paths. Reason: ", err)
a.sendError("Unable to update the DB record with the tmp tx-paths. Reason: ", err)
}
if err := a.DB.Where("wallet_alias = ?", a.wallet.WalletAlias).First(&a.wallet).Updates(&models.Wallet{KeyStorePath: keystorePath, KeystorePasswordHash: keystorePasswordHashed, KeyPasswordHash: keyPasswordHashed}).Error; err != nil {
a.log.Errorln("Unable to query database object for the imported wallet. Reason: ", err)
a.LoginError("Unable to query database object for the imported wallet.")
return false
}
a.UserLoggedIn = false
a.NewUser = true
a.WalletImported = true
err = a.initWallet(keystorePath)
if err != nil {
a.log.Errorln("Failed to initialize wallet. Reason: ", err)
// If unable to import previous transactions, remove wallet from DB and logout.
if err := a.DB.Model(&a.wallet).Where("wallet_alias = ?", a.wallet.WalletAlias).Delete(&a.wallet).Error; err != nil {
a.log.Errorln("Unable to delete wallet upon failed import. Reason: ", err)
return false
}
return false
}
return true
} else if a.DB.NewRecord(&a.wallet) { // There may already be an existing DB record in some circumstances.
a.DB.First(&a.wallet)
a.UserLoggedIn = false
a.NewUser = false
a.WalletImported = true
err := a.initWallet(a.wallet.KeyStorePath)
if err != nil {
a.log.Errorln("Faled to initialize wallet. Reason: ", err)
return false
}
return true
}
}
return false
}
// CreateWallet is called when creating a new wallet in frontend component Login.vue
func (a *WalletApplication) CreateWallet(keystorePath, keystorePassword, keyPassword, alias, label string) bool {
alias = strings.ToLower(alias)
if runtime.GOOS == "windows" && !a.javaInstalled() {
a.LoginError("Unable to detect your Java path. Please make sure that Java has been installed.")
return false
}
if !a.TransactionFinished {
a.log.Warn("Cannot Create wallet in a pending transaction.")
a.LoginError("Cannot create a new wallet while there's a pending transaction.")
return false
}
if keystorePath == "" {
a.LoginError("Please provide a path to store the KeyStore file.")
return false
}
if !a.passwordsProvided(keystorePassword, keyPassword, alias) {
a.log.Warnln("One or more passwords were not provided.")
return false
}
if alias == "" {
alias = a.wallet.WalletAlias
}
os.Setenv("CL_STOREPASS", keystorePassword)
os.Setenv("CL_KEYPASS", keyPassword)
keystorePasswordHashed, err := a.GenerateSaltedHash(keystorePassword)
if err != nil {
a.log.Errorln("Unable to generate password hash. Reason: ", err)
a.sendError("Unable to generate password hash. Reason: ", err)
return false
}
keyPasswordHashed, err := a.GenerateSaltedHash(keyPassword)
if err != nil {
a.log.Errorln("Unable to generate password hash. Reason: ", err)
a.sendError("Unable to generate password hash. Reason: ", err)
return false
}
a.wallet = models.Wallet{
KeyStorePath: keystorePath,
KeystorePasswordHash: keystorePasswordHashed,
KeyPasswordHash: keyPasswordHashed,
WalletAlias: alias,
WalletTag: label}
if !a.DB.NewRecord(&a.wallet) {
if err := a.DB.Create(&a.wallet).Error; err != nil {
a.log.Errorln("Unable to create database object for new wallet. Reason: ", err)
a.LoginError("Unable to create new wallet. Alias already exists.")
return false
}
if err := a.DB.Where("wallet_alias = ?", alias).First(&a.wallet).Updates(&models.Wallet{KeyStorePath: keystorePath, KeystorePasswordHash: keystorePasswordHashed, KeyPasswordHash: keyPasswordHashed}).Error; err != nil {
a.log.Errorln("Unable to query database object for new wallet after wallet creation. Reason: ", err)
a.sendError("Unable to query database object for new wallet after wallet creation. Reason: ", err)
return false
}
err = a.CreateEncryptedKeyStore()
if err != nil {
return false
}
a.wallet.Address = a.GenerateDAGAddress()
if err := a.DB.Model(&a.wallet).Where("wallet_alias = ?", a.wallet.WalletAlias).Update("Address", a.wallet.Address).Error; err != nil {
a.log.Errorln("Unable to update db object new wallet, with the DAG address. Reason: ", err)
a.sendError("Unable to update db object new wallet, with the DAG address. Reason. Reason: ", err)
}
a.KeyStoreAccess = a.WalletKeystoreAccess()
if a.KeyStoreAccess {
a.paths.LastTXFile = a.TempFileName("tx-")
a.paths.PrevTXFile = a.TempFileName("tx-")
a.paths.EmptyTXFile = a.TempFileName("tx-")
err := a.createTXFiles()
if err != nil {
a.log.Fatalln("Unable to create TX files. Check fs permissions. Reason: ", err)
a.sendError("Unable to create TX files. Check fs permissions. Reason: ", err)
}
if err := a.DB.Where("wallet_alias = ?", a.wallet.WalletAlias).First(&a.wallet).Update("Path", models.Path{LastTXFile: a.paths.LastTXFile, PrevTXFile: a.paths.PrevTXFile, EmptyTXFile: a.paths.EmptyTXFile}).Error; err != nil {
a.log.Errorln("Unable to update the DB record with the tmp tx-paths. Reason: ", err)
a.sendError("Unable to update the DB record with the tmp tx-paths. Reason: ", err)
}
a.UserLoggedIn = false
a.FirstTX = true
a.NewUser = true
a.initNewWallet()
return true
}
} else {
a.LoginError("Unable to create new wallet. Alias already exists.")
}
return false
}
// initWallet initializes a new wallet. This is called from login.vue
// only when a new wallet is created.
func (a *WalletApplication) initNewWallet() {
a.StoreImagePathInDB("faces/face-0.jpg")
//a.initTransactionHistory()
a.passKeysToFrontend()
if !a.WidgetRunning.DashboardWidgets {
a.initDashboardWidgets()
}
a.log.Infoln("A New wallet has been created successfully!")
}
// initExistingWallet queries the database for the user wallet and pushes
// the information to the front end components.
func (a *WalletApplication) initWallet(keystorePath string) error {
if a.NewUser {
err := a.initTXFromBlockExplorer()
if err != nil {
return err
}
a.StoreImagePathInDB("faces/face-0.jpg")
} else if !a.NewUser {
a.initTXFromDB() // Disregard upon import
a.initTXFilePath() // Update paths from DB.
}
a.RT.Events.Emit("wallet_init", a.wallet.TermsOfService, a.wallet.Currency)
if !a.WidgetRunning.DashboardWidgets {
a.initDashboardWidgets()
}
if !a.WidgetRunning.PassKeysToFrontend {
a.passKeysToFrontend()
}
a.log.Infoln("User has logged into the wallet")
return nil
}
func (a *WalletApplication) initDashboardWidgets() {
// Initializes a struct containing all Chart Data on the dashboard
chartData := a.ChartDataInit()
// Below methods are continously updating the client side modules.
a.pollTokenBalance()
a.nodeStats(chartData)
a.txStats(chartData)
a.networkStats(chartData)
a.blockAmount()
a.pricePoller()
a.WidgetRunning.DashboardWidgets = true
}
func (a *WalletApplication) createTXFiles() error {
files := []string{a.paths.LastTXFile, a.paths.PrevTXFile, a.paths.EmptyTXFile}
for _, f := range files {
file, err := os.Create(f)
if err != nil {
return err
}
defer file.Close()
}
return nil
}
// ImportKeys is called from the frontend to use a file dialog to select key file.
func (a *WalletApplication) ImportKeys() error {
filename := a.RT.Dialog.SelectFile()
a.log.Info("Path to keys that user wants to import: " + filename)
return nil
}
// ExportKeys is called from the frontend to use a file dialog to select directory
// where user wants to export the keys to.
func (a *WalletApplication) ExportKeys() error {
filename := a.RT.Dialog.SelectDirectory()
a.log.Info("File user wants to save to: " + filename)
return nil
}
func (a *WalletApplication) initTXFilePath() {
paths := &a.wallet.Path
if err := a.DB.Model(&a.wallet).Where("alias = ?", a.wallet.WalletAlias).Association("Path").Find(&paths).Error; err != nil {
a.log.Fatal("Unable to initialize TX filepaths. Reason: ", err)
a.sendError("Unable to initialize TX filepaths. Reason: ", err)
return
}
if a.wallet.Path.LastTXFile == "" && a.wallet.Path.PrevTXFile == "" {
a.log.Fatal("Unable to initialize TX filepaths. Both are empty after DB query.")
}
a.paths.LastTXFile = a.wallet.Path.LastTXFile
a.paths.PrevTXFile = a.wallet.Path.PrevTXFile
a.paths.EmptyTXFile = a.wallet.Path.EmptyTXFile
}
func (a *WalletApplication) initTXFromDB() {
if err := a.DB.Model(&a.wallet).Where("alias = ?", a.wallet.WalletAlias).Association("TXHistory").Find(&a.wallet.TXHistory).Error; err != nil {
a.log.Error("Unable to initialize historic transactions from DB. Reason: ", err)
a.sendError("Unable to initialize historic transactions from DB. Reason: ", err)
return
}
allTX := []models.TXHistory{}
for i, tx := range a.wallet.TXHistory {
allTX = append([]models.TXHistory{tx}, allTX...) // prepend to reverse list for FE
if a.wallet.TXHistory[i].Status == "Pending" {
a.TxPending(a.wallet.TXHistory[i].Hash)
}
}
a.RT.Events.Emit("update_tx_history", allTX) // Pass the tx to the frontend as a new transaction.
}
// initTXFromBlockExplorer is called when an existing wallet is imported.
func (a *WalletApplication) initTXFromBlockExplorer() error {
a.log.Info("Sending API call to block explorer on: " + a.Network.BlockExplorer.URL + a.Network.BlockExplorer.Handles.CollectTX + a.wallet.Address)
resp, err := http.Get(a.Network.BlockExplorer.URL + a.Network.BlockExplorer.Handles.CollectTX + a.wallet.Address)
if err != nil {
a.log.Errorln("Failed to send HTTP request. Reason: ", err)
a.LoginError("Unable to collect previous transactions from blockexplorer.")
return err
}
defer resp.Body.Close()
if resp.Body != nil {
bodyBytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
a.LoginError("Unable to collect previous transactions from blockexplorer. Try again later.")
a.log.Errorln("Unable to collect previous transactions from blockexplorer. Reason: ", err)
return err
}
ok, error := a.verifyAPIResponse(bodyBytes)
// Blockexplorer returns below string when no previous transactions are found
if !ok && error != "Cannot find transactions for sender" {
a.log.Errorln("API returned the following error", error)
a.LoginError("The wallet import failed. Please check your internet connection and try again.")
return errors.New(error)
}
// If no previous transactions for imported wallet - proceed
if !ok && error == "Cannot find transactions for sender" {
a.log.Info("Unable to detect any previous transactions.")
return nil
}
allTX := []models.TXHistory{}
err = json.Unmarshal(bodyBytes, &allTX)
if err != nil {
a.log.Errorln("Unable to fetch TX history from block explorer. Reason: ", err)
a.sendError("Unable to fetch TX history from block explorer. Reason: ", err)
return err
}
// Reverse order
for i := len(allTX)/2 - 1; i >= 0; i-- {
opp := len(allTX) - 1 - i
allTX[i], allTX[opp] = allTX[opp], allTX[i]
}
a.log.Infof("Successfully collected %d previous transactions. Updating local state...", len(allTX))
for i, tx := range allTX {
txData := &models.TXHistory{
Amount: tx.Amount,
Receiver: tx.Receiver,
Fee: tx.Fee,
Hash: tx.Hash,
TS: time.Now().Format("Jan _2 15:04:05") + " (imported)",
Status: "Complete",
Failed: false,
}
a.storeTX(txData)
a.RT.Events.Emit("new_transaction", txData)
if i+1 == len(allTX) {
err := a.rebuildTxChainState(tx.Hash)
if err != nil {
a.log.Errorln(err)
// If unable to import previous transactions, remove wallet from DB and logout.
//TODO: logout
if err := a.DB.Model(&a.wallet).Where("wallet_alias = ?", a.wallet.WalletAlias).Delete(&a.wallet).Error; err != nil {
a.log.Errorln("Unable to delete wallet upon failed import. Reason: ", err)
return err
}
a.log.Panicln("Unable to import previous transactions")
a.LoginError("Unable to collect previous TX's from blockexplorer. Please try again later.")
}
}
}
} else {
a.log.Info("Unable to detect any previous transactions.")
return nil
}
return nil
}
// PassKeysToFrontend emits the keys to the settings.Vue component on a
// 5 second interval
func (a *WalletApplication) passKeysToFrontend() {
if a.wallet.KeyStorePath != "" && a.wallet.Address != "" {
go func() {
for {
a.RT.Events.Emit("wallet_keys", a.wallet.Address)
time.Sleep(5 * time.Second)
}
}()
a.WidgetRunning.PassKeysToFrontend = true
} else {
a.WidgetRunning.PassKeysToFrontend = false
}
}
func (a *WalletApplication) passwordsProvided(keystorePassword, keyPassword, alias string) bool {
if keystorePassword == "" {
a.LoginError("Please provide a Key Store password.")
return false
} else if keyPassword == "" {
a.LoginError("Please provide a Key Password.")
return false
} else if alias == "" {
a.LoginError("An Alias has not been provided.")
return false
}
return true
}
// GetTokenBalance polls and parses the token balance of a wallet and returns it as a float64.
func (a *WalletApplication) GetTokenBalance() (float64, error) {
a.log.Debug("Contacting mainnet on: " + a.Network.URL + a.Network.Handles.Balance + " Sending the following payload: " + a.wallet.Address)
resp, err := http.Get(a.Network.URL + a.Network.Handles.Balance + a.wallet.Address)
if err != nil {
a.log.Errorln("Failed to send HTTP request. Reason: ", err)
return 0, err
}
if resp == nil {
a.log.Errorln("Killing pollTokenBalance after 10 failed attempts to get balance from mainnet, Reason: ", err)
a.sendWarning("Unable to showcase current balance. Please check your internet connectivity and restart the wallet application.")
return 0, err
}
defer resp.Body.Close()
bodyBytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
a.log.Warn("Unable to update token balance. Reason: ", err)
return 0, err
}
var result map[string]interface{}
// Unmarshal or Decode the JSON to the interface.
err = json.Unmarshal(bodyBytes, &result)
if err != nil {
return 0, err
}
s := result["balance"]
if s == "" {
s = "0" // Empty means zero
}
a.log.Infoln("Parsed the following balance: ", s)
b, ok := s.(float64)
if !ok {
if err != nil {
a.log.Warnln("Unable to parse balance. Reason:", err)
}
return 0, err
}
f := fmt.Sprintf("%.2f", b/1e8) // Reverse normalized float
a.log.Infoln("Normalized the following balance: ", f)
balance, err := strconv.ParseFloat(f, 64)
if err != nil {
a.log.Warnln("Unable to type cast string to float for token balance poller. Check your internet connectivity")
return 0, err
}
a.log.Infoln("Returning the following balance: ", balance)
return balance, nil
}

89
gui_wails_learn/docs/faq.md

@ -1,89 +0,0 @@
# Molly Wallet FAQ
This is an attempt to address the most Frequently Asked Questions around the Molly Wallet in hopes of easing the community into leveraging the Desktop Wallet for $DAG to securely store their tokens.
The Molly Wallet is still in the early phases of development with the first stable, mainnet connected build released to the general public mere days ago (at the time of writing this). This means that the way we're distributing the application, and some of the manual steps required will be improved upon with time.
## General Questions
#### Q: Where can I download the Molly Wallet?
**A:** https://github.com/grvlle/constellation_wallet/releases
#### Q: Why can't I login to my wallet?
**A:** You can always reset the Molly Wallet, simply by closing the application, removing your `.dag` folder, starting the application and re-importing your key. See the below sections under your OS of choice for how to locate the `.dag` folder.
#### Q: What does the IMPORT feature do on the login screen?
**A:** The Import feature exists to make it possible to access your wallet key from different computers. This only has to be done once on any computer, and it'll sync your transactions with the mainnet, and update the wallet with your previous transactions.
Once imported, you will access your funds by simply login into the wallet.
#### Q: How do I create a new wallet?
**A:** Select the create wallet button on the login screen and it'll let you browse to a location in which you wish to store your keyfile.p12. Remember to give it a name, and select save.
After that you need to provide the keyfile with authentication, populate the remaining fields and click create. Then the keyfile.p12 will be saved to the location you specified, and you can use that to access your funds through Molly Wallet.
I will organize the questions based on the OS the wallet is installed upon.
## Windows
#### Q: Why am I getting an error when sending transaction?
**A:** This is most likely because you've used an older testnet build of the wallet with testnet artifacts interfering with your mainnet wallet. In order to fix this you need to remove the `.dag` folder that is located in your `C:/Users/<username>/` directory. Once done, feel free to import your wallet again and you should be good to go.
#### Q: I am getting `Unable to detect your Java path, make sure Java has been installed` when trying to login or create/import a wallet.
***A:*** This is either because [OpenJDK v9](https://java.com/) and [JRE](https://www.oracle.com/java/technologies/javase-jre8-downloads.html) hasn't been installed, or because the enviornment paths haven't been set up correctly.
For Molly Wallet to be able to detect the installation directory of Java, the `JAVA_HOME` enviornmental variable needs t
o be set. The Java path also has to be included in the `Path` variable.
Search for enviornment variables in windows search and select *Edit the system enviornment variables*, then set the JAVA
paths like in the below image.
[![env](https://i.ibb.co/Br1M31s/envvars.png)](https://constellationnetwork.io/technology/molly-wallet/)
#### Q: How to open a ``.rar`` file?
[![IMAGE ALT TEXT HERE](https://img.youtube.com/vi/dr0g_Ux7_8M/0.jpg)](https://www.youtube.com/watch?v=dr0g_Ux7_8M)
#### Q: I am running Windows 7 (or earlier) - can I run Molly Wallet?
**A:** The answer is maybe. It's not officially supported, so probably not.
## MacOS
#### Q: Why am I getting an error when sending transaction?
**A:** This is most likely because you've used an older testnet build of the wallet with testnet artifacts interfering with your mainnet wallet. In order to fix this you need to remove the `.dag` folder that is located in your `$HOME` (if you don't know what this is, see [this article](https://www.cnet.com/how-to/how-to-find-your-macs-home-folder-and-add-it-to-finder/)) path. Once done, feel free to import your wallet again and you should be good to go.
#### Q: Why am I getting `The application 'Molly - Constellation Desktop Wallet` can't be opened?
![env](https://i.ibb.co/VWw30HN/a123555f-0881-4ae8-9b1d-7dd36d4d6802.jpg)
**A:** This is because the application is compressed and archived. You need to use a software called [Unarchiver](https://theunarchiver.com/).
After you've downloaded that, right click on the .zip file and select "open with" and then choose "Unarchiver" instead of the default program. It'll unzip and you'll be able to run it.
#### Q: Why is my Molly Wallet download not downloaded as a .zip file?
**A:** On some systems, Safari will automatically unzip the contents. We do not want that, we want to leverage the [Unarchiver](https://theunarchiver.com/). Here's a guide [how to disable automatic unzipping](https://www.addictivetips.com/mac-os/stop-automatically-unzipping-files-in-safari/) of files in Safari. Once that has been disabled, redownload Molly Wallet from the [official website](https://constellationnetwork.io/technology/molly-wallet/) and use [Unarchiver](https://theunarchiver.com/) to extract the contents.
## Linux
#### Q: Why does it say `Alias not found` when trying to import/login/create wallet?
**A:** This is probably due to Java missing on the system. For now, I've only implemented means of detecting that on Windows. So please, if you're using a debian based distribution, download `openjdk-8` from aptitude.
#### Q: Why am I getting an error when sending transaction?
**A:** This is most likely because you've used an older testnet build of the wallet with testnet artifacts interfering with your mainnet wallet. In order to fix this you need to remove the `.dag` folder that is located in your `$HOME` path. Once done, feel free to import your wallet again and you should be good to go.
## Java
#### Q: Why is Java needed?
**A:** The wallet binary that I'm integrating Molly Wallet against has been built by the Constellation Core team in a programming language called *Scala*. This is a functional programming language that runs on JVM (Java virtual machine). Thus Java becomes a dependency.

5
gui_wails_learn/frontend/babel.config.js

@ -1,5 +0,0 @@
module.exports = {
presets: [
'@vue/app'
]
}

20364
gui_wails_learn/frontend/package-lock.json

File diff suppressed because it is too large

82
gui_wails_learn/frontend/package.json

@ -1,82 +0,0 @@
{
"name": "my_project",
"author": "vito<gralle89@gmail.com>",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"dependencies": {
"@types/vuelidate": "^0.7.13",
"@wailsapp/runtime": "^1.0.11",
"babel-helper-vue-jsx-merge-props": "^2.0.3",
"bootstrap": "^4.4.1",
"chartist": "^0.11.4",
"core-js": "^2.6.11",
"es6-promise": "^4.2.8",
"jquery": "^3.5.0",
"node-sass": "^4.13.1",
"popper.js": "^1.16.1",
"sass-loader": "^8.0.2",
"serialize-javascript": "^2.1.2",
"update": "^0.7.4",
"vue": "^2.6.11",
"vue-clickaway": "^2.2.2",
"vue-js-toggle-button": "^1.3.3",
"vue-loading-spinner": "^1.0.11",
"vue-notifyjs": "^0.4.3",
"vue-progressbar": "^0.7.5",
"vue-router": "^3.1.6",
"vue-select": "^3.10.3",
"vue-spinner": "^1.0.3",
"vue-spinner-component": "^1.0.5",
"idle-vue": "^2.0.5",
"sweetalert2": "^9.10.12",
"@sweetalert2/theme-bootstrap-4": "^3.1.4",
"vuelidate": "^0.7.5",
"vuex": "^3.1.3",
"webpack": "^4.42.0",
"yarn": "^1.22.4"
},
"devDependencies": {
"@vue/cli-plugin-babel": "^3.12.1",
"@vue/cli-plugin-eslint": "^3.12.1",
"@vue/cli-service": "^4.3.1",
"acorn": "^6.4.1",
"babel-eslint": "^10.1.0",
"eslint": "^5.8.0",
"eslint-plugin-vue": "^5.0.0",
"eventsource-polyfill": "^0.9.6",
"json-loader": "^0.5.7",
"kind-of": "^6.0.3",
"minimist": "^1.2.3",
"prerender-spa-plugin": "^3.4.0",
"vue-template-compiler": "^2.6.11",
"webpack-hot-middleware": "^2.24.3"
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/essential",
"eslint:recommended"
],
"rules": {},
"parserOptions": {
"parser": "babel-eslint"
}
},
"postcss": {
"plugins": {
"autoprefixer": {}
}
},
"browserslist": [
"> 1%",
"last 2 versions",
"not ie <= 8"
]
}

1
gui_wails_learn/frontend/package.json.md5

@ -1 +0,0 @@
3efa6e8b927d5ad25a172f56fca8cf6a

303
gui_wails_learn/frontend/src/App.vue

@ -1,303 +0,0 @@
<template>
<div :class="darkMode ? 'theme--dark' : 'theme--light'">
<vue-progress-bar></vue-progress-bar>
<div :class="{'nav-open': $sidebar.showSidebar}">
<notifications></notifications>
<router-view></router-view>
</div>
<page-overlay text="Applying Update. Please wait..." :isActive="overlay" />
</div>
</template>
<script>
import {mapState} from 'vuex'
import ErrorNotification from "./pages/Notifications/ErrorMessage";
import WarningNotification from "./pages/Notifications/Warning";
import SuccessNotification from "./pages/Notifications/Success";
import NewRelease from "./pages/Notifications/NewRelease";
import Swal from "sweetalert2/dist/sweetalert2";
export default {
components: {
},
computed: {
...mapState('app', ['isLoggedIn', 'downloading']),
...mapState('wallet', ['darkMode'])
},
data() {
return {
overlay: false
};
},
onIdle() {
let timerInterval, closeInSeconds = 10
const swalPopup = Swal.mixin({
customClass: {
container: this.darkMode
? "theme--dark"
: "theme--light"
}
});
if (this.isLoggedIn) {
swalPopup.fire({
title: "You have been idle for 5 minutes.",
html: "To keep your Molly wallet safe from unauthorised access it will automatically logout in <b>10</b> seconds",
showConfirmButton: false,
showCancelButton: false,
timer: closeInSeconds * 1000,
timerProgressBar: true,
onBeforeOpen: () => {
Swal.showLoading();
timerInterval = setInterval(() => {
closeInSeconds--;
const content = Swal.getContent();
if (content) {
const b = content.querySelector('b');
if (b) {
b.textContent = closeInSeconds;
}
}
}, 1000)
},
onClose: () => {
clearInterval(timerInterval)
}
}).then(() => {
if (this.$store.state.idleVue.isIdle) {
window.backend.WalletApplication.LogOut().then(txFinishedState => {
if (txFinishedState) {
this.logout();
}
}), (this.random = "1");
}
});
}
},
mounted() {
// Backend Errors
window.wails.Events.On("error_handling", (m, err) => {
this.$store.commit('app/setErrorMessage', m + err);
this.$notifications.clear();
setTimeout(() => {
this.$notifications.clear();
}, 60000);
this.$notify({
component: ErrorNotification,
timeout: 50000,
icon: "fa fa-times",
horizontalAlign: "right",
verticalAlign: "top",
type: "danger",
onClick: () => {
this.$notifications.clear();
}
});
});
window.wails.Events.On("warning", m => {
this.$store.commit('app/setWarningMessage', m);
this.$notifications.clear();
setTimeout(() => {
this.$notifications.clear();
}, 60000);
this.$notify({
component: WarningNotification,
timeout: 50000,
icon: "fa fa-times",
horizontalAlign: "right",
verticalAlign: "top",
type: "warning",
onClick: () => {
this.$notifications.clear();
}
});
});
window.wails.Events.On("success", m => {
this.$store.commit('app/setSuccessMessage', m);
this.$notifications.clear();
setTimeout(() => {
this.$notifications.clear();
}, 60000);
this.$notify({
component: SuccessNotification,
timeout: 50000,
icon: "fa fa-check",
horizontalAlign: "right",
verticalAlign: "top",
type: "success",
onClick: () => {
this.$notifications.clear();
}
});
});
window.wails.Events.On("new_release", m => {
this.$store.commit('app/setNewRelease', m);
var self = this;
this.$notifications.clear();
this.$notify({
component: NewRelease,
timeout: 500000,
icon: "fa fa-info",
horizontalAlign: "right",
verticalAlign: "bottom",
type: "info",
onClick: () => {
const swalPopup = Swal.mixin({
customClass: {
container: this.darkMode
? "theme--dark"
: "theme--light"
}
});
swalPopup.fire({
title: "Update Molly Wallet",
html: "Do you want to update your Molly Wallet? Selecting update will download the latest build and apply the update. <br><br> <b>The application will restart once update is complete. </b>",
showCloseButton: true,
showCancelButton: true,
focusConfirm: false,
confirmButtonText:
'<i class="fa fa-thumbs-up"></i> <b>Update</b>',
confirmButtonAriaLabel: "Text",
cancelButtonText:
'Cancel',
cancelButtonAriaLabel: "Cancel"
}).then(result => {
if (result.value) {
self.$Progress.start(2000);
self.overlay = true;
window.backend.WalletApplication.UpdateMolly()
self.$notifications.clear();
}
});
}
});
});
window.wails.Events.On("login_error", (m, err) => {
this.$store.commit('app/setLoginErrorMessage', m);
this.$store.commit('app/setDisplayLoginError', err);
setTimeout(() => {
this.$store.commit('app/setDisplayLoginError', false);
}, 10000);
});
// Transactions.vue sockets
window.wails.Events.On("update_tx_history", txHistoryFull => {
if (Object.entries(txHistoryFull).length != 0) {
this.$store.commit({type: 'transaction/updateFullTxHistory', txHistoryFull});
}
});
window.wails.Events.On("tx_in_transit", txFinished => {
this.$store.commit('transaction/setTxFinished', txFinished);
});
window.wails.Events.On("new_transaction", txObject => {
this.$store.commit("transaction/updateTxHistory", txObject);
});
window.wails.Events.On("tx_pending", txStatus => {
this.$store.commit("transaction/updateTxStatus", txStatus);
});
window.wails.Events.On("downloading", (filename, size) => {
if (this.downloading.filename !== filename) {
this.$store.commit('app/setDownloadFileName', filename);
}
this.$store.commit('app/setDownloadFileSize', size);
});
// Login.vue sockets
// window.wails.Events.On("registeredLogin", event => {});
// Dashboard.vue sockets
window.wails.Events.On("token", (amount, available, total) => {
this.$store.commit('wallet/setTokenAmount', amount);
this.$store.commit('wallet/setAvailableBalance', available);
this.$store.commit('wallet/setTotalBalance', total);
});
window.wails.Events.On("blocks", number => {
this.$store.commit('dashboard/setBlocks', number);
});
window.wails.Events.On("totalValue", (currency, value) => {
this.$store.commit('wallet/setCurrency', currency);
this.$store.commit('wallet/setTotalValue', value);
});
window.wails.Events.On("token_counter", count => {
this.$store.commit('dashboard/setTokenCounter', count);
});
window.wails.Events.On("value_counter", valueCount => {
this.$store.commit('dashboard/setValueCounter', valueCount);
});
window.wails.Events.On("block_counter", blockCount => {
this.$store.commit('dashboard/setBlockCounter', blockCount);
});
window.wails.Events.On("chart_counter", pieChartCount => {
this.$store.commit('dashboard/setChartCounter', pieChartCount);
});
window.wails.Events.On("node_stats", (series, labels) => {
if (Object.entries(series).length != 0 &&
Object.entries(labels).length != 0) {
this.$store.commit({type: 'dashboard/setNodeOnlineChart', series, labels});
}
});
window.wails.Events.On("tx_stats", (seriesOne, seriesTwo, labels) => {
if (Object.entries(seriesOne).length != 0 &&
Object.entries(seriesTwo).length != 0 &&
Object.entries(labels).length != 0) {
this.$store.commit({type: 'dashboard/setTransactionStatsChart', seriesOne, seriesTwo, labels});
}
});
window.wails.Events.On("network_stats", (seriesOne, seriesTwo, labels) => {
if (Object.entries(seriesOne).length != 0 &&
Object.entries(seriesTwo).length != 0 &&
Object.entries(labels).length != 0) {
this.$store.commit({type: 'dashboard/setNetworkStatsChart', seriesOne, seriesTwo, labels});
}
});
// Settings.vue sockets
window.wails.Events.On("wallet_keys", address => {
this.$store.commit('wallet/setAddress', address);
});
}
};
</script>
<style lang="scss">
.vue-notifyjs.notifications {
.alert {
z-index: 10000;
font-size: 0.875rem;
}
.alert[data-notify="container"] {
width: 21.875rem;
}
.alert-icon {
margin-left: -0.5em;
margin-top: -0.5em;
}
.list-move {
transition: transform 0.3s, opacity 0.4s;
}
.list-item {
display: inline-block;
margin-right: 0.625em;
}
.list-enter-active {
transition: transform 0.2s ease-in, opacity 0.4s ease-in;
}
.list-leave-active {
transition: transform 1s ease-out, opacity 0.4s ease-out;
}
.list-enter {
opacity: 0;
transform: scale(1.1);
}
.list-leave-to {
opacity: 0;
transform: scale(1.2, 0.7);
}
}
</style>

1081
gui_wails_learn/frontend/src/assets/css/themify-icons.css

File diff suppressed because it is too large

202
gui_wails_learn/frontend/src/assets/fonts/LICENSE.txt

@ -1,202 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

BIN
gui_wails_learn/frontend/src/assets/fonts/fa-brands-400.eot

Binary file not shown.

3451
gui_wails_learn/frontend/src/assets/fonts/fa-brands-400.svg

File diff suppressed because it is too large

Before

Width:  |  Height:  |  Size: 676 KiB

BIN
gui_wails_learn/frontend/src/assets/fonts/fa-brands-400.ttf

Binary file not shown.

BIN
gui_wails_learn/frontend/src/assets/fonts/fa-brands-400.woff

Binary file not shown.

BIN
gui_wails_learn/frontend/src/assets/fonts/fa-brands-400.woff2

Binary file not shown.

BIN
gui_wails_learn/frontend/src/assets/fonts/fa-regular-400.eot

Binary file not shown.

803
gui_wails_learn/frontend/src/assets/fonts/fa-regular-400.svg

@ -1,803 +0,0 @@
<?xml version="1.0" standalone="no"?>
<!--
Font Awesome Free 5.10.1 by @fontawesome - https://fontawesome.com
License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
-->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1">
<metadata>
Created by FontForge 20190112 at Fri Aug 2 14:42:17 2019
By Robert Madole
Copyright (c) Font Awesome
</metadata>
<defs>
<font id="FontAwesome5Free-Regular" horiz-adv-x="512" >
<font-face
font-family="Font Awesome 5 Free Regular"
font-weight="400"
font-stretch="normal"
units-per-em="512"
panose-1="2 0 5 3 0 0 0 0 0 0"
ascent="448"
descent="-64"
bbox="-0.0663408 -64.0662 640.01 448.1"
underline-thickness="25"
underline-position="-51"
unicode-range="U+0020-F5C8"
/>
<missing-glyph />
<glyph glyph-name="heart" unicode="&#xf004;"
d="M458.4 383.7c75.2998 -63.4004 64.0996 -166.601 10.5996 -221.3l-175.4 -178.7c-10 -10.2002 -23.2998 -15.7998 -37.5996 -15.7998c-14.2002 0 -27.5996 5.69922 -37.5996 15.8994l-175.4 178.7c-53.5996 54.7002 -64.5996 157.9 10.5996 221.2
c57.8008 48.7002 147.101 41.2998 202.4 -15c55.2998 56.2998 144.6 63.5996 202.4 15zM434.8 196.2c36.2002 36.8994 43.7998 107.7 -7.2998 150.8c-38.7002 32.5996 -98.7002 27.9004 -136.5 -10.5996l-35 -35.7002l-35 35.7002
c-37.5996 38.2998 -97.5996 43.1992 -136.5 10.5c-51.2002 -43.1006 -43.7998 -113.5 -7.2998 -150.7l175.399 -178.7c2.40039 -2.40039 4.40039 -2.40039 6.80078 0z" />
<glyph glyph-name="star" unicode="&#xf005;" horiz-adv-x="576"
d="M528.1 276.5c26.2002 -3.7998 36.7002 -36.0996 17.7002 -54.5996l-105.7 -103l25 -145.5c4.5 -26.3008 -23.1992 -45.9004 -46.3994 -33.7002l-130.7 68.7002l-130.7 -68.7002c-23.2002 -12.2998 -50.8994 7.39941 -46.3994 33.7002l25 145.5l-105.7 103
c-19 18.5 -8.5 50.7998 17.7002 54.5996l146.1 21.2998l65.2998 132.4c11.7998 23.8994 45.7002 23.5996 57.4004 0l65.2998 -132.4zM388.6 135.7l100.601 98l-139 20.2002l-62.2002 126l-62.2002 -126l-139 -20.2002l100.601 -98l-23.7002 -138.4l124.3 65.2998
l124.3 -65.2998z" />
<glyph glyph-name="user" unicode="&#xf007;" horiz-adv-x="448"
d="M313.6 144c74.2002 0 134.4 -60.2002 134.4 -134.4v-25.5996c0 -26.5 -21.5 -48 -48 -48h-352c-26.5 0 -48 21.5 -48 48v25.5996c0 74.2002 60.2002 134.4 134.4 134.4c28.7998 0 42.5 -16 89.5996 -16s60.9004 16 89.5996 16zM400 -16v25.5996
c0 47.6006 -38.7998 86.4004 -86.4004 86.4004c-14.6992 0 -37.8994 -16 -89.5996 -16c-51.2998 0 -75 16 -89.5996 16c-47.6006 0 -86.4004 -38.7998 -86.4004 -86.4004v-25.5996h352zM224 160c-79.5 0 -144 64.5 -144 144s64.5 144 144 144s144 -64.5 144 -144
s-64.5 -144 -144 -144zM224 400c-52.9004 0 -96 -43.0996 -96 -96s43.0996 -96 96 -96s96 43.0996 96 96s-43.0996 96 -96 96z" />
<glyph glyph-name="clock" unicode="&#xf017;"
d="M256 440c137 0 248 -111 248 -248s-111 -248 -248 -248s-248 111 -248 248s111 248 248 248zM256 -8c110.5 0 200 89.5 200 200s-89.5 200 -200 200s-200 -89.5 -200 -200s89.5 -200 200 -200zM317.8 96.4004l-84.8994 61.6992
c-3.10059 2.30078 -4.90039 5.90039 -4.90039 9.7002v164.2c0 6.59961 5.40039 12 12 12h32c6.59961 0 12 -5.40039 12 -12v-141.7l66.7998 -48.5996c5.40039 -3.90039 6.5 -11.4004 2.60059 -16.7998l-18.8008 -25.9004c-3.89941 -5.2998 -11.3994 -6.5 -16.7998 -2.59961z
" />
<glyph glyph-name="list-alt" unicode="&#xf022;"
d="M464 416c26.5098 0 48 -21.4902 48 -48v-352c0 -26.5098 -21.4902 -48 -48 -48h-416c-26.5098 0 -48 21.4902 -48 48v352c0 26.5098 21.4902 48 48 48h416zM458 16c3.31152 0 6 2.68848 6 6v340c0 3.31152 -2.68848 6 -6 6h-404c-3.31152 0 -6 -2.68848 -6 -6v-340
c0 -3.31152 2.68848 -6 6 -6h404zM416 108v-24c0 -6.62695 -5.37305 -12 -12 -12h-200c-6.62695 0 -12 5.37305 -12 12v24c0 6.62695 5.37305 12 12 12h200c6.62695 0 12 -5.37305 12 -12zM416 204v-24c0 -6.62695 -5.37305 -12 -12 -12h-200c-6.62695 0 -12 5.37305 -12 12
v24c0 6.62695 5.37305 12 12 12h200c6.62695 0 12 -5.37305 12 -12zM416 300v-24c0 -6.62695 -5.37305 -12 -12 -12h-200c-6.62695 0 -12 5.37305 -12 12v24c0 6.62695 5.37305 12 12 12h200c6.62695 0 12 -5.37305 12 -12zM164 288c0 -19.8818 -16.1182 -36 -36 -36
s-36 16.1182 -36 36s16.1182 36 36 36s36 -16.1182 36 -36zM164 192c0 -19.8818 -16.1182 -36 -36 -36s-36 16.1182 -36 36s16.1182 36 36 36s36 -16.1182 36 -36zM164 96c0 -19.8818 -16.1182 -36 -36 -36s-36 16.1182 -36 36s16.1182 36 36 36s36 -16.1182 36 -36z" />
<glyph glyph-name="flag" unicode="&#xf024;"
d="M336.174 368c35.4668 0 73.0195 12.6914 108.922 28.1797c31.6406 13.6514 66.9043 -9.65723 66.9043 -44.1162v-239.919c0 -16.1953 -8.1543 -31.3057 -21.7129 -40.1631c-26.5762 -17.3643 -70.0693 -39.9814 -128.548 -39.9814c-68.6084 0 -112.781 32 -161.913 32
c-56.5674 0 -89.957 -11.2803 -127.826 -28.5566v-83.4434c0 -8.83691 -7.16309 -16 -16 -16h-16c-8.83691 0 -16 7.16309 -16 16v406.438c-14.3428 8.2998 -24 23.7979 -24 41.5615c0 27.5693 23.2422 49.71 51.2012 47.8965
c22.9658 -1.49023 41.8662 -19.4717 44.4805 -42.3379c0.177734 -1.52441 0.321289 -4.00781 0.321289 -5.54199c0 -4.30176 -1.10352 -11.1035 -2.46289 -15.1846c22.418 8.68555 49.4199 15.168 80.7207 15.168c68.6084 0 112.781 -32 161.913 -32zM464 112v240
c-31.5059 -14.6338 -84.5547 -32 -127.826 -32c-59.9111 0 -101.968 32 -161.913 32c-41.4365 0 -80.4766 -16.5879 -102.261 -32v-232c31.4473 14.5967 84.4648 24 127.826 24c59.9111 0 101.968 -32 161.913 -32c41.4365 0 80.4775 16.5879 102.261 32z" />
<glyph glyph-name="bookmark" unicode="&#xf02e;" horiz-adv-x="384"
d="M336 448c26.5098 0 48 -21.4902 48 -48v-464l-192 112l-192 -112v464c0 26.5098 21.4902 48 48 48h288zM336 19.5703v374.434c0 3.31348 -2.68555 5.99609 -6 5.99609h-276c-3.31152 0 -6 -2.68848 -6 -6v-374.43l144 84z" />
<glyph glyph-name="image" unicode="&#xf03e;"
d="M464 384c26.5098 0 48 -21.4902 48 -48v-288c0 -26.5098 -21.4902 -48 -48 -48h-416c-26.5098 0 -48 21.4902 -48 48v288c0 26.5098 21.4902 48 48 48h416zM458 48c3.31152 0 6 2.68848 6 6v276c0 3.31152 -2.68848 6 -6 6h-404c-3.31152 0 -6 -2.68848 -6 -6v-276
c0 -3.31152 2.68848 -6 6 -6h404zM128 296c22.0908 0 40 -17.9092 40 -40s-17.9092 -40 -40 -40s-40 17.9092 -40 40s17.9092 40 40 40zM96 96v48l39.5137 39.5146c4.6875 4.68652 12.2852 4.68652 16.9717 0l39.5146 -39.5146l119.514 119.515
c4.6875 4.68652 12.2852 4.68652 16.9717 0l87.5146 -87.5146v-80h-320z" />
<glyph glyph-name="edit" unicode="&#xf044;" horiz-adv-x="576"
d="M402.3 103.1l32 32c5 5 13.7002 1.5 13.7002 -5.69922v-145.4c0 -26.5 -21.5 -48 -48 -48h-352c-26.5 0 -48 21.5 -48 48v352c0 26.5 21.5 48 48 48h273.5c7.09961 0 10.7002 -8.59961 5.7002 -13.7002l-32 -32c-1.5 -1.5 -3.5 -2.2998 -5.7002 -2.2998h-241.5v-352h352
v113.5c0 2.09961 0.799805 4.09961 2.2998 5.59961zM558.9 304.9l-262.601 -262.601l-90.3994 -10c-26.2002 -2.89941 -48.5 19.2002 -45.6006 45.6006l10 90.3994l262.601 262.601c22.8994 22.8994 59.8994 22.8994 82.6992 0l43.2002 -43.2002
c22.9004 -22.9004 22.9004 -60 0.100586 -82.7998zM460.1 274l-58.0996 58.0996l-185.8 -185.899l-7.2998 -65.2998l65.2998 7.2998zM524.9 353.7l-43.2002 43.2002c-4.10059 4.09961 -10.7998 4.09961 -14.7998 0l-30.9004 -30.9004l58.0996 -58.0996l30.9004 30.8994
c4 4.2002 4 10.7998 -0.0996094 14.9004z" />
<glyph glyph-name="times-circle" unicode="&#xf057;"
d="M256 440c137 0 248 -111 248 -248s-111 -248 -248 -248s-248 111 -248 248s111 248 248 248zM256 -8c110.5 0 200 89.5 200 200s-89.5 200 -200 200s-200 -89.5 -200 -200s89.5 -200 200 -200zM357.8 254.2l-62.2002 -62.2002l62.2002 -62.2002
c4.7002 -4.7002 4.7002 -12.2998 0 -17l-22.5996 -22.5996c-4.7002 -4.7002 -12.2998 -4.7002 -17 0l-62.2002 62.2002l-62.2002 -62.2002c-4.7002 -4.7002 -12.2998 -4.7002 -17 0l-22.5996 22.5996c-4.7002 4.7002 -4.7002 12.2998 0 17l62.2002 62.2002l-62.2002 62.2002
c-4.7002 4.7002 -4.7002 12.2998 0 17l22.5996 22.5996c4.7002 4.7002 12.2998 4.7002 17 0l62.2002 -62.2002l62.2002 62.2002c4.7002 4.7002 12.2998 4.7002 17 0l22.5996 -22.5996c4.7002 -4.7002 4.7002 -12.2998 0 -17z" />
<glyph glyph-name="check-circle" unicode="&#xf058;"
d="M256 440c136.967 0 248 -111.033 248 -248s-111.033 -248 -248 -248s-248 111.033 -248 248s111.033 248 248 248zM256 392c-110.549 0 -200 -89.4678 -200 -200c0 -110.549 89.4678 -200 200 -200c110.549 0 200 89.4678 200 200c0 110.549 -89.4678 200 -200 200z
M396.204 261.733c4.66699 -4.70508 4.63672 -12.3037 -0.0673828 -16.9717l-172.589 -171.204c-4.70508 -4.66797 -12.3027 -4.63672 -16.9697 0.0683594l-90.7812 91.5156c-4.66797 4.70605 -4.63672 12.3047 0.0683594 16.9717l22.7188 22.5361
c4.70508 4.66699 12.3027 4.63574 16.9697 -0.0693359l59.792 -60.2773l141.353 140.216c4.70508 4.66797 12.3027 4.6377 16.9697 -0.0673828z" />
<glyph glyph-name="question-circle" unicode="&#xf059;"
d="M256 440c136.957 0 248 -111.083 248 -248c0 -136.997 -111.043 -248 -248 -248s-248 111.003 -248 248c0 136.917 111.043 248 248 248zM256 -8c110.569 0 200 89.4697 200 200c0 110.529 -89.5088 200 -200 200c-110.528 0 -200 -89.5049 -200 -200
c0 -110.569 89.4678 -200 200 -200zM363.244 247.2c0 -67.0518 -72.4209 -68.084 -72.4209 -92.8633v-6.33691c0 -6.62695 -5.37305 -12 -12 -12h-45.6475c-6.62695 0 -12 5.37305 -12 12v8.65918c0 35.7451 27.1006 50.0342 47.5791 61.5156
c17.5615 9.84473 28.3242 16.541 28.3242 29.5791c0 17.2461 -21.999 28.6934 -39.7842 28.6934c-23.1885 0 -33.8936 -10.9775 -48.9424 -29.9697c-4.05664 -5.11914 -11.46 -6.07031 -16.666 -2.12402l-27.8232 21.0986
c-5.10742 3.87207 -6.25098 11.0654 -2.64453 16.3633c23.627 34.6934 53.7217 54.1846 100.575 54.1846c49.0713 0 101.45 -38.3037 101.45 -88.7998zM298 80c0 -23.1592 -18.8408 -42 -42 -42s-42 18.8408 -42 42s18.8408 42 42 42s42 -18.8408 42 -42z" />
<glyph glyph-name="eye" unicode="&#xf06e;" horiz-adv-x="576"
d="M288 304c0.0927734 0 0.244141 0.000976562 0.336914 0.000976562c61.6641 0 111.71 -50.0469 111.71 -111.711c0 -61.6631 -50.0459 -111.71 -111.71 -111.71s-111.71 50.0469 -111.71 111.71c0 8.71289 1.95898 22.5781 4.37305 30.9502
c6.93066 -3.94141 19.0273 -7.18457 27 -7.24023c30.9121 0 56 25.0879 56 56c-0.0556641 7.97266 -3.29883 20.0693 -7.24023 27c8.42383 2.62207 22.4189 4.8623 31.2402 5zM572.52 206.6c1.9209 -3.79883 3.47949 -10.3379 3.47949 -14.5947
s-1.55859 -10.7959 -3.47949 -14.5947c-54.1992 -105.771 -161.59 -177.41 -284.52 -177.41s-230.29 71.5898 -284.52 177.4c-1.9209 3.79883 -3.47949 10.3379 -3.47949 14.5947s1.55859 10.7959 3.47949 14.5947c54.1992 105.771 161.59 177.41 284.52 177.41
s230.29 -71.5898 284.52 -177.4zM288 48c98.6602 0 189.1 55 237.93 144c-48.8398 89 -139.27 144 -237.93 144s-189.09 -55 -237.93 -144c48.8398 -89 139.279 -144 237.93 -144z" />
<glyph glyph-name="eye-slash" unicode="&#xf070;" horiz-adv-x="640"
d="M634 -23c3.31738 -2.65137 6.00977 -8.25098 6.00977 -12.498c0 -3.10449 -1.57715 -7.58984 -3.51953 -10.0117l-10 -12.4902c-2.65234 -3.31152 -8.24707 -6 -12.4902 -6c-3.09961 0 -7.58008 1.57227 -10 3.50977l-598 467.49
c-3.31738 2.65137 -6.00977 8.25098 -6.00977 12.498c0 3.10449 1.57715 7.58984 3.51953 10.0117l10 12.4902c2.65234 3.31152 8.24707 6 12.4902 6c3.09961 0 7.58008 -1.57227 10 -3.50977zM296.79 301.53c6.33496 1.35059 16.7324 2.45801 23.21 2.46973
c60.4805 0 109.36 -47.9102 111.58 -107.85zM343.21 82.46c-6.33496 -1.34375 -16.7334 -2.44629 -23.21 -2.45996c-60.4697 0 -109.35 47.9102 -111.58 107.84zM320 336c-19.8799 0 -39.2803 -2.7998 -58.2197 -7.09961l-46.4102 36.29
c32.9199 11.8096 67.9297 18.8096 104.63 18.8096c122.93 0 230.29 -71.5898 284.57 -177.4c1.91992 -3.79883 3.47949 -10.3379 3.47949 -14.5947s-1.55957 -10.7959 -3.47949 -14.5947c-11.7197 -22.7598 -35.4189 -56.4092 -52.9004 -75.1104l-37.7402 29.5
c14.333 15.0156 34.0449 41.9854 44 60.2002c-48.8398 89 -139.279 144 -237.93 144zM320 48c19.8896 0 39.2803 2.7998 58.2197 7.08984l46.4102 -36.2803c-32.9199 -11.7598 -67.9297 -18.8096 -104.63 -18.8096c-122.92 0 -230.28 71.5898 -284.51 177.4
c-1.9209 3.79883 -3.47949 10.3379 -3.47949 14.5947s1.55859 10.7959 3.47949 14.5947c11.7168 22.7568 35.4111 56.4014 52.8896 75.1006l37.7402 -29.5c-14.3467 -15.0107 -34.0811 -41.9756 -44.0498 -60.1904c48.8496 -89 139.279 -144 237.93 -144z" />
<glyph glyph-name="calendar-alt" unicode="&#xf073;" horiz-adv-x="448"
d="M148 160h-40c-6.59961 0 -12 5.40039 -12 12v40c0 6.59961 5.40039 12 12 12h40c6.59961 0 12 -5.40039 12 -12v-40c0 -6.59961 -5.40039 -12 -12 -12zM256 172c0 -6.59961 -5.40039 -12 -12 -12h-40c-6.59961 0 -12 5.40039 -12 12v40c0 6.59961 5.40039 12 12 12h40
c6.59961 0 12 -5.40039 12 -12v-40zM352 172c0 -6.59961 -5.40039 -12 -12 -12h-40c-6.59961 0 -12 5.40039 -12 12v40c0 6.59961 5.40039 12 12 12h40c6.59961 0 12 -5.40039 12 -12v-40zM256 76c0 -6.59961 -5.40039 -12 -12 -12h-40c-6.59961 0 -12 5.40039 -12 12v40
c0 6.59961 5.40039 12 12 12h40c6.59961 0 12 -5.40039 12 -12v-40zM160 76c0 -6.59961 -5.40039 -12 -12 -12h-40c-6.59961 0 -12 5.40039 -12 12v40c0 6.59961 5.40039 12 12 12h40c6.59961 0 12 -5.40039 12 -12v-40zM352 76c0 -6.59961 -5.40039 -12 -12 -12h-40
c-6.59961 0 -12 5.40039 -12 12v40c0 6.59961 5.40039 12 12 12h40c6.59961 0 12 -5.40039 12 -12v-40zM448 336v-352c0 -26.5 -21.5 -48 -48 -48h-352c-26.5 0 -48 21.5 -48 48v352c0 26.5 21.5 48 48 48h48v52c0 6.59961 5.40039 12 12 12h40
c6.59961 0 12 -5.40039 12 -12v-52h128v52c0 6.59961 5.40039 12 12 12h40c6.59961 0 12 -5.40039 12 -12v-52h48c26.5 0 48 -21.5 48 -48zM400 -10v298h-352v-298c0 -3.2998 2.7002 -6 6 -6h340c3.2998 0 6 2.7002 6 6z" />
<glyph glyph-name="comment" unicode="&#xf075;"
d="M256 416c141.4 0 256 -93.0996 256 -208s-114.6 -208 -256 -208c-32.7998 0 -64 5.2002 -92.9004 14.2998c-29.0996 -20.5996 -77.5996 -46.2998 -139.1 -46.2998c-9.59961 0 -18.2998 5.7002 -22.0996 14.5c-3.80078 8.7998 -2 19 4.59961 26
c0.5 0.400391 31.5 33.7998 46.4004 73.2002c-33 35.0996 -52.9004 78.7002 -52.9004 126.3c0 114.9 114.6 208 256 208zM256 48c114.7 0 208 71.7998 208 160s-93.2998 160 -208 160s-208 -71.7998 -208 -160c0 -42.2002 21.7002 -74.0996 39.7998 -93.4004
l20.6006 -21.7998l-10.6006 -28.0996c-5.5 -14.5 -12.5996 -28.1006 -19.8994 -40.2002c23.5996 7.59961 43.1992 18.9004 57.5 29l19.5 13.7998l22.6992 -7.2002c25.3008 -8 51.7002 -12.0996 78.4004 -12.0996z" />
<glyph glyph-name="folder" unicode="&#xf07b;"
d="M464 320c26.5098 0 48 -21.4902 48 -48v-224c0 -26.5098 -21.4902 -48 -48 -48h-416c-26.5098 0 -48 21.4902 -48 48v288c0 26.5098 21.4902 48 48 48h146.74c8.49023 0 16.6299 -3.37012 22.6299 -9.37012l54.6299 -54.6299h192zM464 48v224h-198.62
c-8.49023 0 -16.6299 3.37012 -22.6299 9.37012l-54.6299 54.6299h-140.12v-288h416z" />
<glyph glyph-name="folder-open" unicode="&#xf07c;" horiz-adv-x="576"
d="M527.9 224c37.6992 0 60.6992 -41.5 40.6992 -73.4004l-79.8994 -128c-8.7998 -14.0996 -24.2002 -22.5996 -40.7002 -22.5996h-400c-26.5 0 -48 21.5 -48 48v288c0 26.5 21.5 48 48 48h160l64 -64h160c26.5 0 48 -21.5 48 -48v-48h47.9004zM48 330v-233.4l62.9004 104.2
c8.69922 14.4004 24.2998 23.2002 41.0996 23.2002h280v42c0 3.2998 -2.7002 6 -6 6h-173.9l-64 64h-134.1c-3.2998 0 -6 -2.7002 -6 -6zM448 48l80 128h-378.8l-77.2002 -128h376z" />
<glyph glyph-name="chart-bar" unicode="&#xf080;"
d="M396.8 96c-6.39941 0 -12.7998 6.40039 -12.7998 12.7998v230.4c0 6.39941 6.40039 12.7998 12.7998 12.7998h22.4004c6.39941 0 12.7998 -6.40039 12.7998 -12.7998v-230.4c0 -6.39941 -6.40039 -12.7998 -12.7998 -12.7998h-22.4004zM204.8 96
c-6.39941 0 -12.7998 6.40039 -12.7998 12.7998v198.4c0 6.39941 6.40039 12.7998 12.7998 12.7998h22.4004c6.39941 0 12.7998 -6.40039 12.7998 -12.7998v-198.4c0 -6.39941 -6.40039 -12.7998 -12.7998 -12.7998h-22.4004zM300.8 96
c-6.39941 0 -12.7998 6.40039 -12.7998 12.7998v134.4c0 6.39941 6.40039 12.7998 12.7998 12.7998h22.4004c6.39941 0 12.7998 -6.40039 12.7998 -12.7998v-134.4c0 -6.39941 -6.40039 -12.7998 -12.7998 -12.7998h-22.4004zM496 48c8.83984 0 16 -7.16016 16 -16v-16
c0 -8.83984 -7.16016 -16 -16 -16h-464c-17.6699 0 -32 14.3301 -32 32v336c0 8.83984 7.16016 16 16 16h16c8.83984 0 16 -7.16016 16 -16v-320h448zM108.8 96c-6.39941 0 -12.7998 6.40039 -12.7998 12.7998v70.4004c0 6.39941 6.40039 12.7998 12.7998 12.7998h22.4004
c6.39941 0 12.7998 -6.40039 12.7998 -12.7998v-70.4004c0 -6.39941 -6.40039 -12.7998 -12.7998 -12.7998h-22.4004z" />
<glyph glyph-name="comments" unicode="&#xf086;" horiz-adv-x="576"
d="M532 61.7998c15.2998 -30.7002 37.4004 -54.5 37.7998 -54.7998c6.2998 -6.7002 8 -16.5 4.40039 -25c-3.7002 -8.5 -12 -14 -21.2002 -14c-53.5996 0 -96.7002 20.2998 -125.2 38.7998c-19 -4.39941 -39 -6.7998 -59.7998 -6.7998
c-86.2002 0 -159.9 40.4004 -191.3 97.7998c-9.7002 1.2002 -19.2002 2.7998 -28.4004 4.90039c-28.5 -18.6006 -71.7002 -38.7998 -125.2 -38.7998c-9.19922 0 -17.5996 5.5 -21.1992 14c-3.7002 8.5 -1.90039 18.2998 4.39941 25
c0.400391 0.399414 22.4004 24.1992 37.7002 54.8994c-27.5 27.2002 -44 61.2002 -44 98.2002c0 88.4004 93.0996 160 208 160c86.2998 0 160.3 -40.5 191.8 -98.0996c99.7002 -11.8008 176.2 -77.9004 176.2 -157.9c0 -37.0996 -16.5 -71.0996 -44 -98.2002zM139.2 154.1
l19.7998 -4.5c16 -3.69922 32.5 -5.59961 49 -5.59961c86.7002 0 160 51.2998 160 112s-73.2998 112 -160 112s-160 -51.2998 -160 -112c0 -28.7002 16.2002 -50.5996 29.7002 -64l24.7998 -24.5l-15.5 -31.0996c-2.59961 -5.10059 -5.2998 -10.1006 -8 -14.8008
c14.5996 5.10059 29 12.3008 43.0996 21.4004zM498.3 96c13.5 13.4004 29.7002 35.2998 29.7002 64c0 49.2002 -48.2998 91.5 -112.7 106c0.299805 -3.2998 0.700195 -6.59961 0.700195 -10c0 -80.9004 -78 -147.5 -179.3 -158.3
c29.0996 -29.6006 77.2998 -49.7002 131.3 -49.7002c16.5 0 33 1.90039 49 5.59961l19.9004 4.60059l17.0996 -11.1006c14.0996 -9.09961 28.5 -16.2998 43.0996 -21.3994c-2.69922 4.7002 -5.39941 9.7002 -8 14.7998l-15.5 31.0996z" />
<glyph glyph-name="star-half" unicode="&#xf089;" horiz-adv-x="576"
d="M288 62.7002v-54.2998l-130.7 -68.6006c-23.3994 -12.2998 -50.8994 7.60059 -46.3994 33.7002l25 145.5l-105.7 103c-19 18.5 -8.5 50.7998 17.7002 54.5996l146.1 21.2002l65.2998 132.4c5.90039 11.8994 17.2998 17.7998 28.7002 17.7998v-68.0996l-62.2002 -126
l-139 -20.2002l100.601 -98l-23.7002 -138.4z" />
<glyph glyph-name="lemon" unicode="&#xf094;"
d="M484.112 420.111c28.1221 -28.123 35.9434 -68.0039 19.0215 -97.0547c-23.0576 -39.584 50.1436 -163.384 -82.3311 -295.86c-132.301 -132.298 -256.435 -59.3594 -295.857 -82.3291c-29.0459 -16.917 -68.9219 -9.11426 -97.0576 19.0205
c-28.1221 28.1221 -35.9434 68.0029 -19.0215 97.0547c23.0566 39.5859 -50.1436 163.386 82.3301 295.86c132.308 132.309 256.407 59.3496 295.862 82.332c29.0498 16.9219 68.9307 9.09863 97.0537 -19.0234zM461.707 347.217
c13.5166 23.2031 -27.7578 63.7314 -50.4883 50.4912c-66.6025 -38.7939 -165.646 45.5898 -286.081 -74.8457c-120.444 -120.445 -36.0449 -219.472 -74.8447 -286.08c-13.542 -23.2471 27.8145 -63.6953 50.4932 -50.4883
c66.6006 38.7949 165.636 -45.5996 286.076 74.8428c120.444 120.445 36.0449 219.472 74.8447 286.08zM291.846 338.481c1.37012 -10.96 -6.40332 -20.957 -17.3643 -22.3271c-54.8467 -6.85547 -135.779 -87.7871 -142.636 -142.636
c-1.37305 -10.9883 -11.3984 -18.7334 -22.3262 -17.3643c-10.9609 1.37012 -18.7344 11.3652 -17.3643 22.3262c9.16211 73.2852 104.167 168.215 177.364 177.364c10.9531 1.36816 20.9561 -6.40234 22.3262 -17.3633z" />
<glyph glyph-name="credit-card" unicode="&#xf09d;" horiz-adv-x="576"
d="M527.9 416c26.5996 0 48.0996 -21.5 48.0996 -48v-352c0 -26.5 -21.5 -48 -48.0996 -48h-479.801c-26.5996 0 -48.0996 21.5 -48.0996 48v352c0 26.5 21.5 48 48.0996 48h479.801zM54.0996 368c-3.2998 0 -6 -2.7002 -6 -6v-42h479.801v42c0 3.2998 -2.7002 6 -6 6
h-467.801zM521.9 16c3.2998 0 6 2.7002 6 6v170h-479.801v-170c0 -3.2998 2.7002 -6 6 -6h467.801zM192 116v-40c0 -6.59961 -5.40039 -12 -12 -12h-72c-6.59961 0 -12 5.40039 -12 12v40c0 6.59961 5.40039 12 12 12h72c6.59961 0 12 -5.40039 12 -12zM384 116v-40
c0 -6.59961 -5.40039 -12 -12 -12h-136c-6.59961 0 -12 5.40039 -12 12v40c0 6.59961 5.40039 12 12 12h136c6.59961 0 12 -5.40039 12 -12z" />
<glyph glyph-name="hdd" unicode="&#xf0a0;" horiz-adv-x="576"
d="M567.403 212.358c5.59668 -8.04688 8.59668 -17.6113 8.59668 -27.4121v-136.946c0 -26.5098 -21.4902 -48 -48 -48h-480c-26.5098 0 -48 21.4902 -48 48v136.946c0 8.30957 3.85156 20.5898 8.59668 27.4121l105.08 151.053
c7.90625 11.3652 25.5596 20.5889 39.4033 20.5889h0.000976562h269.838h0.000976562c13.8438 0 31.4971 -9.22363 39.4033 -20.5889zM153.081 336l-77.9131 -112h425.664l-77.9131 112h-269.838zM528 48v128h-480v-128h480zM496 112c0 -17.6729 -14.3271 -32 -32 -32
s-32 14.3271 -32 32s14.3271 32 32 32s32 -14.3271 32 -32zM400 112c0 -17.6729 -14.3271 -32 -32 -32s-32 14.3271 -32 32s14.3271 32 32 32s32 -14.3271 32 -32z" />
<glyph glyph-name="hand-point-right" unicode="&#xf0a4;"
d="M428.8 310.4c45.0996 0 83.2002 -38.1016 83.2002 -83.2002c0 -45.6162 -37.7646 -83.2002 -83.2002 -83.2002h-35.6475c-1.41602 -6.36719 -4.96875 -16.252 -7.92969 -22.0645c2.50586 -22.0059 -3.50293 -44.9775 -15.9844 -62.791
c-1.14062 -52.4863 -37.3984 -91.1445 -99.9404 -91.1445h-21.2988c-60.0635 0 -98.5117 40 -127.2 40h-2.67871c-5.74707 -4.95215 -13.5361 -8 -22.1201 -8h-64c-17.6729 0 -32 12.8936 -32 28.7998v230.4c0 15.9062 14.3271 28.7998 32 28.7998h64.001
c8.58398 0 16.373 -3.04785 22.1201 -8h2.67871c6.96387 0 14.8623 6.19336 30.1816 23.6689l0.128906 0.148438l0.130859 0.145508c8.85645 9.93652 18.1162 20.8398 25.8506 33.2529c18.7051 30.2471 30.3936 78.7842 75.707 78.7842c56.9277 0 92 -35.2861 92 -83.2002
v-0.0839844c0 -6.21777 -0.974609 -16.2148 -2.17578 -22.3154h86.1768zM428.8 192c18.9756 0 35.2002 16.2246 35.2002 35.2002c0 18.7002 -16.7754 35.2002 -35.2002 35.2002h-158.399c0 17.3242 26.3994 35.1992 26.3994 70.3994c0 26.4004 -20.625 35.2002 -44 35.2002
c-8.79395 0 -20.4443 -32.7119 -34.9258 -56.0996c-9.07422 -14.5752 -19.5244 -27.2256 -30.7988 -39.875c-16.1094 -18.374 -33.8359 -36.6328 -59.0752 -39.5967v-176.753c42.79 -3.7627 74.5088 -39.6758 120 -39.6758h21.2988
c40.5244 0 57.124 22.1973 50.6006 61.3252c14.6113 8.00098 24.1514 33.9785 12.9248 53.625c19.3652 18.2246 17.7871 46.3809 4.9502 61.0498h91.0254zM88 64c0 13.2549 -10.7451 24 -24 24s-24 -10.7451 -24 -24s10.7451 -24 24 -24s24 10.7451 24 24z" />
<glyph glyph-name="hand-point-left" unicode="&#xf0a5;"
d="M0 227.2c0 45.0986 38.1006 83.2002 83.2002 83.2002h86.1758c-1.3623 6.91016 -2.17578 14.374 -2.17578 22.3994c0 47.9141 35.0723 83.2002 92 83.2002c45.3135 0 57.002 -48.5371 75.7061 -78.7852c7.73438 -12.4121 16.9951 -23.3154 25.8506 -33.2529
l0.130859 -0.145508l0.128906 -0.148438c15.3213 -17.4746 23.2197 -23.668 30.1836 -23.668h2.67871c5.74707 4.95215 13.5361 8 22.1201 8h64c17.6729 0 32 -12.8936 32 -28.7998v-230.4c0 -15.9062 -14.3271 -28.7998 -32 -28.7998h-64
c-8.58398 0 -16.373 3.04785 -22.1201 8h-2.67871c-28.6885 0 -67.1367 -40 -127.2 -40h-21.2988c-62.542 0 -98.8008 38.6582 -99.9404 91.1445c-12.4814 17.8135 -18.4922 40.7852 -15.9844 62.791c-2.96094 5.8125 -6.51367 15.6973 -7.92969 22.0645h-35.6465
c-45.4355 0 -83.2002 37.584 -83.2002 83.2002zM48 227.2c0 -18.9756 16.2246 -35.2002 35.2002 -35.2002h91.0244c-12.8369 -14.6689 -14.415 -42.8252 4.9502 -61.0498c-11.2256 -19.6465 -1.68652 -45.624 12.9248 -53.625
c-6.52246 -39.1279 10.0771 -61.3252 50.6016 -61.3252h21.2988c45.4912 0 77.21 35.9131 120 39.6768v176.752c-25.2393 2.96289 -42.9658 21.2227 -59.0752 39.5967c-11.2744 12.6494 -21.7246 25.2998 -30.7988 39.875
c-14.4814 23.3877 -26.1318 56.0996 -34.9258 56.0996c-23.375 0 -44 -8.7998 -44 -35.2002c0 -35.2002 26.3994 -53.0752 26.3994 -70.3994h-158.399c-18.4248 0 -35.2002 -16.5 -35.2002 -35.2002zM448 88c-13.2549 0 -24 -10.7451 -24 -24s10.7451 -24 24 -24
s24 10.7451 24 24s-10.7451 24 -24 24z" />
<glyph glyph-name="hand-point-up" unicode="&#xf0a6;" horiz-adv-x="448"
d="M105.6 364.8c0 45.0996 38.1016 83.2002 83.2002 83.2002c45.6162 0 83.2002 -37.7646 83.2002 -83.2002v-35.6465c6.36719 -1.41602 16.252 -4.96875 22.0645 -7.92969c22.0059 2.50684 44.9775 -3.50293 62.791 -15.9844
c52.4863 -1.14062 91.1445 -37.3984 91.1445 -99.9404v-21.2988c0 -60.0635 -40 -98.5117 -40 -127.2v-2.67871c4.95215 -5.74707 8 -13.5361 8 -22.1201v-64c0 -17.6729 -12.8936 -32 -28.7998 -32h-230.4c-15.9062 0 -28.7998 14.3271 -28.7998 32v64
c0 8.58398 3.04785 16.373 8 22.1201v2.67871c0 6.96387 -6.19336 14.8623 -23.6689 30.1816l-0.148438 0.128906l-0.145508 0.130859c-9.93652 8.85645 -20.8398 18.1162 -33.2529 25.8506c-30.2471 18.7051 -78.7842 30.3936 -78.7842 75.707
c0 56.9277 35.2861 92 83.2002 92h0.0839844c6.21777 0 16.2148 -0.974609 22.3154 -2.17578v86.1768zM224 364.8c0 18.9756 -16.2246 35.2002 -35.2002 35.2002c-18.7002 0 -35.2002 -16.7754 -35.2002 -35.2002v-158.399c-17.3242 0 -35.1992 26.3994 -70.3994 26.3994
c-26.4004 0 -35.2002 -20.625 -35.2002 -44c0 -8.79395 32.7119 -20.4443 56.0996 -34.9258c14.5752 -9.07422 27.2256 -19.5244 39.875 -30.7988c18.374 -16.1094 36.6328 -33.8359 39.5967 -59.0752h176.753c3.7627 42.79 39.6758 74.5088 39.6758 120v21.2988
c0 40.5244 -22.1973 57.124 -61.3252 50.6006c-8.00098 14.6113 -33.9785 24.1514 -53.625 12.9248c-18.2246 19.3652 -46.3809 17.7871 -61.0498 4.9502v91.0254zM352 24c-13.2549 0 -24 -10.7451 -24 -24s10.7451 -24 24 -24s24 10.7451 24 24s-10.7451 24 -24 24z" />
<glyph glyph-name="hand-point-down" unicode="&#xf0a7;" horiz-adv-x="448"
d="M188.8 -64c-45.0986 0 -83.2002 38.1006 -83.2002 83.2002v86.1758c-6.91016 -1.3623 -14.374 -2.17578 -22.3994 -2.17578c-47.9141 0 -83.2002 35.0723 -83.2002 92c0 45.3135 48.5371 57.002 78.7852 75.707c12.4121 7.73438 23.3154 16.9951 33.2529 25.8506
l0.145508 0.130859l0.148438 0.128906c17.4746 15.3213 23.668 23.2197 23.668 30.1836v2.67871c-4.95215 5.74707 -8 13.5361 -8 22.1201v64c0 17.6729 12.8936 32 28.7998 32h230.4c15.9062 0 28.7998 -14.3271 28.7998 -32v-64.001
c0 -8.58398 -3.04785 -16.373 -8 -22.1201v-2.67871c0 -28.6885 40 -67.1367 40 -127.2v-21.2988c0 -62.542 -38.6582 -98.8008 -91.1445 -99.9404c-17.8135 -12.4814 -40.7852 -18.4922 -62.791 -15.9844c-5.8125 -2.96094 -15.6973 -6.51367 -22.0645 -7.92969v-35.6465
c0 -45.4355 -37.584 -83.2002 -83.2002 -83.2002zM188.8 -16c18.9756 0 35.2002 16.2246 35.2002 35.2002v91.0244c14.6689 -12.8369 42.8252 -14.415 61.0498 4.9502c19.6465 -11.2256 45.624 -1.68652 53.625 12.9248c39.1279 -6.52246 61.3252 10.0771 61.3252 50.6016
v21.2988c0 45.4912 -35.9131 77.21 -39.6768 120h-176.752c-2.96289 -25.2393 -21.2227 -42.9658 -39.5967 -59.0752c-12.6494 -11.2744 -25.2998 -21.7246 -39.875 -30.7988c-23.3877 -14.4814 -56.0996 -26.1318 -56.0996 -34.9258c0 -23.375 8.7998 -44 35.2002 -44
c35.2002 0 53.0752 26.3994 70.3994 26.3994v-158.399c0 -18.4248 16.5 -35.2002 35.2002 -35.2002zM328 384c0 -13.2549 10.7451 -24 24 -24s24 10.7451 24 24s-10.7451 24 -24 24s-24 -10.7451 -24 -24z" />
<glyph glyph-name="copy" unicode="&#xf0c5;" horiz-adv-x="448"
d="M433.941 382.059c7.75977 -7.75977 14.0586 -22.9658 14.0586 -33.9404v-268.118c0 -26.5098 -21.4902 -48 -48 -48h-80v-48c0 -26.5098 -21.4902 -48 -48 -48h-224c-26.5098 0 -48 21.4902 -48 48v320c0 26.5098 21.4902 48 48 48h80v48c0 26.5098 21.4902 48 48 48
h172.118c10.9746 0 26.1807 -6.29883 33.9404 -14.0586zM266 -16c3.31152 0 6 2.68848 6 6v42h-96c-26.5098 0 -48 21.4902 -48 48v224h-74c-3.31152 0 -6 -2.68848 -6 -6v-308c0 -3.31152 2.68848 -6 6 -6h212zM394 80c3.31152 0 6 2.68848 6 6v202h-88
c-13.2549 0 -24 10.7451 -24 24v88h-106c-3.31152 0 -6 -2.68848 -6 -6v-308c0 -3.31152 2.68848 -6 6 -6h212zM400 336v9.63184v0.000976562c0 1.37207 -0.787109 3.27246 -1.75684 4.24219l-48.3682 48.3682c-1.12598 1.125 -2.65234 1.75684 -4.24316 1.75684h-9.63184
v-64h64z" />
<glyph glyph-name="save" unicode="&#xf0c7;" horiz-adv-x="448"
d="M433.941 318.059c7.75977 -7.75977 14.0586 -22.9658 14.0586 -33.9404v-268.118c0 -26.5098 -21.4902 -48 -48 -48h-352c-26.5098 0 -48 21.4902 -48 48v352c0 26.5098 21.4902 48 48 48h268.118c10.9746 0 26.1807 -6.29883 33.9404 -14.0586zM272 368h-128v-80h128v80
zM394 16c3.31152 0 6 2.68848 6 6v259.632v0.000976562c0 1.37207 -0.787109 3.27246 -1.75684 4.24219l-78.2432 78.2432v-100.118c0 -13.2549 -10.7451 -24 -24 -24h-176c-13.2549 0 -24 10.7451 -24 24v104h-42c-3.31152 0 -6 -2.68848 -6 -6v-340
c0 -3.31152 2.68848 -6 6 -6h340zM224 216c48.5234 0 88 -39.4766 88 -88s-39.4766 -88 -88 -88s-88 39.4766 -88 88s39.4766 88 88 88zM224 88c22.0557 0 40 17.9443 40 40s-17.9443 40 -40 40s-40 -17.9443 -40 -40s17.9443 -40 40 -40z" />
<glyph glyph-name="square" unicode="&#xf0c8;" horiz-adv-x="448"
d="M400 416c26.5 0 48 -21.5 48 -48v-352c0 -26.5 -21.5 -48 -48 -48h-352c-26.5 0 -48 21.5 -48 48v352c0 26.5 21.5 48 48 48h352zM394 16c3.2998 0 6 2.7002 6 6v340c0 3.2998 -2.7002 6 -6 6h-340c-3.2998 0 -6 -2.7002 -6 -6v-340c0 -3.2998 2.7002 -6 6 -6h340z" />
<glyph glyph-name="envelope" unicode="&#xf0e0;"
d="M464 384c26.5098 0 48 -21.4902 48 -48v-288c0 -26.5098 -21.4902 -48 -48 -48h-416c-26.5098 0 -48 21.4902 -48 48v288c0 26.5098 21.4902 48 48 48h416zM464 336h-416v-40.8047c22.4248 -18.2627 58.1797 -46.6602 134.587 -106.49
c16.834 -13.2422 50.2051 -45.0762 73.4131 -44.7012c23.2119 -0.371094 56.5723 31.4541 73.4131 44.7012c76.4189 59.8389 112.165 88.2305 134.587 106.49v40.8047zM48 48h416v185.601c-22.915 -18.252 -55.4189 -43.8691 -104.947 -82.6523
c-22.5439 -17.748 -60.3359 -55.1787 -103.053 -54.9473c-42.9277 -0.231445 -81.2051 37.75 -103.062 54.9551c-49.5293 38.7842 -82.0244 64.3945 -104.938 82.6455v-185.602z" />
<glyph glyph-name="lightbulb" unicode="&#xf0eb;" horiz-adv-x="352"
d="M176 368c8.83984 0 16 -7.16016 16 -16s-7.16016 -16 -16 -16c-35.2803 0 -64 -28.7002 -64 -64c0 -8.83984 -7.16016 -16 -16 -16s-16 7.16016 -16 16c0 52.9404 43.0596 96 96 96zM96.0596 -11.1699l-0.0400391 43.1797h159.961l-0.0507812 -43.1797
c-0.00976562 -3.13965 -0.939453 -6.21973 -2.67969 -8.83984l-24.5098 -36.8398c-2.95996 -4.45996 -7.95996 -7.14062 -13.3203 -7.14062h-78.8496c-5.35059 0 -10.3506 2.68066 -13.3203 7.14062l-24.5098 36.8398c-1.75 2.62012 -2.68066 5.68945 -2.68066 8.83984z
M176 448c97.2002 0 176 -78.7998 176 -176c0 -44.3701 -16.4502 -84.8496 -43.5498 -115.79c-16.6406 -18.9795 -42.7402 -58.79 -52.4199 -92.1602v-0.0498047h-48v0.0996094c0.00390625 4.04199 0.999023 10.4482 2.21973 14.3008
c5.67969 17.9893 22.9902 64.8496 62.0996 109.46c20.4102 23.29 31.6504 53.1699 31.6504 84.1396c0 70.5801 -57.4199 128 -128 128c-68.2803 0 -128.15 -54.3604 -127.95 -128c0.0898438 -30.9902 11.0703 -60.71 31.6104 -84.1396
c39.3496 -44.9004 56.5801 -91.8604 62.1699 -109.67c1.42969 -4.56055 2.13965 -9.30078 2.15039 -14.0703v-0.120117h-48v0.0595703c-9.68066 33.3604 -35.7803 73.1709 -52.4209 92.1602c-27.1094 30.9307 -43.5596 71.4102 -43.5596 115.78
c0 93.0303 73.7197 176 176 176z" />
<glyph glyph-name="bell" unicode="&#xf0f3;" horiz-adv-x="448"
d="M439.39 85.71c6 -6.44043 8.66016 -14.1602 8.61035 -21.71c-0.0996094 -16.4004 -12.9805 -32 -32.0996 -32h-383.801c-19.1191 0 -31.9893 15.5996 -32.0996 32c-0.0498047 7.5498 2.61035 15.2598 8.61035 21.71c19.3193 20.7598 55.4697 51.9902 55.4697 154.29
c0 77.7002 54.4795 139.9 127.939 155.16v20.8398c0 17.6699 14.3203 32 31.9805 32s31.9805 -14.3301 31.9805 -32v-20.8398c73.46 -15.2598 127.939 -77.46 127.939 -155.16c0 -102.3 36.1504 -133.53 55.4697 -154.29zM67.5303 80h312.939
c-21.2197 27.96 -44.4199 74.3203 -44.5293 159.42c0 0.200195 0.0595703 0.379883 0.0595703 0.580078c0 61.8604 -50.1396 112 -112 112s-112 -50.1396 -112 -112c0 -0.200195 0.0595703 -0.379883 0.0595703 -0.580078
c-0.109375 -85.0898 -23.3096 -131.45 -44.5293 -159.42zM224 -64c-35.3203 0 -63.9697 28.6504 -63.9697 64h127.939c0 -35.3496 -28.6494 -64 -63.9697 -64z" />
<glyph glyph-name="hospital" unicode="&#xf0f8;" horiz-adv-x="448"
d="M128 204v40c0 6.62695 5.37305 12 12 12h40c6.62695 0 12 -5.37305 12 -12v-40c0 -6.62695 -5.37305 -12 -12 -12h-40c-6.62695 0 -12 5.37305 -12 12zM268 192c-6.62695 0 -12 5.37305 -12 12v40c0 6.62695 5.37305 12 12 12h40c6.62695 0 12 -5.37305 12 -12v-40
c0 -6.62695 -5.37305 -12 -12 -12h-40zM192 108c0 -6.62695 -5.37305 -12 -12 -12h-40c-6.62695 0 -12 5.37305 -12 12v40c0 6.62695 5.37305 12 12 12h40c6.62695 0 12 -5.37305 12 -12v-40zM268 96c-6.62695 0 -12 5.37305 -12 12v40c0 6.62695 5.37305 12 12 12h40
c6.62695 0 12 -5.37305 12 -12v-40c0 -6.62695 -5.37305 -12 -12 -12h-40zM448 -28v-36h-448v36c0 6.62695 5.37305 12 12 12h19.5v378.965c0 11.6172 10.7451 21.0352 24 21.0352h88.5v40c0 13.2549 10.7451 24 24 24h112c13.2549 0 24 -10.7451 24 -24v-40h88.5
c13.2549 0 24 -9.41797 24 -21.0352v-378.965h19.5c6.62695 0 12 -5.37305 12 -12zM79.5 -15h112.5v67c0 6.62695 5.37305 12 12 12h40c6.62695 0 12 -5.37305 12 -12v-67h112.5v351h-64.5v-24c0 -13.2549 -10.7451 -24 -24 -24h-112c-13.2549 0 -24 10.7451 -24 24v24
h-64.5v-351zM266 384h-26v26c0 3.31152 -2.68848 6 -6 6h-20c-3.31152 0 -6 -2.68848 -6 -6v-26h-26c-3.31152 0 -6 -2.68848 -6 -6v-20c0 -3.31152 2.68848 -6 6 -6h26v-26c0 -3.31152 2.68848 -6 6 -6h20c3.31152 0 6 2.68848 6 6v26h26c3.31152 0 6 2.68848 6 6v20
c0 3.31152 -2.68848 6 -6 6z" />
<glyph glyph-name="plus-square" unicode="&#xf0fe;" horiz-adv-x="448"
d="M352 208v-32c0 -6.59961 -5.40039 -12 -12 -12h-88v-88c0 -6.59961 -5.40039 -12 -12 -12h-32c-6.59961 0 -12 5.40039 -12 12v88h-88c-6.59961 0 -12 5.40039 -12 12v32c0 6.59961 5.40039 12 12 12h88v88c0 6.59961 5.40039 12 12 12h32c6.59961 0 12 -5.40039 12 -12
v-88h88c6.59961 0 12 -5.40039 12 -12zM448 368v-352c0 -26.5 -21.5 -48 -48 -48h-352c-26.5 0 -48 21.5 -48 48v352c0 26.5 21.5 48 48 48h352c26.5 0 48 -21.5 48 -48zM400 22v340c0 3.2998 -2.7002 6 -6 6h-340c-3.2998 0 -6 -2.7002 -6 -6v-340
c0 -3.2998 2.7002 -6 6 -6h340c3.2998 0 6 2.7002 6 6z" />
<glyph glyph-name="circle" unicode="&#xf111;"
d="M256 440c137 0 248 -111 248 -248s-111 -248 -248 -248s-248 111 -248 248s111 248 248 248zM256 -8c110.5 0 200 89.5 200 200s-89.5 200 -200 200s-200 -89.5 -200 -200s89.5 -200 200 -200z" />
<glyph glyph-name="smile" unicode="&#xf118;" horiz-adv-x="496"
d="M248 440c137 0 248 -111 248 -248s-111 -248 -248 -248s-248 111 -248 248s111 248 248 248zM248 -8c110.3 0 200 89.7002 200 200s-89.7002 200 -200 200s-200 -89.7002 -200 -200s89.7002 -200 200 -200zM168 208c-17.7002 0 -32 14.2998 -32 32s14.2998 32 32 32
s32 -14.2998 32 -32s-14.2998 -32 -32 -32zM328 208c-17.7002 0 -32 14.2998 -32 32s14.2998 32 32 32s32 -14.2998 32 -32s-14.2998 -32 -32 -32zM332 135.4c8.5 10.1992 23.7002 11.5 33.7998 3.09961c10.2002 -8.5 11.6006 -23.5996 3.10059 -33.7998
c-30 -36 -74.1006 -56.6006 -120.9 -56.6006s-90.9004 20.6006 -120.9 56.6006c-8.39941 10.2002 -7.09961 25.2998 3.10059 33.7998c10.0996 8.40039 25.2998 7.09961 33.7998 -3.09961c20.7998 -25.1006 51.5 -39.4004 84 -39.4004s63.2002 14.4004 84 39.4004z" />
<glyph glyph-name="frown" unicode="&#xf119;" horiz-adv-x="496"
d="M248 440c137 0 248 -111 248 -248s-111 -248 -248 -248s-248 111 -248 248s111 248 248 248zM248 -8c110.3 0 200 89.7002 200 200s-89.7002 200 -200 200s-200 -89.7002 -200 -200s89.7002 -200 200 -200zM168 208c-17.7002 0 -32 14.2998 -32 32s14.2998 32 32 32
s32 -14.2998 32 -32s-14.2998 -32 -32 -32zM328 272c17.7002 0 32 -14.2998 32 -32s-14.2998 -32 -32 -32s-32 14.2998 -32 32s14.2998 32 32 32zM248 144c40.2002 0 78 -17.7002 103.8 -48.5996c8.40039 -10.2002 7.10059 -25.3008 -3.09961 -33.8008
c-10.7002 -8.7998 -25.7002 -6.59961 -33.7998 3.10059c-16.6006 20 -41 31.3994 -66.9004 31.3994s-50.2998 -11.5 -66.9004 -31.3994c-8.5 -10.2002 -23.5996 -11.5 -33.7998 -3.10059c-10.2002 8.5 -11.5996 23.6006 -3.09961 33.8008
c25.7998 30.8994 63.5996 48.5996 103.8 48.5996z" />
<glyph glyph-name="meh" unicode="&#xf11a;" horiz-adv-x="496"
d="M248 440c137 0 248 -111 248 -248s-111 -248 -248 -248s-248 111 -248 248s111 248 248 248zM248 -8c110.3 0 200 89.7002 200 200s-89.7002 200 -200 200s-200 -89.7002 -200 -200s89.7002 -200 200 -200zM168 208c-17.7002 0 -32 14.2998 -32 32s14.2998 32 32 32
s32 -14.2998 32 -32s-14.2998 -32 -32 -32zM328 272c17.7002 0 32 -14.2998 32 -32s-14.2998 -32 -32 -32s-32 14.2998 -32 32s14.2998 32 32 32zM336 128c13.2002 0 24 -10.7998 24 -24s-10.7998 -24 -24 -24h-176c-13.2002 0 -24 10.7998 -24 24s10.7998 24 24 24h176z
" />
<glyph glyph-name="keyboard" unicode="&#xf11c;" horiz-adv-x="576"
d="M528 384c26.5098 0 48 -21.4902 48 -48v-288c0 -26.5098 -21.4902 -48 -48 -48h-480c-26.5098 0 -48 21.4902 -48 48v288c0 26.5098 21.4902 48 48 48h480zM536 48v288c0 4.41113 -3.58887 8 -8 8h-480c-4.41113 0 -8 -3.58887 -8 -8v-288c0 -4.41113 3.58887 -8 8 -8
h480c4.41113 0 8 3.58887 8 8zM170 178c0 -6.62695 -5.37305 -12 -12 -12h-28c-6.62695 0 -12 5.37305 -12 12v28c0 6.62695 5.37305 12 12 12h28c6.62695 0 12 -5.37305 12 -12v-28zM266 178c0 -6.62695 -5.37305 -12 -12 -12h-28c-6.62695 0 -12 5.37305 -12 12v28
c0 6.62695 5.37305 12 12 12h28c6.62695 0 12 -5.37305 12 -12v-28zM362 178c0 -6.62695 -5.37305 -12 -12 -12h-28c-6.62695 0 -12 5.37305 -12 12v28c0 6.62695 5.37305 12 12 12h28c6.62695 0 12 -5.37305 12 -12v-28zM458 178c0 -6.62695 -5.37305 -12 -12 -12h-28
c-6.62695 0 -12 5.37305 -12 12v28c0 6.62695 5.37305 12 12 12h28c6.62695 0 12 -5.37305 12 -12v-28zM122 96c0 -6.62695 -5.37305 -12 -12 -12h-28c-6.62695 0 -12 5.37305 -12 12v28c0 6.62695 5.37305 12 12 12h28c6.62695 0 12 -5.37305 12 -12v-28zM506 96
c0 -6.62695 -5.37305 -12 -12 -12h-28c-6.62695 0 -12 5.37305 -12 12v28c0 6.62695 5.37305 12 12 12h28c6.62695 0 12 -5.37305 12 -12v-28zM122 260c0 -6.62695 -5.37305 -12 -12 -12h-28c-6.62695 0 -12 5.37305 -12 12v28c0 6.62695 5.37305 12 12 12h28
c6.62695 0 12 -5.37305 12 -12v-28zM218 260c0 -6.62695 -5.37305 -12 -12 -12h-28c-6.62695 0 -12 5.37305 -12 12v28c0 6.62695 5.37305 12 12 12h28c6.62695 0 12 -5.37305 12 -12v-28zM314 260c0 -6.62695 -5.37305 -12 -12 -12h-28c-6.62695 0 -12 5.37305 -12 12v28
c0 6.62695 5.37305 12 12 12h28c6.62695 0 12 -5.37305 12 -12v-28zM410 260c0 -6.62695 -5.37305 -12 -12 -12h-28c-6.62695 0 -12 5.37305 -12 12v28c0 6.62695 5.37305 12 12 12h28c6.62695 0 12 -5.37305 12 -12v-28zM506 260c0 -6.62695 -5.37305 -12 -12 -12h-28
c-6.62695 0 -12 5.37305 -12 12v28c0 6.62695 5.37305 12 12 12h28c6.62695 0 12 -5.37305 12 -12v-28zM408 102c0 -6.62695 -5.37305 -12 -12 -12h-216c-6.62695 0 -12 5.37305 -12 12v16c0 6.62695 5.37305 12 12 12h216c6.62695 0 12 -5.37305 12 -12v-16z" />
<glyph glyph-name="calendar" unicode="&#xf133;" horiz-adv-x="448"
d="M400 384c26.5 0 48 -21.5 48 -48v-352c0 -26.5 -21.5 -48 -48 -48h-352c-26.5 0 -48 21.5 -48 48v352c0 26.5 21.5 48 48 48h48v52c0 6.59961 5.40039 12 12 12h40c6.59961 0 12 -5.40039 12 -12v-52h128v52c0 6.59961 5.40039 12 12 12h40c6.59961 0 12 -5.40039 12 -12
v-52h48zM394 -16c3.2998 0 6 2.7002 6 6v298h-352v-298c0 -3.2998 2.7002 -6 6 -6h340z" />
<glyph glyph-name="play-circle" unicode="&#xf144;"
d="M371.7 210c16.3994 -9.2002 16.3994 -32.9004 0 -42l-176 -101c-15.9004 -8.7998 -35.7002 2.59961 -35.7002 21v208c0 18.5 19.9004 29.7998 35.7002 21zM504 192c0 -137 -111 -248 -248 -248s-248 111 -248 248s111 248 248 248s248 -111 248 -248zM56 192
c0 -110.5 89.5 -200 200 -200s200 89.5 200 200s-89.5 200 -200 200s-200 -89.5 -200 -200z" />
<glyph glyph-name="minus-square" unicode="&#xf146;" horiz-adv-x="448"
d="M108 164c-6.59961 0 -12 5.40039 -12 12v32c0 6.59961 5.40039 12 12 12h232c6.59961 0 12 -5.40039 12 -12v-32c0 -6.59961 -5.40039 -12 -12 -12h-232zM448 368v-352c0 -26.5 -21.5 -48 -48 -48h-352c-26.5 0 -48 21.5 -48 48v352c0 26.5 21.5 48 48 48h352
c26.5 0 48 -21.5 48 -48zM400 22v340c0 3.2998 -2.7002 6 -6 6h-340c-3.2998 0 -6 -2.7002 -6 -6v-340c0 -3.2998 2.7002 -6 6 -6h340c3.2998 0 6 2.7002 6 6z" />
<glyph glyph-name="check-square" unicode="&#xf14a;" horiz-adv-x="448"
d="M400 416c26.5098 0 48 -21.4902 48 -48v-352c0 -26.5098 -21.4902 -48 -48 -48h-352c-26.5098 0 -48 21.4902 -48 48v352c0 26.5098 21.4902 48 48 48h352zM400 16v352h-352v-352h352zM364.136 257.724l-172.589 -171.204
c-4.70508 -4.66699 -12.3027 -4.63672 -16.9697 0.0683594l-90.7812 91.5156c-4.66699 4.70508 -4.63672 12.3037 0.0693359 16.9717l22.7188 22.5361c4.70508 4.66699 12.3027 4.63672 16.9697 -0.0693359l59.792 -60.2773l141.353 140.217
c4.70508 4.66699 12.3027 4.63672 16.9697 -0.0683594l22.5361 -22.7178c4.66699 -4.70605 4.63672 -12.3047 -0.0683594 -16.9717z" />
<glyph glyph-name="share-square" unicode="&#xf14d;" horiz-adv-x="576"
d="M561.938 289.94c18.75 -18.7402 18.75 -49.1406 0 -67.8809l-143.998 -144c-29.9727 -29.9727 -81.9404 -9.05273 -81.9404 33.9404v53.7998c-101.266 -7.83691 -99.625 -31.6406 -84.1104 -78.7598c14.2285 -43.0889 -33.4736 -79.248 -71.0195 -55.7402
c-51.6924 32.3057 -84.8701 83.0635 -84.8701 144.76c0 39.3408 12.2197 72.7402 36.3301 99.3008c19.8398 21.8398 47.7402 38.4697 82.9102 49.4199c36.7295 11.4395 78.3096 16.1094 120.76 17.9893v57.1982c0 42.9355 51.9258 63.9541 81.9404 33.9404zM384 112l144 144
l-144 144v-104.09c-110.86 -0.90332 -240 -10.5166 -240 -119.851c0 -52.1396 32.79 -85.6094 62.3096 -104.06c-39.8174 120.65 48.999 141.918 177.69 143.84v-103.84zM408.74 27.5068c6.14844 1.75684 15.5449 5.92383 20.9736 9.30273
c7.97656 4.95215 18.2861 -0.825195 18.2861 -10.2139v-42.5957c0 -26.5098 -21.4902 -48 -48 -48h-352c-26.5098 0 -48 21.4902 -48 48v352c0 26.5098 21.4902 48 48 48h132c6.62695 0 12 -5.37305 12 -12v-4.48633c0 -4.91699 -2.9873 -9.36914 -7.56934 -11.1514
c-13.7021 -5.33105 -26.3955 -11.5371 -38.0498 -18.585c-1.59668 -0.974609 -4.41016 -1.77051 -6.28027 -1.77734h-86.1006c-3.31152 0 -6 -2.68848 -6 -6v-340c0 -3.31152 2.68848 -6 6 -6h340c3.31152 0 6 2.68848 6 6v25.9658c0 5.37012 3.5791 10.0596 8.74023 11.541
z" />
<glyph glyph-name="compass" unicode="&#xf14e;" horiz-adv-x="496"
d="M347.94 318.14c16.6592 7.61035 33.8096 -9.54004 26.1992 -26.1992l-65.9697 -144.341c-2.73047 -5.97363 -9.7959 -13.0391 -15.7695 -15.7695l-144.341 -65.9697c-16.6592 -7.61035 -33.8096 9.5498 -26.1992 26.1992l65.9697 144.341
c2.73047 5.97363 9.7959 13.0391 15.7695 15.7695zM270.58 169.42c12.4697 12.4697 12.4697 32.6904 0 45.1602s-32.6904 12.4697 -45.1602 0s-12.4697 -32.6904 0 -45.1602s32.6904 -12.4697 45.1602 0zM248 440c136.97 0 248 -111.03 248 -248s-111.03 -248 -248 -248
s-248 111.03 -248 248s111.03 248 248 248zM248 -8c110.28 0 200 89.7197 200 200s-89.7197 200 -200 200s-200 -89.7197 -200 -200s89.7197 -200 200 -200z" />
<glyph glyph-name="caret-square-down" unicode="&#xf150;" horiz-adv-x="448"
d="M125.1 240h197.801c10.6992 0 16.0996 -13 8.5 -20.5l-98.9004 -98.2998c-4.7002 -4.7002 -12.2002 -4.7002 -16.9004 0l-98.8994 98.2998c-7.7002 7.5 -2.2998 20.5 8.39941 20.5zM448 368v-352c0 -26.5 -21.5 -48 -48 -48h-352c-26.5 0 -48 21.5 -48 48v352
c0 26.5 21.5 48 48 48h352c26.5 0 48 -21.5 48 -48zM400 22v340c0 3.2998 -2.7002 6 -6 6h-340c-3.2998 0 -6 -2.7002 -6 -6v-340c0 -3.2998 2.7002 -6 6 -6h340c3.2998 0 6 2.7002 6 6z" />
<glyph glyph-name="caret-square-up" unicode="&#xf151;" horiz-adv-x="448"
d="M322.9 144h-197.801c-10.6992 0 -16.0996 13 -8.5 20.5l98.9004 98.2998c4.7002 4.7002 12.2002 4.7002 16.9004 0l98.8994 -98.2998c7.7002 -7.5 2.2998 -20.5 -8.39941 -20.5zM448 368v-352c0 -26.5 -21.5 -48 -48 -48h-352c-26.5 0 -48 21.5 -48 48v352
c0 26.5 21.5 48 48 48h352c26.5 0 48 -21.5 48 -48zM400 22v340c0 3.2998 -2.7002 6 -6 6h-340c-3.2998 0 -6 -2.7002 -6 -6v-340c0 -3.2998 2.7002 -6 6 -6h340c3.2998 0 6 2.7002 6 6z" />
<glyph glyph-name="caret-square-right" unicode="&#xf152;" horiz-adv-x="448"
d="M176 93.0996v197.801c0 10.6992 13 16.0996 20.5 8.5l98.2998 -98.9004c4.7002 -4.7002 4.7002 -12.2002 0 -16.9004l-98.2998 -98.8994c-7.5 -7.7002 -20.5 -2.2998 -20.5 8.39941zM448 368v-352c0 -26.5 -21.5 -48 -48 -48h-352c-26.5 0 -48 21.5 -48 48v352
c0 26.5 21.5 48 48 48h352c26.5 0 48 -21.5 48 -48zM400 22v340c0 3.2998 -2.7002 6 -6 6h-340c-3.2998 0 -6 -2.7002 -6 -6v-340c0 -3.2998 2.7002 -6 6 -6h340c3.2998 0 6 2.7002 6 6z" />
<glyph glyph-name="file" unicode="&#xf15b;" horiz-adv-x="384"
d="M369.9 350.1c9 -9 14.0996 -21.2998 14.0996 -34v-332.1c0 -26.5 -21.5 -48 -48 -48h-288c-26.5 0 -48 21.5 -48 48v416c0 26.5 21.5 48 48 48.0996h204.1c12.7002 0 24.9004 -5.09961 33.9004 -14.0996zM332.1 320l-76.0996 76.0996v-76.0996h76.0996zM48 -16h288v288
h-104c-13.2998 0 -24 10.7002 -24 24v104h-160v-416z" />
<glyph glyph-name="file-alt" unicode="&#xf15c;" horiz-adv-x="384"
d="M288 200v-28c0 -6.59961 -5.40039 -12 -12 -12h-168c-6.59961 0 -12 5.40039 -12 12v28c0 6.59961 5.40039 12 12 12h168c6.59961 0 12 -5.40039 12 -12zM276 128c6.59961 0 12 -5.40039 12 -12v-28c0 -6.59961 -5.40039 -12 -12 -12h-168c-6.59961 0 -12 5.40039 -12 12
v28c0 6.59961 5.40039 12 12 12h168zM384 316.1v-332.1c0 -26.5 -21.5 -48 -48 -48h-288c-26.5 0 -48 21.5 -48 48v416c0 26.5 21.5 48 48 48h204.1c12.7002 0 24.9004 -5.09961 33.9004 -14.0996l83.9004 -83.9004c9 -8.90039 14.0996 -21.2002 14.0996 -33.9004z
M256 396.1v-76.0996h76.0996zM336 -16v288h-104c-13.2998 0 -24 10.7002 -24 24v104h-160v-416h288z" />
<glyph glyph-name="thumbs-up" unicode="&#xf164;"
d="M466.27 161.31c4.6748 -22.6465 0.864258 -44.5371 -8.98926 -62.9893c2.95898 -23.8682 -4.02148 -48.5654 -17.3398 -66.9902c-0.954102 -55.9072 -35.8232 -95.3301 -112.94 -95.3301c-7 0 -15 0.00976562 -22.2197 0.00976562
c-102.742 0 -133.293 38.9395 -177.803 39.9404c-3.56934 -13.7764 -16.085 -23.9502 -30.9775 -23.9502h-64c-17.6729 0 -32 14.3271 -32 32v240c0 17.6729 14.3271 32 32 32h98.7598c19.1455 16.9531 46.0137 60.6533 68.7598 83.4004
c13.667 13.667 10.1533 108.6 71.7607 108.6c57.5801 0 95.2695 -31.9355 95.2695 -104.73c0 -18.4092 -3.92969 -33.7295 -8.84961 -46.5391h36.4795c48.6025 0 85.8203 -41.5654 85.8203 -85.5801c0 -19.1504 -4.95996 -34.9902 -13.7305 -49.8408zM404.52 107.48
c21.5811 20.3838 18.6992 51.0645 5.21094 65.6191c9.44922 0 22.3594 18.9102 22.2695 37.8105c-0.0898438 18.9102 -16.71 37.8203 -37.8203 37.8203h-103.989c0 37.8193 28.3594 55.3691 28.3594 94.5391c0 23.75 0 56.7305 -47.2695 56.7305
c-18.9102 -18.9102 -9.45996 -66.1797 -37.8203 -94.54c-26.5596 -26.5703 -66.1797 -97.46 -94.54 -97.46h-10.9199v-186.17c53.6113 0 100.001 -37.8203 171.64 -37.8203h37.8203c35.5117 0 60.8203 17.1201 53.1201 65.9004
c15.2002 8.16016 26.5 36.4395 13.9395 57.5703zM88 16c0 13.2549 -10.7451 24 -24 24s-24 -10.7451 -24 -24s10.7451 -24 24 -24s24 10.7451 24 24z" />
<glyph glyph-name="thumbs-down" unicode="&#xf165;"
d="M466.27 222.69c8.77051 -14.8506 13.7305 -30.6904 13.7305 -49.8408c0 -44.0146 -37.2178 -85.5801 -85.8203 -85.5801h-36.4795c4.91992 -12.8096 8.84961 -28.1299 8.84961 -46.5391c0 -72.7949 -37.6895 -104.73 -95.2695 -104.73
c-61.6074 0 -58.0938 94.9326 -71.7607 108.6c-22.7461 22.7471 -49.6133 66.4473 -68.7598 83.4004h-7.05176c-5.5332 -9.56152 -15.8662 -16 -27.708 -16h-64c-17.6729 0 -32 14.3271 -32 32v240c0 17.6729 14.3271 32 32 32h64c8.11328 0 15.5146 -3.02539 21.1553 -8
h10.8447c40.9971 0 73.1953 39.9902 176.78 39.9902c7.21973 0 15.2197 0.00976562 22.2197 0.00976562c77.1172 0 111.986 -39.4229 112.94 -95.3301c13.3184 -18.4248 20.2979 -43.1221 17.3398 -66.9902c9.85352 -18.4521 13.6641 -40.3428 8.98926 -62.9893zM64 152
c13.2549 0 24 10.7451 24 24s-10.7451 24 -24 24s-24 -10.7451 -24 -24s10.7451 -24 24 -24zM394.18 135.27c21.1104 0 37.7305 18.9102 37.8203 37.8203c0.0898438 18.9004 -12.8203 37.8105 -22.2695 37.8105c13.4883 14.5547 16.3701 45.2354 -5.21094 65.6191
c12.5605 21.1309 1.26074 49.4102 -13.9395 57.5703c7.7002 48.7803 -17.6084 65.9004 -53.1201 65.9004h-37.8203c-71.6387 0 -118.028 -37.8203 -171.64 -37.8203v-186.17h10.9199c28.3604 0 67.9805 -70.8896 94.54 -97.46
c28.3604 -28.3604 18.9102 -75.6299 37.8203 -94.54c47.2695 0 47.2695 32.9805 47.2695 56.7305c0 39.1699 -28.3594 56.7197 -28.3594 94.5391h103.989z" />
<glyph glyph-name="sun" unicode="&#xf185;"
d="M494.2 226.1c11.2002 -7.59961 17.7998 -20.0996 17.8994 -33.6992c0 -13.4004 -6.69922 -26 -17.7998 -33.5l-59.7998 -40.5l13.7002 -71c2.5 -13.2002 -1.60059 -26.8008 -11.1006 -36.3008s-22.8994 -13.7998 -36.2998 -11.0996l-70.8994 13.7002l-40.4004 -59.9004
c-7.5 -11.0996 -20.0996 -17.7998 -33.5 -17.7998s-26 6.7002 -33.5 17.9004l-40.4004 59.8994l-70.7998 -13.7002c-13.3994 -2.59961 -26.7998 1.60059 -36.2998 11.1006s-13.7002 23.0996 -11.0996 36.2998l13.6992 71l-59.7998 40.5
c-11.0996 7.5 -17.7998 20 -17.7998 33.5s6.59961 26 17.7998 33.5996l59.7998 40.5l-13.6992 71c-2.60059 13.2002 1.59961 26.7002 11.0996 36.3008c9.5 9.59961 23 13.6992 36.2998 11.1992l70.7998 -13.6992l40.4004 59.8994c15.0996 22.2998 51.9004 22.2998 67 0
l40.4004 -59.8994l70.8994 13.6992c13 2.60059 26.6006 -1.59961 36.2002 -11.0996c9.5 -9.59961 13.7002 -23.2002 11.0996 -36.4004l-13.6992 -71zM381.3 140.5l76.7998 52.0996l-76.7998 52l17.6006 91.1006l-91 -17.6006l-51.9004 76.9004l-51.7998 -76.7998
l-91 17.5996l17.5996 -91.2002l-76.7998 -52l76.7998 -52l-17.5996 -91.1992l90.8994 17.5996l51.9004 -77l51.9004 76.9004l91 -17.6006zM256 296c57.2998 0 104 -46.7002 104 -104s-46.7002 -104 -104 -104s-104 46.7002 -104 104s46.7002 104 104 104zM256 136
c30.9004 0 56 25.0996 56 56s-25.0996 56 -56 56s-56 -25.0996 -56 -56s25.0996 -56 56 -56z" />
<glyph glyph-name="moon" unicode="&#xf186;"
d="M279.135 -64c-141.424 0 -256 114.64 -256 256c0 141.425 114.641 256 256 256c13.0068 -0.00195312 33.9443 -1.91797 46.7354 -4.27734c44.0205 -8.13086 53.7666 -66.8691 15.0215 -88.9189c-41.374 -23.5439 -67.4336 -67.4121 -67.4336 -115.836
c0 -83.5234 75.9238 -146.475 158.272 -130.792c43.6904 8.32129 74.5186 -42.5693 46.248 -77.4004c-47.8613 -58.9717 -120.088 -94.7754 -198.844 -94.7754zM279.135 400c-114.875 0 -208 -93.125 -208 -208s93.125 -208 208 -208
c65.2314 0 123.439 30.0361 161.575 77.0244c-111.611 -21.2568 -215.252 64.0957 -215.252 177.943c0 67.5127 36.9326 126.392 91.6934 157.555c-12.3271 2.27637 -25.0312 3.47754 -38.0166 3.47754z" />
<glyph glyph-name="caret-square-left" unicode="&#xf191;" horiz-adv-x="448"
d="M272 290.9v-197.801c0 -10.6992 -13 -16.0996 -20.5 -8.5l-98.2998 98.9004c-4.7002 4.7002 -4.7002 12.2002 0 16.9004l98.2998 98.8994c7.5 7.7002 20.5 2.2998 20.5 -8.39941zM448 368v-352c0 -26.5 -21.5 -48 -48 -48h-352c-26.5 0 -48 21.5 -48 48v352
c0 26.5 21.5 48 48 48h352c26.5 0 48 -21.5 48 -48zM400 22v340c0 3.2998 -2.7002 6 -6 6h-340c-3.2998 0 -6 -2.7002 -6 -6v-340c0 -3.2998 2.7002 -6 6 -6h340c3.2998 0 6 2.7002 6 6z" />
<glyph glyph-name="dot-circle" unicode="&#xf192;"
d="M256 392c-110.549 0 -200 -89.4678 -200 -200c0 -110.549 89.4678 -200 200 -200c110.549 0 200 89.4678 200 200c0 110.549 -89.4678 200 -200 200zM256 440c136.967 0 248 -111.033 248 -248s-111.033 -248 -248 -248s-248 111.033 -248 248s111.033 248 248 248z
M256 272c44.1826 0 80 -35.8174 80 -80s-35.8174 -80 -80 -80s-80 35.8174 -80 80s35.8174 80 80 80z" />
<glyph glyph-name="building" unicode="&#xf1ad;" horiz-adv-x="448"
d="M128 300v40c0 6.59961 5.40039 12 12 12h40c6.59961 0 12 -5.40039 12 -12v-40c0 -6.59961 -5.40039 -12 -12 -12h-40c-6.59961 0 -12 5.40039 -12 12zM268 288c-6.59961 0 -12 5.40039 -12 12v40c0 6.59961 5.40039 12 12 12h40c6.59961 0 12 -5.40039 12 -12v-40
c0 -6.59961 -5.40039 -12 -12 -12h-40zM140 192c-6.59961 0 -12 5.40039 -12 12v40c0 6.59961 5.40039 12 12 12h40c6.59961 0 12 -5.40039 12 -12v-40c0 -6.59961 -5.40039 -12 -12 -12h-40zM268 192c-6.59961 0 -12 5.40039 -12 12v40c0 6.59961 5.40039 12 12 12h40
c6.59961 0 12 -5.40039 12 -12v-40c0 -6.59961 -5.40039 -12 -12 -12h-40zM192 108c0 -6.59961 -5.40039 -12 -12 -12h-40c-6.59961 0 -12 5.40039 -12 12v40c0 6.59961 5.40039 12 12 12h40c6.59961 0 12 -5.40039 12 -12v-40zM268 96c-6.59961 0 -12 5.40039 -12 12v40
c0 6.59961 5.40039 12 12 12h40c6.59961 0 12 -5.40039 12 -12v-40c0 -6.59961 -5.40039 -12 -12 -12h-40zM448 -28v-36h-448v36c0 6.59961 5.40039 12 12 12h19.5v440c0 13.2998 10.7002 24 24 24h337c13.2998 0 24 -10.7002 24 -24v-440h19.5
c6.59961 0 12 -5.40039 12 -12zM79.5 -15h112.5v67c0 6.59961 5.40039 12 12 12h40c6.59961 0 12 -5.40039 12 -12v-67h112.5v414l-288.5 1z" />
<glyph glyph-name="file-pdf" unicode="&#xf1c1;" horiz-adv-x="384"
d="M369.9 350.1c9 -9 14.0996 -21.2998 14.0996 -34v-332.1c0 -26.5 -21.5 -48 -48 -48h-288c-26.5 0 -48 21.5 -48 48v416c0 26.5 21.5 48 48 48.0996h204.1c12.7002 0 24.9004 -5.09961 33.9004 -14.0996zM332.1 320l-76.0996 76.0996v-76.0996h76.0996zM48 -16h288v288
h-104c-13.2998 0 -24 10.7002 -24 24v104h-160v-416zM298.2 127.7c10.5 -10.5 8 -38.7002 -17.5 -38.7002c-14.7998 0 -36.9004 6.7998 -55.7998 17c-21.6006 -3.59961 -46 -12.7002 -68.4004 -20.0996c-50.0996 -86.4004 -79.4004 -47 -76.0996 -31.2002
c4 20 31 35.8994 51 46.2002c10.5 18.3994 25.3994 50.5 35.3994 74.3994c-7.39941 28.6006 -11.3994 51 -7 67.1006c4.7998 17.6992 38.4004 20.2998 42.6006 -5.90039c4.69922 -15.4004 -1.5 -39.9004 -5.40039 -56c8.09961 -21.2998 19.5996 -35.7998 36.7998 -46.2998
c17.4004 2.2002 52.2002 5.5 64.4004 -6.5zM100.1 49.9004c0 -0.700195 11.4004 4.69922 30.4004 35c-5.90039 -5.5 -25.2998 -21.3008 -30.4004 -35zM181.7 240.5c-2.5 0 -2.60059 -26.9004 1.7998 -40.7998c4.90039 8.7002 5.59961 40.7998 -1.7998 40.7998zM157.3 103.9
c15.9004 6.09961 34 14.8994 54.7998 19.1992c-11.1992 8.30078 -21.7998 20.4004 -30.0996 35.5c-6.7002 -17.6992 -15 -37.7998 -24.7002 -54.6992zM288.9 108.9c3.59961 2.39941 -2.2002 10.3994 -37.3008 7.7998c32.3008 -13.7998 37.3008 -7.7998 37.3008 -7.7998z" />
<glyph glyph-name="file-word" unicode="&#xf1c2;" horiz-adv-x="384"
d="M369.9 350.1c9 -9 14.0996 -21.2998 14.0996 -34v-332.1c0 -26.5 -21.5 -48 -48 -48h-288c-26.5 0 -48 21.5 -48 48v416c0 26.5 21.5 48 48 48.0996h204.1c12.7002 0 24.9004 -5.09961 33.9004 -14.0996zM332.1 320l-76.0996 76.0996v-76.0996h76.0996zM48 -16h288v288
h-104c-13.2998 0 -24 10.7002 -24 24v104h-160v-416zM268.1 192v0.200195h15.8008c7.7998 0 13.5 -7.2998 11.5996 -14.9004c-4.2998 -17 -13.7002 -54.0996 -34.5 -136c-1.2998 -5.39941 -6.09961 -9.09961 -11.5996 -9.09961h-24.7002
c-5.5 0 -10.2998 3.7998 -11.6006 9.09961c-5.2998 20.9004 -17.7998 71 -17.8994 71.4004l-2.90039 17.2998c-0.5 -5.2998 -1.5 -11.0996 -3 -17.2998l-17.8994 -71.4004c-1.30078 -5.39941 -6.10059 -9.09961 -11.6006 -9.09961h-25.2002
c-5.59961 0 -10.3994 3.7002 -11.6992 9.09961c-6.5 26.5 -25.2002 103.4 -33.2002 136c-1.7998 7.5 3.89941 14.7998 11.7002 14.7998h16.7998c5.7998 0 10.7002 -4.09961 11.7998 -9.69922c5 -25.7002 18.4004 -93.8008 19.0996 -99
c0.300781 -1.7002 0.400391 -3.10059 0.5 -4.2002c0.800781 7.5 0.400391 4.7002 24.8008 103.7c1.39941 5.2998 6.19922 9.09961 11.6992 9.09961h13.3008c5.59961 0 10.3994 -3.7998 11.6992 -9.2002c23.9004 -99.7002 22.8008 -94.3994 23.6006 -99.5
c0.299805 -1.7002 0.5 -3.09961 0.700195 -4.2998c0.599609 8.09961 0.399414 5.7998 21 103.5c1.09961 5.5 6 9.5 11.6992 9.5z" />
<glyph glyph-name="file-excel" unicode="&#xf1c3;" horiz-adv-x="384"
d="M369.9 350.1c9 -9 14.0996 -21.2998 14.0996 -34v-332.1c0 -26.5 -21.5 -48 -48 -48h-288c-26.5 0 -48 21.5 -48 48v416c0 26.5 21.5 48 48 48.0996h204.1c12.7002 0 24.9004 -5.09961 33.9004 -14.0996zM332.1 320l-76.0996 76.0996v-76.0996h76.0996zM48 -16h288v288
h-104c-13.2998 0 -24 10.7002 -24 24v104h-160v-416zM260 224c9.2002 0 15 -10 10.2998 -18c-16 -27.5 -45.5996 -76.9004 -46.2998 -78l46.4004 -78c4.59961 -8 -1.10059 -18 -10.4004 -18h-28.7998c-4.40039 0 -8.5 2.40039 -10.6006 6.2998
c-22.6992 41.7998 -13.6992 27.5 -28.5996 57.7002c-5.59961 -12.7002 -6.90039 -17.7002 -28.5996 -57.7002c-2.10059 -3.89941 -6.10059 -6.2998 -10.5 -6.2998h-28.9004c-9.2998 0 -15.0996 10 -10.4004 18l46.3008 78l-46.3008 78c-4.59961 8 1.10059 18 10.4004 18
h28.9004c4.39941 0 8.5 -2.40039 10.5996 -6.2998c21.7002 -40.4004 14.7002 -28.6006 28.5996 -57.7002c6.40039 15.2998 10.6006 24.5996 28.6006 57.7002c2.09961 3.89941 6.09961 6.2998 10.5 6.2998h28.7998z" />
<glyph glyph-name="file-powerpoint" unicode="&#xf1c4;" horiz-adv-x="384"
d="M369.9 350.1c9 -9 14.0996 -21.2998 14.0996 -34v-332.1c0 -26.5 -21.5 -48 -48 -48h-288c-26.5 0 -48 21.5 -48 48v416c0 26.5 21.5 48 48 48.0996h204.1c12.7002 0 24.9004 -5.09961 33.9004 -14.0996zM332.1 320l-76.0996 76.0996v-76.0996h76.0996zM48 -16h288v288
h-104c-13.2998 0 -24 10.7002 -24 24v104h-160v-416zM120 44v168c0 6.59961 5.40039 12 12 12h69.2002c36.7002 0 62.7998 -27 62.7998 -66.2998c0 -74.2998 -68.7002 -66.5 -95.5 -66.5v-47.2002c0 -6.59961 -5.40039 -12 -12 -12h-24.5c-6.59961 0 -12 5.40039 -12 12z
M168.5 131.4h23c7.90039 0 13.9004 2.39941 18.0996 7.19922c8.5 9.80078 8.40039 28.5 0.100586 37.8008c-4.10059 4.59961 -9.90039 7 -17.4004 7h-23.8994v-52h0.0996094z" />
<glyph glyph-name="file-image" unicode="&#xf1c5;" horiz-adv-x="384"
d="M369.9 350.1c9 -9 14.0996 -21.2998 14.0996 -34v-332.1c0 -26.5 -21.5 -48 -48 -48h-288c-26.5 0 -48 21.5 -48 48v416c0 26.5 21.5 48 48 48.0996h204.1c12.7002 0 24.9004 -5.09961 33.9004 -14.0996zM332.1 320l-76.0996 76.0996v-76.0996h76.0996zM48 -16h288v288
h-104c-13.2998 0 -24 10.7002 -24 24v104h-160v-416zM80 32v64l39.5 39.5c4.7002 4.7002 12.2998 4.7002 17 0l39.5 -39.5l87.5 87.5c4.7002 4.7002 12.2998 4.7002 17 0l23.5 -23.5v-128h-224zM128 272c26.5 0 48 -21.5 48 -48s-21.5 -48 -48 -48s-48 21.5 -48 48
s21.5 48 48 48z" />
<glyph glyph-name="file-archive" unicode="&#xf1c6;" horiz-adv-x="384"
d="M128.3 288h32v-32h-32v32zM192.3 384v-32h-32v32h32zM128.3 352h32v-32h-32v32zM192.3 320v-32h-32v32h32zM369.9 350.1c9 -9 14.0996 -21.2998 14.0996 -34v-332.1c0 -26.5 -21.5 -48 -48 -48h-288c-26.5 0 -48 21.5 -48 48v416c0 26.5 21.5 48 48 48.0996h204.1
c12.7002 0 24.9004 -5.09961 33.9004 -14.0996zM256 396.1v-76.0996h76.0996zM336 -16v288h-104c-13.2998 0 -24 10.7002 -24 24v104h-48.2998v-16h-32v16h-79.7002v-416h288zM194.2 182.3l17.2998 -87.7002c6.40039 -32.3994 -18.4004 -62.5996 -51.5 -62.5996
c-33.2002 0 -58 30.4004 -51.4004 62.9004l19.7002 97.0996v32h32v-32h22.1006c5.7998 0 10.6992 -4.09961 11.7998 -9.7002zM160.3 57.9004c17.9004 0 32.4004 12.0996 32.4004 27c0 14.8994 -14.5 27 -32.4004 27c-17.8994 0 -32.3994 -12.1006 -32.3994 -27
c0 -14.9004 14.5 -27 32.3994 -27zM192.3 256v-32h-32v32h32z" />
<glyph glyph-name="file-audio" unicode="&#xf1c7;" horiz-adv-x="384"
d="M369.941 350.059c7.75977 -7.75977 14.0586 -22.9658 14.0586 -33.9404v-332.118c0 -26.5098 -21.4902 -48 -48 -48h-288c-26.5098 0 -48 21.4902 -48 48v416c0 26.5098 21.4902 48 48 48h204.118c10.9746 0 26.1807 -6.29883 33.9404 -14.0586zM332.118 320
l-76.1182 76.1182v-76.1182h76.1182zM48 -16h288v288h-104c-13.2549 0 -24 10.7451 -24 24v104h-160v-416zM192 60.0244c0 -10.6914 -12.9258 -16.0459 -20.4854 -8.48535l-35.5146 35.9746h-28c-6.62695 0 -12 5.37305 -12 12v56c0 6.62695 5.37305 12 12 12h28
l35.5146 36.9473c7.56055 7.56055 20.4854 2.20605 20.4854 -8.48535v-135.951zM233.201 107.154c9.05078 9.29688 9.05957 24.1328 0.000976562 33.4385c-22.1494 22.752 12.2344 56.2461 34.3945 33.4814c27.1982 -27.9404 27.2119 -72.4443 0.000976562 -100.401
c-21.793 -22.3857 -56.9463 10.3154 -34.3965 33.4814z" />
<glyph glyph-name="file-video" unicode="&#xf1c8;" horiz-adv-x="384"
d="M369.941 350.059c7.75977 -7.75977 14.0586 -22.9658 14.0586 -33.9404v-332.118c0 -26.5098 -21.4902 -48 -48 -48h-288c-26.5098 0 -48 21.4902 -48 48v416c0 26.5098 21.4902 48 48 48h204.118c10.9746 0 26.1807 -6.29883 33.9404 -14.0586zM332.118 320
l-76.1182 76.1182v-76.1182h76.1182zM48 -16h288v288h-104c-13.2549 0 -24 10.7451 -24 24v104h-160v-416zM276.687 195.303c10.0049 10.0049 27.3135 2.99707 27.3135 -11.3135v-111.976c0 -14.2939 -17.2959 -21.332 -27.3135 -11.3135l-52.6865 52.6738v-37.374
c0 -11.0459 -8.9541 -20 -20 -20h-104c-11.0459 0 -20 8.9541 -20 20v104c0 11.0459 8.9541 20 20 20h104c11.0459 0 20 -8.9541 20 -20v-37.374z" />
<glyph glyph-name="file-code" unicode="&#xf1c9;" horiz-adv-x="384"
d="M149.9 98.9004c3.5 -3.30078 3.69922 -8.90039 0.399414 -12.4004l-17.3994 -18.5996c-1.60059 -1.80078 -4 -2.80078 -6.40039 -2.80078c-2.2002 0 -4.40039 0.900391 -6 2.40039l-57.7002 54.0996c-3.7002 3.40039 -3.7002 9.30078 0 12.8008l57.7002 54.0996
c3.40039 3.2998 9 3.2002 12.4004 -0.400391l17.3994 -18.5996l0.200195 -0.200195c3.2002 -3.59961 2.7998 -9.2002 -0.799805 -12.3994l-32.7998 -28.9004l32.7998 -28.9004zM369.9 350.1c9 -9 14.0996 -21.2998 14.0996 -34v-332.1c0 -26.5 -21.5 -48 -48 -48h-288
c-26.5 0 -48 21.5 -48 48v416c0 26.5 21.5 48 48 48.0996h204.1c12.7002 0 24.9004 -5.09961 33.9004 -14.0996zM256 396.1v-76.0996h76.0996zM336 -16v288h-104c-13.2998 0 -24 10.7002 -24 24v104h-160v-416h288zM209.6 234l24.4004 -7
c4.7002 -1.2998 7.40039 -6.2002 6 -10.9004l-54.7002 -188.199c-1.2998 -4.60059 -6.2002 -7.40039 -10.8994 -6l-24.4004 7.09961c-4.7002 1.2998 -7.40039 6.2002 -6 10.9004l54.7002 188.1c1.39941 4.7002 6.2002 7.40039 10.8994 6zM234.1 157.1
c-3.5 3.30078 -3.69922 8.90039 -0.399414 12.4004l17.3994 18.5996c3.30078 3.60059 8.90039 3.7002 12.4004 0.400391l57.7002 -54.0996c3.7002 -3.40039 3.7002 -9.30078 0 -12.8008l-57.7002 -54.0996c-3.5 -3.2998 -9.09961 -3.09961 -12.4004 0.400391
l-17.3994 18.5996l-0.200195 0.200195c-3.2002 3.59961 -2.7998 9.2002 0.799805 12.3994l32.7998 28.9004l-32.7998 28.9004z" />
<glyph glyph-name="life-ring" unicode="&#xf1cd;"
d="M256 -56c-136.967 0 -248 111.033 -248 248s111.033 248 248 248s248 -111.033 248 -248s-111.033 -248 -248 -248zM152.602 20.7197c63.2178 -38.3184 143.579 -38.3184 206.797 0l-53.4111 53.4111c-31.8467 -13.5215 -68.168 -13.5059 -99.9746 0zM336 192
c0 44.1123 -35.8877 80 -80 80s-80 -35.8877 -80 -80s35.8877 -80 80 -80s80 35.8877 80 80zM427.28 88.6016c38.3184 63.2178 38.3184 143.579 0 206.797l-53.4111 -53.4111c13.5215 -31.8467 13.5049 -68.168 0 -99.9746zM359.397 363.28
c-63.2168 38.3184 -143.578 38.3184 -206.796 0l53.4111 -53.4111c31.8457 13.5215 68.167 13.5049 99.9736 0zM84.7197 295.398c-38.3184 -63.2178 -38.3184 -143.579 0 -206.797l53.4111 53.4111c-13.5215 31.8467 -13.5059 68.168 0 99.9746z" />
<glyph glyph-name="paper-plane" unicode="&#xf1d8;"
d="M440 441.5c34.5996 19.9004 77.5996 -8.7998 71.5 -48.9004l-59.4004 -387.199c-2.2998 -14.5 -11.0996 -27.3008 -23.8994 -34.5c-7.2998 -4.10059 -15.4004 -6.2002 -23.6006 -6.2002c-6.19922 0 -12.3994 1.2002 -18.2998 3.59961l-111.899 46.2002l-43.8008 -59.0996
c-27.3994 -36.9004 -86.5996 -17.8008 -86.5996 28.5996v84.4004l-114.3 47.2998c-36.7998 15.0996 -40.1006 66 -5.7002 85.8994zM192 -16l36.5996 49.5l-36.5996 15.0996v-64.5996zM404.6 12.7002l59.4004 387.3l-416 -240l107.8 -44.5996l211.5 184.3
c14.2002 12.2998 34.4004 -5.7002 23.7002 -21.2002l-140.2 -202.3z" />
<glyph glyph-name="futbol" unicode="&#xf1e3;" horiz-adv-x="496"
d="M483.8 268.6c42.2998 -130.199 -29 -270.1 -159.2 -312.399c-25.5 -8.2998 -51.2998 -12.2002 -76.6992 -12.2002c-104.5 0 -201.7 66.5996 -235.7 171.4c-42.2998 130.199 29 270.1 159.2 312.399c25.5 8.2998 51.2998 12.2002 76.6992 12.2002
c104.5 0 201.7 -66.5996 235.7 -171.4zM409.3 74.9004c6.10059 8.39941 12.1006 16.8994 16.7998 26.1992c14.3008 28.1006 21.5 58.5 21.7002 89.2002l-38.8994 36.4004l-71.1006 -22.1006l-24.3994 -75.1992l43.6992 -60.9004zM409.3 310.3
c-24.5 33.4004 -58.7002 58.4004 -97.8994 71.4004l-47.4004 -26.2002v-73.7998l64.2002 -46.5l70.7002 22zM184.9 381.6c-39.9004 -13.2998 -73.5 -38.5 -97.8008 -71.8994l10.1006 -52.5l70.5996 -22l64.2002 46.5v73.7998zM139 68.5l43.5 61.7002l-24.2998 74.2998
l-71.1006 22.2002l-39 -36.4004c0.5 -55.7002 23.4004 -95.2002 37.8008 -115.3zM187.2 1.5c64.0996 -20.4004 115.5 -1.7998 121.7 0l22.3994 48.0996l-44.2998 61.7002h-78.5996l-43.6006 -61.7002z" />
<glyph glyph-name="newspaper" unicode="&#xf1ea;" horiz-adv-x="576"
d="M552 384c13.2549 0 24 -10.7451 24 -24v-336c0 -13.2549 -10.7451 -24 -24 -24h-496c-30.9277 0 -56 25.0723 -56 56v272c0 13.2549 10.7451 24 24 24h42.752c6.60547 18.623 24.3896 32 45.248 32h440zM48 56c0 -4.41113 3.58887 -8 8 -8s8 3.58887 8 8v248h-16v-248z
M528 48v288h-416v-280c0 -2.7168 -0.204102 -5.38574 -0.578125 -8h416.578zM172 168c-6.62695 0 -12 5.37305 -12 12v96c0 6.62695 5.37305 12 12 12h136c6.62695 0 12 -5.37305 12 -12v-96c0 -6.62695 -5.37305 -12 -12 -12h-136zM200 248v-40h80v40h-80zM160 108v24
c0 6.62695 5.37305 12 12 12h136c6.62695 0 12 -5.37305 12 -12v-24c0 -6.62695 -5.37305 -12 -12 -12h-136c-6.62695 0 -12 5.37305 -12 12zM352 108v24c0 6.62695 5.37305 12 12 12h104c6.62695 0 12 -5.37305 12 -12v-24c0 -6.62695 -5.37305 -12 -12 -12h-104
c-6.62695 0 -12 5.37305 -12 12zM352 252v24c0 6.62695 5.37305 12 12 12h104c6.62695 0 12 -5.37305 12 -12v-24c0 -6.62695 -5.37305 -12 -12 -12h-104c-6.62695 0 -12 5.37305 -12 12zM352 180v24c0 6.62695 5.37305 12 12 12h104c6.62695 0 12 -5.37305 12 -12v-24
c0 -6.62695 -5.37305 -12 -12 -12h-104c-6.62695 0 -12 5.37305 -12 12z" />
<glyph glyph-name="bell-slash" unicode="&#xf1f6;" horiz-adv-x="640"
d="M633.99 -23.0195c6.91016 -5.52051 8.01953 -15.5908 2.5 -22.4902l-10 -12.4902c-5.53027 -6.88965 -15.5898 -8.00977 -22.4902 -2.49023l-598 467.51c-6.90039 5.52051 -8.01953 15.5908 -2.49023 22.4902l10 12.4902
c5.52051 6.90039 15.5898 8.00977 22.4902 2.49023zM163.53 80h182.84l61.3994 -48h-279.659c-19.1201 0 -31.9902 15.5996 -32.1006 32c-0.0498047 7.5498 2.61035 15.2598 8.61035 21.71c18.3701 19.7402 51.5703 49.6904 54.8398 140.42l45.4697 -35.5498
c-6.91992 -54.7803 -24.6895 -88.5498 -41.3994 -110.58zM320 352c-23.3496 0 -45 -7.17969 -62.9404 -19.4004l-38.1699 29.8408c19.6807 15.7793 43.1104 27.3096 69.1299 32.7197v20.8398c0 17.6699 14.3203 32 31.9805 32s31.9805 -14.3301 31.9805 -32v-20.8398
c73.46 -15.2598 127.939 -77.46 127.939 -155.16c0 -41.3604 6.03027 -70.7197 14.3398 -92.8496l-59.5293 46.54c-1.63086 13.96 -2.77051 28.8896 -2.79004 45.7295c0 0.200195 0.0595703 0.379883 0.0595703 0.580078c0 61.8604 -50.1396 112 -112 112zM320 -64
c-35.3203 0 -63.9697 28.6504 -63.9697 64h127.939c0 -35.3496 -28.6494 -64 -63.9697 -64z" />
<glyph glyph-name="copyright" unicode="&#xf1f9;"
d="M256 440c136.967 0 248 -111.033 248 -248s-111.033 -248 -248 -248s-248 111.033 -248 248s111.033 248 248 248zM256 -8c110.549 0 200 89.4678 200 200c0 110.549 -89.4678 200 -200 200c-110.549 0 -200 -89.4688 -200 -200c0 -110.549 89.4678 -200 200 -200z
M363.351 93.0645c-9.61328 -9.71289 -45.5293 -41.3965 -104.064 -41.3965c-82.4297 0 -140.484 61.4248 -140.484 141.567c0 79.1514 60.2754 139.4 139.763 139.4c55.5303 0 88.7373 -26.6201 97.5928 -34.7783c2.13379 -1.96289 3.86523 -5.9082 3.86523 -8.80762
c0 -1.95508 -0.864258 -4.87402 -1.92969 -6.51465l-18.1543 -28.1133c-3.8418 -5.9502 -11.9668 -7.28223 -17.499 -2.9209c-8.5957 6.77637 -31.8145 22.5381 -61.708 22.5381c-48.3037 0 -77.916 -35.3301 -77.916 -80.082c0 -41.5889 26.8877 -83.6924 78.2764 -83.6924
c32.6572 0 56.8428 19.0391 65.7266 27.2256c5.26953 4.85645 13.5957 4.03906 17.8193 -1.73828l19.8652 -27.1699c1.28613 -1.74512 2.33008 -4.91992 2.33008 -7.08789c0 -2.72363 -1.56055 -6.5 -3.48242 -8.42969z" />
<glyph glyph-name="closed-captioning" unicode="&#xf20a;"
d="M464 384c26.5 0 48 -21.5 48 -48v-288c0 -26.5 -21.5 -48 -48 -48h-416c-26.5 0 -48 21.5 -48 48v288c0 26.5 21.5 48 48 48h416zM458 48c3.2998 0 6 2.7002 6 6v276c0 3.2998 -2.7002 6 -6 6h-404c-3.2998 0 -6 -2.7002 -6 -6v-276c0 -3.2998 2.7002 -6 6 -6h404z
M246.9 133.7c1.69922 -2.40039 1.5 -5.60059 -0.5 -7.7002c-53.6006 -56.7998 -172.801 -32.0996 -172.801 67.9004c0 97.2998 121.7 119.5 172.5 70.0996c2.10059 -2 2.5 -3.2002 1 -5.7002l-17.5 -30.5c-1.89941 -3.09961 -6.19922 -4 -9.09961 -1.7002
c-40.7998 32 -94.5996 14.9004 -94.5996 -31.1992c0 -48 51 -70.5 92.1992 -32.6006c2.80078 2.5 7.10059 2.10059 9.2002 -0.899414zM437.3 133.7c1.7002 -2.40039 1.5 -5.60059 -0.5 -7.7002c-53.5996 -56.9004 -172.8 -32.0996 -172.8 67.9004
c0 97.2998 121.7 119.5 172.5 70.0996c2.09961 -2 2.5 -3.2002 1 -5.7002l-17.5 -30.5c-1.90039 -3.09961 -6.2002 -4 -9.09961 -1.7002c-40.8008 32 -94.6006 14.9004 -94.6006 -31.1992c0 -48 51 -70.5 92.2002 -32.6006c2.7998 2.5 7.09961 2.10059 9.2002 -0.899414z
" />
<glyph glyph-name="object-group" unicode="&#xf247;"
d="M500 320h-12v-256h12c6.62695 0 12 -5.37305 12 -12v-72c0 -6.62695 -5.37305 -12 -12 -12h-72c-6.62695 0 -12 5.37305 -12 12v12h-320v-12c0 -6.62695 -5.37305 -12 -12 -12h-72c-6.62695 0 -12 5.37305 -12 12v72c0 6.62695 5.37305 12 12 12h12v256h-12
c-6.62695 0 -12 5.37305 -12 12v72c0 6.62695 5.37305 12 12 12h72c6.62695 0 12 -5.37305 12 -12v-12h320v12c0 6.62695 5.37305 12 12 12h72c6.62695 0 12 -5.37305 12 -12v-72c0 -6.62695 -5.37305 -12 -12 -12zM448 384v-32h32v32h-32zM32 384v-32h32v32h-32zM64 0v32
h-32v-32h32zM480 0v32h-32v-32h32zM440 64v256h-12c-6.62695 0 -12 5.37305 -12 12v12h-320v-12c0 -6.62695 -5.37305 -12 -12 -12h-12v-256h12c6.62695 0 12 -5.37305 12 -12v-12h320v12c0 6.62695 5.37305 12 12 12h12zM404 256c6.62695 0 12 -5.37207 12 -12v-168
c0 -6.62793 -5.37305 -12 -12 -12h-200c-6.62695 0 -12 5.37207 -12 12v52h-84c-6.62695 0 -12 5.37207 -12 12v168c0 6.62793 5.37305 12 12 12h200c6.62695 0 12 -5.37207 12 -12v-52h84zM136 280v-112h144v112h-144zM376 104v112h-56v-76
c0 -6.62793 -5.37305 -12 -12 -12h-76v-24h144z" />
<glyph glyph-name="object-ungroup" unicode="&#xf248;" horiz-adv-x="576"
d="M564 224h-12v-160h12c6.62695 0 12 -5.37305 12 -12v-72c0 -6.62695 -5.37305 -12 -12 -12h-72c-6.62695 0 -12 5.37305 -12 12v12h-224v-12c0 -6.62695 -5.37305 -12 -12 -12h-72c-6.62695 0 -12 5.37305 -12 12v72c0 6.62695 5.37305 12 12 12h12v24h-88v-12
c0 -6.62695 -5.37305 -12 -12 -12h-72c-6.62695 0 -12 5.37305 -12 12v72c0 6.62695 5.37305 12 12 12h12v160h-12c-6.62695 0 -12 5.37305 -12 12v72c0 6.62695 5.37305 12 12 12h72c6.62695 0 12 -5.37305 12 -12v-12h224v12c0 6.62695 5.37305 12 12 12h72
c6.62695 0 12 -5.37305 12 -12v-72c0 -6.62695 -5.37305 -12 -12 -12h-12v-24h88v12c0 6.62695 5.37305 12 12 12h72c6.62695 0 12 -5.37305 12 -12v-72c0 -6.62695 -5.37305 -12 -12 -12zM352 384v-32h32v32h-32zM352 128v-32h32v32h-32zM64 96v32h-32v-32h32zM64 352v32
h-32v-32h32zM96 136h224v12c0 6.62695 5.37305 12 12 12h12v160h-12c-6.62695 0 -12 5.37305 -12 12v12h-224v-12c0 -6.62695 -5.37305 -12 -12 -12h-12v-160h12c6.62695 0 12 -5.37305 12 -12v-12zM224 0v32h-32v-32h32zM504 64v160h-12c-6.62695 0 -12 5.37305 -12 12v12
h-88v-88h12c6.62695 0 12 -5.37305 12 -12v-72c0 -6.62695 -5.37305 -12 -12 -12h-72c-6.62695 0 -12 5.37305 -12 12v12h-88v-24h12c6.62695 0 12 -5.37305 12 -12v-12h224v12c0 6.62695 5.37305 12 12 12h12zM544 0v32h-32v-32h32zM544 256v32h-32v-32h32z" />
<glyph glyph-name="sticky-note" unicode="&#xf249;" horiz-adv-x="448"
d="M448 99.8936c0 -10.9746 -6.29883 -26.1797 -14.0586 -33.9404l-83.8828 -83.8818c-7.75977 -7.76074 -22.9658 -14.0596 -33.9404 -14.0596h-268.118c-26.5098 0 -48 21.4902 -48 48v351.988c0 26.5098 21.4902 48 48 48h352c26.5098 0 48 -21.4902 48 -48v-268.106z
M320 19.8936l76.1182 76.1182h-76.1182v-76.1182zM400 368h-352v-351.988h224v104c0 13.2549 10.7451 24 24 24h104v223.988z" />
<glyph glyph-name="clone" unicode="&#xf24d;"
d="M464 448c26.5098 0 48 -21.4902 48 -48v-320c0 -26.5098 -21.4902 -48 -48 -48h-48v-48c0 -26.5098 -21.4902 -48 -48 -48h-320c-26.5098 0 -48 21.4902 -48 48v320c0 26.5098 21.4902 48 48 48h48v48c0 26.5098 21.4902 48 48 48h320zM362 -16c3.31152 0 6 2.68848 6 6
v42h-224c-26.5098 0 -48 21.4902 -48 48v224h-42c-3.31152 0 -6 -2.68848 -6 -6v-308c0 -3.31152 2.68848 -6 6 -6h308zM458 80c3.31152 0 6 2.68848 6 6v308c0 3.31152 -2.68848 6 -6 6h-308c-3.31152 0 -6 -2.68848 -6 -6v-308c0 -3.31152 2.68848 -6 6 -6h308z" />
<glyph glyph-name="hourglass" unicode="&#xf254;" horiz-adv-x="384"
d="M368 400c0 -80.0996 -31.8984 -165.619 -97.1797 -208c64.9912 -42.1934 97.1797 -127.436 97.1797 -208h4c6.62695 0 12 -5.37305 12 -12v-24c0 -6.62695 -5.37305 -12 -12 -12h-360c-6.62695 0 -12 5.37305 -12 12v24c0 6.62695 5.37305 12 12 12h4
c0 80.0996 31.8994 165.619 97.1797 208c-64.9912 42.1934 -97.1797 127.436 -97.1797 208h-4c-6.62695 0 -12 5.37305 -12 12v24c0 6.62695 5.37305 12 12 12h360c6.62695 0 12 -5.37305 12 -12v-24c0 -6.62695 -5.37305 -12 -12 -12h-4zM64 400
c0 -101.621 57.3066 -184 128 -184s128 82.3799 128 184h-256zM320 -16c0 101.62 -57.3076 184 -128 184s-128 -82.3799 -128 -184h256z" />
<glyph glyph-name="hand-rock" unicode="&#xf255;"
d="M408.864 368.948c48.8213 20.751 103.136 -15.0723 103.136 -67.9111v-114.443c0 -15.3955 -3.08887 -30.3906 -9.18262 -44.5674l-42.835 -99.6562c-4.99707 -11.625 -3.98242 -18.8574 -3.98242 -42.3701c0 -17.6729 -14.3271 -32 -32 -32h-252
c-17.6729 0 -32 14.3271 -32 32c0 27.3301 1.1416 29.2012 -3.11035 32.9033l-97.71 85.0811c-24.8994 21.6797 -39.1797 52.8926 -39.1797 85.6338v56.9531c0 47.4277 44.8457 82.0215 91.0459 71.1807c1.96094 55.751 63.5107 87.8262 110.671 60.8057
c29.1895 31.0713 78.8604 31.4473 108.334 -0.0214844c32.7051 18.6846 76.4121 10.3096 98.8135 -23.5879zM464 186.594v114.445c0 34.29 -52 33.8232 -52 0.676758c0 -8.83594 -7.16309 -16 -16 -16h-7c-8.83691 0 -16 7.16406 -16 16v26.751
c0 34.457 -52 33.707 -52 0.676758v-27.4287c0 -8.83594 -7.16309 -16 -16 -16h-7c-8.83691 0 -16 7.16406 -16 16v40.4658c0 34.3525 -52 33.8115 -52 0.677734v-41.1436c0 -8.83594 -7.16406 -16 -16 -16h-7c-8.83594 0 -16 7.16406 -16 16v26.751
c0 34.4023 -52 33.7744 -52 0.676758v-116.571c0 -8.83203 -7.16797 -16 -16 -16c-3.30664 0 -8.01367 1.7627 -10.5068 3.93359l-7 6.09473c-3.03223 2.64062 -5.49316 8.04688 -5.49316 12.0674v0v41.2275c0 34.2148 -52 33.8857 -52 0.677734v-56.9531
c0 -18.8555 8.27441 -36.874 22.7002 -49.4365l97.71 -85.0801c12.4502 -10.8398 19.5898 -26.4463 19.5898 -42.8164v-10.2861h220v7.07617c0 13.21 2.65332 26.0791 7.88281 38.25l42.835 99.6553c2.91602 6.75391 5.28223 18.207 5.28223 25.5635v0.0488281z" />
<glyph glyph-name="hand-paper" unicode="&#xf256;" horiz-adv-x="448"
d="M372.57 335.359c39.9062 5.63281 75.4297 -25.7393 75.4297 -66.3594v-131.564c-0.00195312 -12.7666 -2.33008 -33.2246 -5.19531 -45.666l-30.1836 -130.958c-3.34668 -14.5234 -16.2783 -24.8125 -31.1816 -24.8125h-222.897
c-9.10352 0 -20.7793 6.01758 -26.0615 13.4316l-119.97 168.415c-21.2441 29.8203 -14.8047 71.3574 14.5498 93.1533c18.7754 13.9395 42.1309 16.2979 62.083 8.87109v126.13c0 44.0547 41.125 75.5439 82.4053 64.9834c23.8926 48.1963 92.3535 50.2471 117.982 0.74707
c42.5186 11.1445 83.0391 -21.9346 83.0391 -65.5469v-10.8242zM399.997 137.437l-0.00195312 131.563c0 24.9492 -36.5703 25.5508 -36.5703 -0.691406v-76.3086c0 -8.83691 -7.16309 -16 -16 -16h-6.85645c-8.83691 0 -16 7.16309 -16 16v154.184
c0 25.501 -36.5703 26.3633 -36.5703 0.691406v-154.875c0 -8.83691 -7.16309 -16 -16 -16h-6.85645c-8.83691 0 -16 7.16309 -16 16v188.309c0 25.501 -36.5703 26.3545 -36.5703 0.691406v-189c0 -8.83691 -7.16309 -16 -16 -16h-6.85645c-8.83691 0 -16 7.16309 -16 16
v153.309c0 25.501 -36.5713 26.3359 -36.5713 0.691406v-206.494c0 -15.5703 -20.0352 -21.9092 -29.0303 -9.2832l-27.1279 38.0791c-14.3711 20.1709 -43.833 -2.33496 -29.3945 -22.6045l115.196 -161.697h201.92l27.3252 118.551
c2.63086 11.417 3.96484 23.1553 3.96484 34.8857z" />
<glyph glyph-name="hand-scissors" unicode="&#xf257;"
d="M256 -32c-44.9561 0 -77.3428 43.2627 -64.0244 85.8535c-21.6484 13.71 -34.0156 38.7617 -30.3408 65.0068h-87.6348c-40.8037 0 -74 32.8105 -74 73.1406c0 40.3291 33.1963 73.1396 74 73.1396l94 -9.14062l-78.8496 18.6787
c-38.3076 14.7422 -57.04 57.4707 -41.9424 95.1123c15.0303 37.4736 57.7549 55.7803 95.6416 41.2012l144.929 -55.7568c24.9551 30.5566 57.8086 43.9932 92.2178 24.7324l97.999 -54.8525c20.9746 -11.7393 34.0049 -33.8457 34.0049 -57.6904v-205.702
c0 -30.7422 -21.4404 -57.5576 -51.7979 -64.5537l-118.999 -27.4268c-4.97168 -1.14648 -10.0889 -1.72949 -15.2031 -1.72949zM256 16.0127l70 -0.000976562c1.23633 0 3.21777 0.225586 4.42285 0.501953l119.001 27.4277
c8.58203 1.97754 14.5762 9.29102 14.5762 17.7812v205.701c0 6.4873 -3.62109 12.542 -9.44922 15.8047l-98 54.8545c-8.13965 4.55566 -18.668 2.61914 -24.4873 -4.50781l-21.7646 -26.6475c-2.65039 -3.24512 -8.20215 -5.87891 -12.3926 -5.87891
c-1.64062 0 -4.21484 0.477539 -5.74609 1.06738l-166.549 64.0908c-32.6543 12.5664 -50.7744 -34.5771 -19.2227 -46.7168l155.357 -59.7852c5.66016 -2.17773 10.2539 -8.86816 10.2539 -14.9326v0v-11.6328c0 -8.83691 -7.16309 -16 -16 -16h-182
c-34.375 0 -34.4297 -50.2803 0 -50.2803h182c8.83691 0 16 -7.16309 16 -16v-6.85645c0 -8.83691 -7.16309 -16 -16 -16h-28c-25.1221 0 -25.1592 -36.5674 0 -36.5674h28c8.83691 0 16 -7.16211 16 -16v-6.85547c0 -8.83691 -7.16309 -16 -16 -16
c-25.1201 0 -25.1602 -36.5674 0 -36.5674z" />
<glyph glyph-name="hand-lizard" unicode="&#xf258;" horiz-adv-x="576"
d="M556.686 157.458c12.6357 -19.4863 19.3145 -42.0615 19.3145 -65.2871v-124.171h-224v71.582l-99.751 38.7871c-2.7832 1.08203 -5.70996 1.63086 -8.69727 1.63086h-131.552c-30.8789 0 -56 25.1211 -56 56c0 48.5234 39.4766 88 88 88h113.709l18.333 48h-196.042
c-44.1123 0 -80 35.8877 -80 80v8c0 30.8779 25.1211 56 56 56h293.917c24.5 0 47.084 -12.2725 60.4111 -32.8291zM528 16v76.1709v0.0478516c0 11.7461 -5.19141 29.2734 -11.5879 39.124l-146.358 225.715c-4.44336 6.85254 -11.9707 10.9424 -20.1367 10.9424h-293.917
c-4.41113 0 -8 -3.58887 -8 -8v-8c0 -17.6445 14.3555 -32 32 -32h213.471c25.2021 0 42.626 -25.293 33.6299 -48.8457l-24.5518 -64.2812c-7.05371 -18.4658 -25.0732 -30.873 -44.8398 -30.873h-113.709c-22.0557 0 -40 -17.9443 -40 -40c0 -4.41113 3.58887 -8 8 -8
h131.552h0.0517578c7.44141 0 19.1074 -2.19238 26.041 -4.89355l99.752 -38.7881c18.5898 -7.22852 30.6035 -24.7881 30.6035 -44.7363v-23.582h128z" />
<glyph glyph-name="hand-spock" unicode="&#xf259;"
d="M21.0957 66.21c-26.9688 25.3818 -28.2471 67.7461 -2.87109 94.707c24.1982 25.7139 64.2881 28.2373 91.4824 5.72168l-31.04 136.509c-9.38379 41.2803 21.4336 81.0127 64.0713 81.8438c1.74414 28.9062 22.2656 54.4912 51.8818 61.2949
c36.001 8.27539 72.0176 -14.2266 80.3037 -50.2959l21.6748 -131.99l16.9014 105.25c9.02344 36.0947 45.4473 57.7021 81.25 48.75c27.3066 -6.82715 45.7061 -29.1357 49.8496 -53.9922c43.2285 0.212891 75.6436 -40.1133 65.5439 -82.5244l-31.7295 -133.41
c-0.938477 -3.94141 -1.41406 -7.99414 -1.41406 -12.0449v-36.8389v-0.00683594c0 -9.29102 -2.14355 -24.0596 -4.78516 -32.9668l-31.8145 -107.312c-4.02734 -13.585 -16.5107 -22.9043 -30.6807 -22.9043h-237.6c-7.00586 0 -16.8311 3.89648 -21.9316 8.69824z
M53.1641 128.021c-7.17969 -7.62891 -6.81543 -19.6777 0.813477 -26.8574l124.487 -117.164h219.311l28.4199 95.8613c1.86133 6.27637 2.80469 12.7793 2.80469 19.3281v36.8389c0.000976562 6.48047 1.21973 16.8574 2.71973 23.1621l31.7549 133.407
c5.83105 24.4893 -31.1445 33.25 -36.9658 8.80273l-26.9229 -113.105c-1.61523 -6.78711 -8.58887 -12.2949 -15.5645 -12.2949h-9.69434c-10.4072 0 -18.043 9.79199 -15.5225 19.8799l38.127 152.512c6.09766 24.376 -30.7607 33.6396 -36.8643 9.21777l-42.3721 -169.49
c-1.67285 -6.68945 -8.62695 -12.1191 -15.5225 -12.1191h-13.2168v0c-7.0332 0 -14.0195 5.5625 -15.5938 12.417l-45.2207 196.828c-5.64453 24.5684 -42.6572 15.9609 -37.0342 -8.50781l41.6191 -181.153c2.30078 -10.0156 -5.31738 -19.583 -15.5938 -19.583h-8.60352
h-0.000976562c-7.0498 0 -14.04 5.5791 -15.6025 12.4541l-30.3984 133.757c-5.55273 24.4395 -42.6504 16.1963 -37.0547 -8.4209l34.1299 -150.172c0.263672 -1.16309 0.397461 -2.35352 0.397461 -3.5459v-69.4795c0 -13.9941 -16.7754 -21.2432 -26.9658 -11.6523
l-53.0117 49.8936c-7.61523 7.16699 -19.6377 6.85938 -26.8564 -0.8125z" />
<glyph glyph-name="hand-pointer" unicode="&#xf25a;" horiz-adv-x="448"
d="M358.182 268.639c43.1934 16.6348 89.8184 -15.7949 89.8184 -62.6387v-84c-0.000976562 -4.25 -0.775391 -11.0615 -1.72754 -15.2041l-27.4297 -118.999c-6.98242 -30.2969 -33.7549 -51.7969 -64.5566 -51.7969h-178.286c-21.2588 0 -41.3682 10.4102 -53.791 27.8457
l-109.699 154.001c-21.2432 29.8193 -14.8047 71.3574 14.5498 93.1523c18.8115 13.9658 42.1748 16.2822 62.083 8.87207v161.129c0 36.9443 29.7363 67 66.2861 67s66.2861 -30.0557 66.2861 -67v-73.6338c20.4131 2.85742 41.4678 -3.94238 56.5947 -19.6289
c27.1934 12.8467 60.3799 5.66992 79.8721 -19.0986zM80.9854 168.303c-14.4004 20.2119 -43.8008 -2.38281 -29.3945 -22.6055l109.712 -154c3.43457 -4.81934 8.92871 -7.69727 14.6973 -7.69727h178.285c8.49219 0 15.8037 5.99414 17.7822 14.5762l27.4297 119.001
c0.333008 1.44629 0.501953 2.93457 0.501953 4.42285v84c0 25.1602 -36.5713 25.1211 -36.5713 0c0 -8.83594 -7.16309 -16 -16 -16h-6.85645c-8.83691 0 -16 7.16406 -16 16v21c0 25.1602 -36.5713 25.1201 -36.5713 0v-21c0 -8.83594 -7.16309 -16 -16 -16h-6.85938
c-8.83691 0 -16 7.16406 -16 16v35c0 25.1602 -36.5703 25.1201 -36.5703 0v-35c0 -8.83594 -7.16309 -16 -16 -16h-6.85742c-8.83691 0 -16 7.16406 -16 16v175c0 25.1602 -36.5713 25.1201 -36.5713 0v-241.493c0 -15.5703 -20.0352 -21.9092 -29.0303 -9.2832z
M176.143 48v96c0 8.83691 6.26855 16 14 16h6c7.73242 0 14 -7.16309 14 -16v-96c0 -8.83691 -6.26758 -16 -14 -16h-6c-7.73242 0 -14 7.16309 -14 16zM251.571 48v96c0 8.83691 6.26758 16 14 16h6c7.73145 0 14 -7.16309 14 -16v-96c0 -8.83691 -6.26855 -16 -14 -16h-6
c-7.73242 0 -14 7.16309 -14 16zM327 48v96c0 8.83691 6.26758 16 14 16h6c7.73242 0 14 -7.16309 14 -16v-96c0 -8.83691 -6.26758 -16 -14 -16h-6c-7.73242 0 -14 7.16309 -14 16z" />
<glyph glyph-name="hand-peace" unicode="&#xf25b;" horiz-adv-x="448"
d="M362.146 256.024c42.5908 13.3184 85.8535 -19.0684 85.8535 -64.0244l-0.0117188 -70.001c-0.000976562 -4.25 -0.775391 -11.0615 -1.72949 -15.2031l-27.4268 -118.999c-6.99707 -30.3564 -33.8105 -51.7969 -64.5547 -51.7969h-205.702
c-23.8447 0 -45.9502 13.0303 -57.6904 34.0059l-54.8525 97.999c-19.2607 34.4092 -5.82422 67.2617 24.7324 92.2178l-55.7568 144.928c-14.5791 37.8867 3.72754 80.6113 41.2012 95.6416c37.6406 15.0977 80.3691 -3.63477 95.1123 -41.9424l18.6787 -78.8496
l-9.14062 94c0 40.8037 32.8096 74 73.1396 74s73.1406 -33.1963 73.1406 -74v-87.6348c26.2451 3.6748 51.2959 -8.69238 65.0068 -30.3408zM399.987 122l-0.000976562 70c0 25.1602 -36.5674 25.1201 -36.5674 0c0 -8.83691 -7.16309 -16 -16 -16h-6.85547
c-8.83789 0 -16 7.16309 -16 16v28c0 25.1592 -36.5674 25.1221 -36.5674 0v-28c0 -8.83691 -7.16309 -16 -16 -16h-6.85645c-8.83691 0 -16 7.16309 -16 16v182c0 34.4297 -50.2803 34.375 -50.2803 0v-182c0 -8.83691 -7.16309 -16 -16 -16h-11.6328v0
c-6.06445 0 -12.7549 4.59375 -14.9326 10.2539l-59.7842 155.357c-12.1396 31.5518 -59.2842 13.4326 -46.7168 -19.2227l64.0898 -166.549c0.589844 -1.53125 1.06738 -4.10547 1.06738 -5.74609c0 -4.19043 -2.63379 -9.74219 -5.87891 -12.3926l-26.6475 -21.7646
c-7.12695 -5.81934 -9.06445 -16.3467 -4.50781 -24.4873l54.8535 -98c3.26367 -5.82812 9.31934 -9.44922 15.8057 -9.44922h205.701c8.49121 0 15.8037 5.99414 17.7812 14.5762l27.4277 119.001c0.333008 1.44629 0.501953 2.93457 0.501953 4.42285z" />
<glyph glyph-name="registered" unicode="&#xf25d;"
d="M256 440c136.967 0 248 -111.033 248 -248s-111.033 -248 -248 -248s-248 111.033 -248 248s111.033 248 248 248zM256 -8c110.549 0 200 89.4678 200 200c0 110.549 -89.4678 200 -200 200c-110.549 0 -200 -89.4688 -200 -200c0 -110.549 89.4678 -200 200 -200z
M366.442 73.791c4.40332 -7.99219 -1.37012 -17.791 -10.5107 -17.791h-42.8096h-0.0126953c-3.97559 0 -8.71582 2.84961 -10.5801 6.36035l-47.5156 89.3027h-31.958v-83.6631c0 -6.61719 -5.38281 -12 -12 -12h-38.5674c-6.61719 0 -12 5.38281 -12 12v248.304
c0 6.61719 5.38281 12 12 12h78.667c71.251 0 101.498 -32.749 101.498 -85.252c0 -31.6123 -15.2148 -59.2969 -39.4824 -73.1758c3.02148 -4.61719 0.225586 0.199219 53.2715 -96.085zM256.933 208.094c20.9131 0 32.4307 11.5186 32.4316 32.4316
c0 19.5752 -6.5127 31.709 -38.9297 31.709h-27.377v-64.1406h33.875z" />
<glyph glyph-name="calendar-plus" unicode="&#xf271;" horiz-adv-x="448"
d="M336 156v-24c0 -6.59961 -5.40039 -12 -12 -12h-76v-76c0 -6.59961 -5.40039 -12 -12 -12h-24c-6.59961 0 -12 5.40039 -12 12v76h-76c-6.59961 0 -12 5.40039 -12 12v24c0 6.59961 5.40039 12 12 12h76v76c0 6.59961 5.40039 12 12 12h24c6.59961 0 12 -5.40039 12 -12
v-76h76c6.59961 0 12 -5.40039 12 -12zM448 336v-352c0 -26.5 -21.5 -48 -48 -48h-352c-26.5 0 -48 21.5 -48 48v352c0 26.5 21.5 48 48 48h48v52c0 6.59961 5.40039 12 12 12h40c6.59961 0 12 -5.40039 12 -12v-52h128v52c0 6.59961 5.40039 12 12 12h40
c6.59961 0 12 -5.40039 12 -12v-52h48c26.5 0 48 -21.5 48 -48zM400 -10v298h-352v-298c0 -3.2998 2.7002 -6 6 -6h340c3.2998 0 6 2.7002 6 6z" />
<glyph glyph-name="calendar-minus" unicode="&#xf272;" horiz-adv-x="448"
d="M124 120c-6.59961 0 -12 5.40039 -12 12v24c0 6.59961 5.40039 12 12 12h200c6.59961 0 12 -5.40039 12 -12v-24c0 -6.59961 -5.40039 -12 -12 -12h-200zM448 336v-352c0 -26.5 -21.5 -48 -48 -48h-352c-26.5 0 -48 21.5 -48 48v352c0 26.5 21.5 48 48 48h48v52
c0 6.59961 5.40039 12 12 12h40c6.59961 0 12 -5.40039 12 -12v-52h128v52c0 6.59961 5.40039 12 12 12h40c6.59961 0 12 -5.40039 12 -12v-52h48c26.5 0 48 -21.5 48 -48zM400 -10v298h-352v-298c0 -3.2998 2.7002 -6 6 -6h340c3.2998 0 6 2.7002 6 6z" />
<glyph glyph-name="calendar-times" unicode="&#xf273;" horiz-adv-x="448"
d="M311.7 73.2998l-17 -17c-4.7002 -4.7002 -12.2998 -4.7002 -17 0l-53.7002 53.7998l-53.7002 -53.6992c-4.7002 -4.7002 -12.2998 -4.7002 -17 0l-17 17c-4.7002 4.69922 -4.7002 12.2998 0 17l53.7002 53.6992l-53.7002 53.7002c-4.7002 4.7002 -4.7002 12.2998 0 17
l17 17c4.7002 4.7002 12.2998 4.7002 17 0l53.7002 -53.7002l53.7002 53.7002c4.7002 4.7002 12.2998 4.7002 17 0l17 -17c4.7002 -4.7002 4.7002 -12.2998 0 -17l-53.7998 -53.7998l53.6992 -53.7002c4.80078 -4.7002 4.80078 -12.2998 0.100586 -17zM448 336v-352
c0 -26.5 -21.5 -48 -48 -48h-352c-26.5 0 -48 21.5 -48 48v352c0 26.5 21.5 48 48 48h48v52c0 6.59961 5.40039 12 12 12h40c6.59961 0 12 -5.40039 12 -12v-52h128v52c0 6.59961 5.40039 12 12 12h40c6.59961 0 12 -5.40039 12 -12v-52h48c26.5 0 48 -21.5 48 -48zM400 -10
v298h-352v-298c0 -3.2998 2.7002 -6 6 -6h340c3.2998 0 6 2.7002 6 6z" />
<glyph glyph-name="calendar-check" unicode="&#xf274;" horiz-adv-x="448"
d="M400 384c26.5098 0 48 -21.4902 48 -48v-352c0 -26.5098 -21.4902 -48 -48 -48h-352c-26.5098 0 -48 21.4902 -48 48v352c0 26.5098 21.4902 48 48 48h48v52c0 6.62695 5.37305 12 12 12h40c6.62695 0 12 -5.37305 12 -12v-52h128v52c0 6.62695 5.37305 12 12 12h40
c6.62695 0 12 -5.37305 12 -12v-52h48zM394 -16c3.31152 0 6 2.68848 6 6v298h-352v-298c0 -3.31152 2.68848 -6 6 -6h340zM341.151 184.65l-142.31 -141.169c-4.70508 -4.66699 -12.3027 -4.6377 -16.9707 0.0673828l-75.0908 75.6992
c-4.66699 4.70508 -4.6377 12.3027 0.0673828 16.9707l22.7197 22.5361c4.70508 4.66699 12.3027 4.63672 16.9697 -0.0693359l44.1035 -44.4609l111.072 110.182c4.70508 4.66699 12.3027 4.63672 16.9707 -0.0683594l22.5361 -22.7178
c4.66699 -4.70508 4.63672 -12.3027 -0.0683594 -16.9697z" />
<glyph glyph-name="map" unicode="&#xf279;" horiz-adv-x="576"
d="M560.02 416c8.4502 0 15.9805 -6.83008 15.9805 -16.0195v-346.32c0 -11.9609 -9.01367 -25.2705 -20.1201 -29.71l-151.83 -52.8105c-5.32617 -1.7334 -14.1953 -3.13965 -19.7969 -3.13965c-5.7373 0 -14.8105 1.47363 -20.2529 3.29004l-172 60.71l-170.05 -62.8398
c-1.99023 -0.790039 -4 -1.16016 -5.95996 -1.16016c-8.45996 0 -15.9902 6.83008 -15.9902 16.0195v346.32c0.00292969 11.959 9.0166 25.2686 20.1201 29.71l151.83 52.8105c6.43945 2.08984 13.1201 3.13965 19.8096 3.13965
c5.73242 -0.00195312 14.8008 -1.47168 20.2402 -3.28027l172 -60.7197h0.00976562l170.05 62.8398c1.98047 0.790039 4 1.16016 5.95996 1.16016zM224 357.58v-285.97l128 -45.1904v285.97zM48 29.9502l127.36 47.0801l0.639648 0.229492v286.2l-128 -44.5303v-288.979z
M528 65.0801v288.97l-127.36 -47.0693l-0.639648 -0.240234v-286.19z" />
<glyph glyph-name="comment-alt" unicode="&#xf27a;"
d="M448 448c35.2998 0 64 -28.7002 64 -64v-288c0 -35.2998 -28.7002 -64 -64 -64h-144l-124.9 -93.5996c-2.19922 -1.7002 -4.69922 -2.40039 -7.09961 -2.40039c-6.2002 0 -12 4.90039 -12 12v84h-96c-35.2998 0 -64 28.7002 -64 64v288c0 35.2998 28.7002 64 64 64h384z
M464 96v288c0 8.7998 -7.2002 16 -16 16h-384c-8.7998 0 -16 -7.2002 -16 -16v-288c0 -8.7998 7.2002 -16 16 -16h144v-60l67.2002 50.4004l12.7998 9.59961h160c8.7998 0 16 7.2002 16 16z" />
<glyph glyph-name="pause-circle" unicode="&#xf28b;"
d="M256 440c137 0 248 -111 248 -248s-111 -248 -248 -248s-248 111 -248 248s111 248 248 248zM256 -8c110.5 0 200 89.5 200 200s-89.5 200 -200 200s-200 -89.5 -200 -200s89.5 -200 200 -200zM352 272v-160c0 -8.7998 -7.2002 -16 -16 -16h-48
c-8.7998 0 -16 7.2002 -16 16v160c0 8.7998 7.2002 16 16 16h48c8.7998 0 16 -7.2002 16 -16zM240 272v-160c0 -8.7998 -7.2002 -16 -16 -16h-48c-8.7998 0 -16 7.2002 -16 16v160c0 8.7998 7.2002 16 16 16h48c8.7998 0 16 -7.2002 16 -16z" />
<glyph glyph-name="stop-circle" unicode="&#xf28d;"
d="M504 192c0 -137 -111 -248 -248 -248s-248 111 -248 248s111 248 248 248s248 -111 248 -248zM56 192c0 -110.5 89.5 -200 200 -200s200 89.5 200 200s-89.5 200 -200 200s-200 -89.5 -200 -200zM352 272v-160c0 -8.7998 -7.2002 -16 -16 -16h-160
c-8.7998 0 -16 7.2002 -16 16v160c0 8.7998 7.2002 16 16 16h160c8.7998 0 16 -7.2002 16 -16z" />
<glyph glyph-name="handshake" unicode="&#xf2b5;" horiz-adv-x="640"
d="M519.2 320.1h120.8v-255.699h-64c-17.5 0 -31.7998 14.1992 -31.9004 31.6992h-57.8994c-1.7998 -8.19922 -5.2998 -16.0996 -10.9004 -23l-26.2002 -32.2998c-15.7998 -19.3994 -41.8994 -25.5 -64 -16.7998c-13.5 -16.5996 -30.5996 -24 -48.7998 -24
c-15.0996 0 -28.5996 5.09961 -41.0996 15.9004c-31.7998 -21.9004 -74.7002 -21.3008 -105.601 3.7998l-84.5996 76.3994h-9.09961c-0.100586 -17.5 -14.3008 -31.6992 -31.9004 -31.6992h-64v255.699h118l47.5996 47.6006c10.5 10.3994 24.8008 16.2998 39.6006 16.2998
h226.8v0c12.7812 0 30.5225 -7.30273 39.5996 -16.2998zM48 96.4004c8.7998 0 16 7.09961 16 16c0 8.7998 -7.2002 16 -16 16s-16 -7.2002 -16 -16c0 -8.80078 7.2002 -16 16 -16zM438 103.3c2.7002 3.40039 2.2002 8.5 -1.2002 11.2998l-108.2 87.8008l-8.19922 -7.5
c-40.3008 -36.8008 -86.7002 -11.8008 -101.5 4.39941c-26.7002 29 -25 74.4004 4.39941 101.3l38.7002 35.5h-56.7002c-2 -0.799805 -3.7002 -1.5 -5.7002 -2.2998l-61.6992 -61.5996h-41.9004v-128.101h27.7002l97.2998 -88
c16.0996 -13.0996 41.4004 -10.5 55.2998 6.60059l15.6006 19.2002l36.7998 -31.5c3 -2.40039 12 -4.90039 18 2.39941l30 36.5l23.8994 -19.3994c3.5 -2.80078 8.5 -2.2002 11.3008 1.19922zM544 144.1v128h-44.7002l-61.7002 61.6006
c-1.39941 1.5 -3.39941 2.2998 -5.5 2.2998l-83.6992 -0.200195c-10 0 -19.6006 -3.7002 -27 -10.5l-65.6006 -60.0996c-9.7002 -8.7998 -10.5 -24 -1.2002 -33.9004c8.90039 -9.39941 25.1006 -8.7002 34.6006 0l55.2002 50.6006c6.5 5.89941 16.5996 5.5 22.5996 -1
l10.9004 -11.7002c6 -6.5 5.5 -16.6006 -1 -22.6006l-12.5 -11.3994l102.699 -83.4004c2.80078 -2.2998 5.40039 -4.89941 7.7002 -7.7002h69.2002zM592 96.4004c8.7998 0 16 7.09961 16 16c0 8.7998 -7.2002 16 -16 16s-16 -7.2002 -16 -16c0 -8.80078 7.2002 -16 16 -16z
" />
<glyph glyph-name="envelope-open" unicode="&#xf2b6;"
d="M494.586 283.484c9.6123 -7.94824 17.4141 -24.5205 17.4141 -36.9932v-262.491c0 -26.5098 -21.4902 -48 -48 -48h-416c-26.5098 0 -48 21.4902 -48 48v262.515c0 12.5166 7.84668 29.1279 17.5146 37.0771c4.08008 3.35449 110.688 89.0996 135.15 108.549
c22.6992 18.1426 60.1299 55.8594 103.335 55.8594c43.4365 0 81.2314 -38.1914 103.335 -55.8594c23.5283 -18.707 130.554 -104.773 135.251 -108.656zM464 -10v253.632v0.00488281c0 1.5791 -0.996094 3.66602 -2.22363 4.6582
c-15.8633 12.8232 -108.793 87.5752 -132.366 106.316c-17.5527 14.0195 -49.7168 45.3887 -73.4102 45.3887c-23.6016 0 -55.2451 -30.8799 -73.4102 -45.3887c-23.5713 -18.7393 -116.494 -93.4795 -132.364 -106.293
c-1.40918 -1.13965 -2.22559 -2.85254 -2.22559 -4.66504v-253.653c0 -3.31152 2.68848 -6 6 -6h404c3.31152 0 6 2.68848 6 6zM432.009 177.704c4.24902 -5.15918 3.46484 -12.7949 -1.74512 -16.9814c-28.9746 -23.2822 -59.2734 -47.5967 -70.9287 -56.8623
c-22.6992 -18.1436 -60.1299 -55.8604 -103.335 -55.8604c-43.4521 0 -81.2871 38.2373 -103.335 55.8604c-11.2793 8.9668 -41.7441 33.4131 -70.9268 56.8643c-5.20996 4.1875 -5.99316 11.8223 -1.74512 16.9814l15.2578 18.5283
c4.17773 5.07227 11.6572 5.84277 16.7793 1.72559c28.6182 -23.001 58.5654 -47.0352 70.5596 -56.5713c17.5527 -14.0195 49.7168 -45.3887 73.4102 -45.3887c23.6016 0 55.2461 30.8799 73.4102 45.3887c11.9941 9.53516 41.9434 33.5703 70.5625 56.5684
c5.12207 4.11621 12.6016 3.3457 16.7783 -1.72656z" />
<glyph glyph-name="address-book" unicode="&#xf2b9;" horiz-adv-x="448"
d="M436 288h-20v-64h20c6.59961 0 12 -5.40039 12 -12v-40c0 -6.59961 -5.40039 -12 -12 -12h-20v-64h20c6.59961 0 12 -5.40039 12 -12v-40c0 -6.59961 -5.40039 -12 -12 -12h-20v-48c0 -26.5 -21.5 -48 -48 -48h-320c-26.5 0 -48 21.5 -48 48v416c0 26.5 21.5 48 48 48
h320c26.5 0 48 -21.5 48 -48v-48h20c6.59961 0 12 -5.40039 12 -12v-40c0 -6.59961 -5.40039 -12 -12 -12zM368 -16v416h-320v-416h320zM208 192c-35.2998 0 -64 28.7002 -64 64s28.7002 64 64 64s64 -28.7002 64 -64s-28.7002 -64 -64 -64zM118.4 64
c-12.4004 0 -22.4004 8.59961 -22.4004 19.2002v19.2002c0 31.7998 30.0996 57.5996 67.2002 57.5996c11.3994 0 17.8994 -8 44.7998 -8c26.0996 0 34 8 44.7998 8c37.1006 0 67.2002 -25.7998 67.2002 -57.5996v-19.2002c0 -10.6006 -10 -19.2002 -22.4004 -19.2002
h-179.199z" />
<glyph glyph-name="address-card" unicode="&#xf2bb;" horiz-adv-x="576"
d="M528 416c26.5 0 48 -21.5 48 -48v-352c0 -26.5 -21.5 -48 -48 -48h-480c-26.5 0 -48 21.5 -48 48v352c0 26.5 21.5 48 48 48h480zM528 16v352h-480v-352h480zM208 192c-35.2998 0 -64 28.7002 -64 64s28.7002 64 64 64s64 -28.7002 64 -64s-28.7002 -64 -64 -64z
M118.4 64c-12.4004 0 -22.4004 8.59961 -22.4004 19.2002v19.2002c0 31.7998 30.0996 57.5996 67.2002 57.5996c11.3994 0 17.8994 -8 44.7998 -8c26.0996 0 34 8 44.7998 8c37.1006 0 67.2002 -25.7998 67.2002 -57.5996v-19.2002
c0 -10.6006 -10 -19.2002 -22.4004 -19.2002h-179.199zM360 128c-4.40039 0 -8 3.59961 -8 8v16c0 4.40039 3.59961 8 8 8h112c4.40039 0 8 -3.59961 8 -8v-16c0 -4.40039 -3.59961 -8 -8 -8h-112zM360 192c-4.40039 0 -8 3.59961 -8 8v16c0 4.40039 3.59961 8 8 8h112
c4.40039 0 8 -3.59961 8 -8v-16c0 -4.40039 -3.59961 -8 -8 -8h-112zM360 256c-4.40039 0 -8 3.59961 -8 8v16c0 4.40039 3.59961 8 8 8h112c4.40039 0 8 -3.59961 8 -8v-16c0 -4.40039 -3.59961 -8 -8 -8h-112z" />
<glyph glyph-name="user-circle" unicode="&#xf2bd;" horiz-adv-x="496"
d="M248 344c53 0 96 -43 96 -96s-43 -96 -96 -96s-96 43 -96 96s43 96 96 96zM248 200c26.5 0 48 21.5 48 48s-21.5 48 -48 48s-48 -21.5 -48 -48s21.5 -48 48 -48zM248 440c137 0 248 -111 248 -248s-111 -248 -248 -248s-248 111 -248 248s111 248 248 248zM248 -8
c49.7002 0 95.0996 18.2998 130.1 48.4004c-14.8994 23 -40.3994 38.5 -69.5996 39.5c-20.7998 -6.5 -40.5996 -9.60059 -60.5 -9.60059s-39.7002 3.2002 -60.5 9.60059c-29.2002 -0.900391 -54.7002 -16.5 -69.5996 -39.5c35 -30.1006 80.3994 -48.4004 130.1 -48.4004z
M410.7 76.0996c23.3994 32.7002 37.2998 72.7002 37.2998 115.9c0 110.3 -89.7002 200 -200 200s-200 -89.7002 -200 -200c0 -43.2002 13.9004 -83.2002 37.2998 -115.9c24.5 31.4004 62.2002 51.9004 105.101 51.9004c10.1992 0 26.0996 -9.59961 57.5996 -9.59961
c31.5996 0 47.4004 9.59961 57.5996 9.59961c43 0 80.7002 -20.5 105.101 -51.9004z" />
<glyph glyph-name="id-badge" unicode="&#xf2c1;" horiz-adv-x="384"
d="M336 448c26.5 0 48 -21.5 48 -48v-416c0 -26.5 -21.5 -48 -48 -48h-288c-26.5 0 -48 21.5 -48 48v416c0 26.5 21.5 48 48 48h288zM336 -16v416h-288v-416h288zM144 336c-8.7998 0 -16 7.2002 -16 16s7.2002 16 16 16h96c8.7998 0 16 -7.2002 16 -16s-7.2002 -16 -16 -16
h-96zM192 160c-35.2998 0 -64 28.7002 -64 64s28.7002 64 64 64s64 -28.7002 64 -64s-28.7002 -64 -64 -64zM102.4 32c-12.4004 0 -22.4004 8.59961 -22.4004 19.2002v19.2002c0 31.7998 30.0996 57.5996 67.2002 57.5996c11.3994 0 17.8994 -8 44.7998 -8
c26.0996 0 34 8 44.7998 8c37.1006 0 67.2002 -25.7998 67.2002 -57.5996v-19.2002c0 -10.6006 -10 -19.2002 -22.4004 -19.2002h-179.199z" />
<glyph glyph-name="id-card" unicode="&#xf2c2;" horiz-adv-x="576"
d="M528 416c26.5 0 48 -21.5 48 -48v-352c0 -26.5 -21.5 -48 -48 -48h-480c-26.5 0 -48 21.5 -48 48v352c0 26.5 21.5 48 48 48h480zM528 16v288h-480v-288h32.7998c-1 4.5 -0.799805 -3.59961 -0.799805 22.4004c0 31.7998 30.0996 57.5996 67.2002 57.5996
c11.3994 0 17.8994 -8 44.7998 -8c26.0996 0 34 8 44.7998 8c37.1006 0 67.2002 -25.7998 67.2002 -57.5996c0 -26 0.0996094 -17.9004 -0.799805 -22.4004h224.8zM360 96c-4.40039 0 -8 3.59961 -8 8v16c0 4.40039 3.59961 8 8 8h112c4.40039 0 8 -3.59961 8 -8v-16
c0 -4.40039 -3.59961 -8 -8 -8h-112zM360 160c-4.40039 0 -8 3.59961 -8 8v16c0 4.40039 3.59961 8 8 8h112c4.40039 0 8 -3.59961 8 -8v-16c0 -4.40039 -3.59961 -8 -8 -8h-112zM360 224c-4.40039 0 -8 3.59961 -8 8v16c0 4.40039 3.59961 8 8 8h112
c4.40039 0 8 -3.59961 8 -8v-16c0 -4.40039 -3.59961 -8 -8 -8h-112zM192 128c-35.2998 0 -64 28.7002 -64 64s28.7002 64 64 64s64 -28.7002 64 -64s-28.7002 -64 -64 -64z" />
<glyph glyph-name="window-maximize" unicode="&#xf2d0;"
d="M464 416c26.5 0 48 -21.5 48 -48v-352c0 -26.5 -21.5 -48 -48 -48h-416c-26.5 0 -48 21.5 -48 48v352c0 26.5 21.5 48 48 48h416zM464 22v234h-416v-234c0 -3.2998 2.7002 -6 6 -6h404c3.2998 0 6 2.7002 6 6z" />
<glyph glyph-name="window-minimize" unicode="&#xf2d1;"
d="M480 -32h-448c-17.7002 0 -32 14.2998 -32 32s14.2998 32 32 32h448c17.7002 0 32 -14.2998 32 -32s-14.2998 -32 -32 -32z" />
<glyph glyph-name="window-restore" unicode="&#xf2d2;"
d="M464 448c26.5 0 48 -21.5 48 -48v-320c0 -26.5 -21.5 -48 -48 -48h-48v-48c0 -26.5 -21.5 -48 -48 -48h-320c-26.5 0 -48 21.5 -48 48v320c0 26.5 21.5 48 48 48h48v48c0 26.5 21.5 48 48 48h320zM368 -16v208h-320v-208h320zM464 80v320h-320v-48h224
c26.5 0 48 -21.5 48 -48v-224h48z" />
<glyph glyph-name="snowflake" unicode="&#xf2dc;" horiz-adv-x="448"
d="M440.1 92.7998c7.60059 -4.39941 10.1006 -14.2002 5.5 -21.7002l-7.89941 -13.8994c-4.40039 -7.7002 -14 -10.2998 -21.5 -5.90039l-39.2002 23l9.09961 -34.7002c2.30078 -8.5 -2.69922 -17.2998 -11.0996 -19.5996l-15.2002 -4.09961
c-8.39941 -2.30078 -17.0996 2.7998 -19.2998 11.2998l-21.2998 81l-71.9004 42.2002v-84.5l58.2998 -59.3008c6.10059 -6.19922 6.10059 -16.3994 0 -22.5996l-11.0996 -11.2998c-6.09961 -6.2002 -16.0996 -6.2002 -22.2002 0l-24.8994 25.3994v-46.0996
c0 -8.7998 -7 -16 -15.7002 -16h-15.7002c-8.7002 0 -15.7002 7.2002 -15.7002 16v45.9004l-24.8994 -25.4004c-6.10059 -6.2002 -16.1006 -6.2002 -22.2002 0l-11.1006 11.2998c-6.09961 6.2002 -6.09961 16.4004 0 22.6006l58.3008 59.2998v84.5l-71.9004 -42.2002
l-21.2998 -81c-2.2998 -8.5 -10.9004 -13.5996 -19.2998 -11.2998l-15.2002 4.09961c-8.40039 2.2998 -13.2998 11.1006 -11.1006 19.6006l9.10059 34.6992l-39.2002 -23c-7.5 -4.39941 -17.2002 -1.7998 -21.5 5.90039l-7.90039 13.9004
c-4.2998 7.69922 -1.69922 17.5 5.80078 21.8994l39.1992 23l-34.0996 9.2998c-8.40039 2.30078 -13.2998 11.1006 -11.0996 19.6006l4.09961 15.5c2.2998 8.5 10.9004 13.5996 19.2998 11.2998l79.7002 -21.7002l71.9004 42.2002l-71.9004 42.2002l-79.7002 -21.7002
c-8.39941 -2.2998 -17.0996 2.7998 -19.2998 11.2998l-4.09961 15.5c-2.30078 8.5 2.69922 17.2998 11.0996 19.6006l34.0996 9.09961l-39.1992 23c-7.60059 4.5 -10.1006 14.2002 -5.80078 21.9004l7.90039 13.8994c4.40039 7.7002 14 10.2998 21.5 5.90039l39.2002 -23
l-9.10059 34.7002c-2.2998 8.5 2.7002 17.2998 11.1006 19.5996l15.2002 4.09961c8.39941 2.30078 17.0996 -2.7998 19.2998 -11.2998l21.2998 -81l71.9004 -42.2002v84.5l-58.3008 59.3008c-6.09961 6.19922 -6.09961 16.3994 0 22.5996l11.5 11.2998
c6.10059 6.2002 16.1006 6.2002 22.2002 0l24.9004 -25.3994v46.0996c0 8.7998 7 16 15.7002 16h15.6992c8.7002 0 15.7002 -7.2002 15.7002 -16v-45.9004l24.9004 25.4004c6.09961 6.2002 16.0996 6.2002 22.2002 0l11.0996 -11.2998
c6.09961 -6.2002 6.09961 -16.4004 0 -22.6006l-58.2998 -59.2998v-84.5l71.8994 42.2002l21.3008 81c2.2998 8.5 10.8994 13.5996 19.2998 11.2998l15.2002 -4.09961c8.39941 -2.2998 13.2998 -11.1006 11.0996 -19.6006l-9.09961 -34.6992l39.1992 23
c7.5 4.39941 17.2002 1.7998 21.5 -5.90039l7.90039 -13.9004c4.2998 -7.69922 1.7002 -17.5 -5.7998 -21.8994l-39.2002 -23l34.0996 -9.2998c8.40039 -2.30078 13.3008 -11.1006 11.1006 -19.6006l-4.10059 -15.5c-2.2998 -8.5 -10.8994 -13.5996 -19.2998 -11.2998
l-79.7002 21.7002l-71.8994 -42.2002l71.7998 -42.2002l79.7002 21.7002c8.39941 2.2998 17.0996 -2.7998 19.2998 -11.2998l4.09961 -15.5c2.30078 -8.5 -2.69922 -17.2998 -11.0996 -19.6006l-34.0996 -9.2998z" />
<glyph glyph-name="trash-alt" unicode="&#xf2ed;" horiz-adv-x="448"
d="M268 32c-6.62402 0 -12 5.37598 -12 12v216c0 6.62402 5.37598 12 12 12h24c6.62402 0 12 -5.37598 12 -12v-216c0 -6.62402 -5.37598 -12 -12 -12h-24zM432 368c8.83203 0 16 -7.16797 16 -16v-16c0 -8.83203 -7.16797 -16 -16 -16h-16v-336
c0 -26.4961 -21.5039 -48 -48 -48h-288c-26.4961 0 -48 21.5039 -48 48v336h-16c-8.83203 0 -16 7.16797 -16 16v16c0 8.83203 7.16797 16 16 16h82.4102l34.0195 56.7002c7.71875 12.8613 26.1572 23.2998 41.1572 23.2998h0.00292969h100.82h0.0224609
c15 0 33.4385 -10.4385 41.1572 -23.2998l34 -56.7002h82.4102zM171.84 397.09l-17.4502 -29.0898h139.221l-17.46 29.0898c-0.96582 1.60645 -3.26953 2.91016 -5.14355 2.91016h-0.00683594h-94h-0.0166016c-1.87402 0 -4.17871 -1.30371 -5.14355 -2.91016zM368 -16v336
h-288v-336h288zM156 32c-6.62402 0 -12 5.37598 -12 12v216c0 6.62402 5.37598 12 12 12h24c6.62402 0 12 -5.37598 12 -12v-216c0 -6.62402 -5.37598 -12 -12 -12h-24z" />
<glyph glyph-name="images" unicode="&#xf302;" horiz-adv-x="576"
d="M480 32v-16c0 -26.5098 -21.4902 -48 -48 -48h-384c-26.5098 0 -48 21.4902 -48 48v256c0 26.5098 21.4902 48 48 48h16v-48h-10c-3.31152 0 -6 -2.68848 -6 -6v-244c0 -3.31152 2.68848 -6 6 -6h372c3.31152 0 6 2.68848 6 6v10h48zM522 368h-372
c-3.31152 0 -6 -2.68848 -6 -6v-244c0 -3.31152 2.68848 -6 6 -6h372c3.31152 0 6 2.68848 6 6v244c0 3.31152 -2.68848 6 -6 6zM528 416c26.5098 0 48 -21.4902 48 -48v-256c0 -26.5098 -21.4902 -48 -48 -48h-384c-26.5098 0 -48 21.4902 -48 48v256
c0 26.5098 21.4902 48 48 48h384zM264 304c0 -22.0908 -17.9092 -40 -40 -40s-40 17.9092 -40 40s17.9092 40 40 40s40 -17.9092 40 -40zM192 208l39.5146 39.5146c4.68652 4.68652 12.2842 4.68652 16.9717 0l39.5137 -39.5146l103.515 103.515
c4.68652 4.68652 12.2842 4.68652 16.9717 0l71.5137 -71.5146v-80h-288v48z" />
<glyph glyph-name="clipboard" unicode="&#xf328;" horiz-adv-x="384"
d="M336 384c26.5 0 48 -21.5 48 -48v-352c0 -26.5 -21.5 -48 -48 -48h-288c-26.5 0 -48 21.5 -48 48v352c0 26.5 21.5 48 48 48h80c0 35.2998 28.7002 64 64 64s64 -28.7002 64 -64h80zM192 408c-13.2998 0 -24 -10.7002 -24 -24s10.7002 -24 24 -24s24 10.7002 24 24
s-10.7002 24 -24 24zM336 -10v340c0 3.2998 -2.7002 6 -6 6h-42v-36c0 -6.59961 -5.40039 -12 -12 -12h-168c-6.59961 0 -12 5.40039 -12 12v36h-42c-3.2998 0 -6 -2.7002 -6 -6v-340c0 -3.2998 2.7002 -6 6 -6h276c3.2998 0 6 2.7002 6 6z" />
<glyph glyph-name="arrow-alt-circle-down" unicode="&#xf358;"
d="M256 440c137 0 248 -111 248 -248s-111 -248 -248 -248s-248 111 -248 248s111 248 248 248zM256 -8c110.5 0 200 89.5 200 200s-89.5 200 -200 200s-200 -89.5 -200 -200s89.5 -200 200 -200zM224 308c0 6.59961 5.40039 12 12 12h40c6.59961 0 12 -5.40039 12 -12v-116
h67c10.7002 0 16.0996 -12.9004 8.5 -20.5l-99 -99c-4.7002 -4.7002 -12.2998 -4.7002 -17 0l-99 99c-7.5 7.59961 -2.2002 20.5 8.5 20.5h67v116z" />
<glyph glyph-name="arrow-alt-circle-left" unicode="&#xf359;"
d="M8 192c0 137 111 248 248 248s248 -111 248 -248s-111 -248 -248 -248s-248 111 -248 248zM456 192c0 110.5 -89.5 200 -200 200s-200 -89.5 -200 -200s89.5 -200 200 -200s200 89.5 200 200zM384 212v-40c0 -6.59961 -5.40039 -12 -12 -12h-116v-67
c0 -10.7002 -12.9004 -16 -20.5 -8.5l-99 99c-4.7002 4.7002 -4.7002 12.2998 0 17l99 99c7.59961 7.59961 20.5 2.2002 20.5 -8.5v-67h116c6.59961 0 12 -5.40039 12 -12z" />
<glyph glyph-name="arrow-alt-circle-right" unicode="&#xf35a;"
d="M504 192c0 -137 -111 -248 -248 -248s-248 111 -248 248s111 248 248 248s248 -111 248 -248zM56 192c0 -110.5 89.5 -200 200 -200s200 89.5 200 200s-89.5 200 -200 200s-200 -89.5 -200 -200zM128 172v40c0 6.59961 5.40039 12 12 12h116v67
c0 10.7002 12.9004 16 20.5 8.5l99 -99c4.7002 -4.7002 4.7002 -12.2998 0 -17l-99 -99c-7.59961 -7.59961 -20.5 -2.2002 -20.5 8.5v67h-116c-6.59961 0 -12 5.40039 -12 12z" />
<glyph glyph-name="arrow-alt-circle-up" unicode="&#xf35b;"
d="M256 -56c-137 0 -248 111 -248 248s111 248 248 248s248 -111 248 -248s-111 -248 -248 -248zM256 392c-110.5 0 -200 -89.5 -200 -200s89.5 -200 200 -200s200 89.5 200 200s-89.5 200 -200 200zM276 64h-40c-6.59961 0 -12 5.40039 -12 12v116h-67
c-10.7002 0 -16 12.9004 -8.5 20.5l99 99c4.7002 4.7002 12.2998 4.7002 17 0l99 -99c7.59961 -7.59961 2.2002 -20.5 -8.5 -20.5h-67v-116c0 -6.59961 -5.40039 -12 -12 -12z" />
<glyph glyph-name="gem" unicode="&#xf3a5;" horiz-adv-x="576"
d="M464 448c4.09961 0 7.7998 -2 10.0996 -5.40039l99.9004 -147.199c2.90039 -4.40039 2.59961 -10.1006 -0.700195 -14.2002l-276 -340.8c-4.7998 -5.90039 -13.7998 -5.90039 -18.5996 0l-276 340.8c-3.2998 4 -3.60059 9.7998 -0.700195 14.2002l100 147.199
c2.2002 3.40039 6 5.40039 10 5.40039h352zM444.7 400h-56.7998l51.6992 -96h68.4004zM242.6 400l-51.5996 -96h194l-51.7002 96h-90.7002zM131.3 400l-63.2998 -96h68.4004l51.6992 96h-56.7998zM88.2998 256l119.7 -160l-68.2998 160h-51.4004zM191.2 256l96.7998 -243.3
l96.7998 243.3h-193.6zM368 96l119.6 160h-51.3994z" />
<glyph glyph-name="money-bill-alt" unicode="&#xf3d1;" horiz-adv-x="640"
d="M320 304c53.0195 0 96 -50.1396 96 -112c0 -61.8701 -43 -112 -96 -112c-53.0195 0 -96 50.1504 -96 112c0 61.8604 42.9805 112 96 112zM360 136v16c0 4.41992 -3.58008 8 -8 8h-16v88c0 4.41992 -3.58008 8 -8 8h-13.5801h-0.000976562
c-4.01074 0 -9.97266 -1.80566 -13.3086 -4.03027l-15.3301 -10.2197c-1.96777 -1.30957 -3.56445 -4.29004 -3.56445 -6.65332c0 -1.33691 0.601562 -3.32422 1.34375 -4.43652l8.88086 -13.3105c1.30859 -1.9668 4.29004 -3.56445 6.65332 -3.56445
c1.33691 0 3.32422 0.602539 4.43652 1.34473l0.469727 0.310547v-55.4404h-16c-4.41992 0 -8 -3.58008 -8 -8v-16c0 -4.41992 3.58008 -8 8 -8h64c4.41992 0 8 3.58008 8 8zM608 384c17.6699 0 32 -14.3301 32 -32v-320c0 -17.6699 -14.3301 -32 -32 -32h-576
c-17.6699 0 -32 14.3301 -32 32v320c0 17.6699 14.3301 32 32 32h576zM592 112v160c-35.3496 0 -64 28.6504 -64 64h-416c0 -35.3496 -28.6504 -64 -64 -64v-160c35.3496 0 64 -28.6504 64 -64h416c0 35.3496 28.6504 64 64 64z" />
<glyph glyph-name="window-close" unicode="&#xf410;"
d="M464 416c26.5 0 48 -21.5 48 -48v-352c0 -26.5 -21.5 -48 -48 -48h-416c-26.5 0 -48 21.5 -48 48v352c0 26.5 21.5 48 48 48h416zM464 22v340c0 3.2998 -2.7002 6 -6 6h-404c-3.2998 0 -6 -2.7002 -6 -6v-340c0 -3.2998 2.7002 -6 6 -6h404c3.2998 0 6 2.7002 6 6z
M356.5 253.4l-61.4004 -61.4004l61.4004 -61.4004c4.59961 -4.59961 4.59961 -12.0996 0 -16.7998l-22.2998 -22.2998c-4.60059 -4.59961 -12.1006 -4.59961 -16.7998 0l-61.4004 61.4004l-61.4004 -61.4004c-4.59961 -4.59961 -12.0996 -4.59961 -16.7998 0
l-22.2998 22.2998c-4.59961 4.60059 -4.59961 12.1006 0 16.7998l61.4004 61.4004l-61.4004 61.4004c-4.59961 4.59961 -4.59961 12.0996 0 16.7998l22.2998 22.2998c4.60059 4.59961 12.1006 4.59961 16.7998 0l61.4004 -61.4004l61.4004 61.4004
c4.59961 4.59961 12.0996 4.59961 16.7998 0l22.2998 -22.2998c4.7002 -4.60059 4.7002 -12.1006 0 -16.7998z" />
<glyph glyph-name="comment-dots" unicode="&#xf4ad;"
d="M144 240c17.7002 0 32 -14.2998 32 -32s-14.2998 -32 -32 -32s-32 14.2998 -32 32s14.2998 32 32 32zM256 240c17.7002 0 32 -14.2998 32 -32s-14.2998 -32 -32 -32s-32 14.2998 -32 32s14.2998 32 32 32zM368 240c17.7002 0 32 -14.2998 32 -32s-14.2998 -32 -32 -32
s-32 14.2998 -32 32s14.2998 32 32 32zM256 416c141.4 0 256 -93.0996 256 -208s-114.6 -208 -256 -208c-32.7998 0 -64 5.2002 -92.9004 14.2998c-29.0996 -20.5996 -77.5996 -46.2998 -139.1 -46.2998c-9.59961 0 -18.2998 5.7002 -22.0996 14.5
c-3.80078 8.7998 -2 19 4.59961 26c0.5 0.400391 31.5 33.7998 46.4004 73.2002c-33 35.0996 -52.9004 78.7002 -52.9004 126.3c0 114.9 114.6 208 256 208zM256 48c114.7 0 208 71.7998 208 160s-93.2998 160 -208 160s-208 -71.7998 -208 -160
c0 -42.2002 21.7002 -74.0996 39.7998 -93.4004l20.6006 -21.7998l-10.6006 -28.0996c-5.5 -14.5 -12.5996 -28.1006 -19.8994 -40.2002c23.5996 7.59961 43.1992 18.9004 57.5 29l19.5 13.7998l22.6992 -7.2002c25.3008 -8 51.7002 -12.0996 78.4004 -12.0996z" />
<glyph glyph-name="smile-wink" unicode="&#xf4da;" horiz-adv-x="496"
d="M248 440c137 0 248 -111 248 -248s-111 -248 -248 -248s-248 111 -248 248s111 248 248 248zM248 -8c110.3 0 200 89.7002 200 200s-89.7002 200 -200 200s-200 -89.7002 -200 -200s89.7002 -200 200 -200zM365.8 138.4c10.2002 -8.5 11.6006 -23.6006 3.10059 -33.8008
c-30 -36 -74.1006 -56.5996 -120.9 -56.5996s-90.9004 20.5996 -120.9 56.5996c-8.39941 10.2002 -7.09961 25.3008 3.10059 33.8008c10.0996 8.39941 25.2998 7.09961 33.7998 -3.10059c20.7998 -25.0996 51.5 -39.3994 84 -39.3994s63.2002 14.3994 84 39.3994
c8.5 10.2002 23.5996 11.6006 33.7998 3.10059zM168 208c-17.7002 0 -32 14.2998 -32 32s14.2998 32 32 32s32 -14.2998 32 -32s-14.2998 -32 -32 -32zM328 268c25.7002 0 55.9004 -16.9004 59.7002 -42.0996c1.7998 -11.1006 -11.2998 -18.2002 -19.7998 -10.8008l-9.5 8.5
c-14.8008 13.2002 -46.2002 13.2002 -61 0l-9.5 -8.5c-8.30078 -7.39941 -21.5 -0.399414 -19.8008 10.8008c4 25.1992 34.2002 42.0996 59.9004 42.0996z" />
<glyph glyph-name="angry" unicode="&#xf556;" horiz-adv-x="496"
d="M248 440c137 0 248 -111 248 -248s-111 -248 -248 -248s-248 111 -248 248s111 248 248 248zM248 -8c110.3 0 200 89.7002 200 200s-89.7002 200 -200 200s-200 -89.7002 -200 -200s89.7002 -200 200 -200zM248 136c33.5996 0 65.2002 -14.7998 86.7998 -40.5996
c8.40039 -10.2002 7.10059 -25.3008 -3.09961 -33.8008c-10.6006 -8.89941 -25.7002 -6.69922 -33.7998 3c-24.8008 29.7002 -75 29.7002 -99.8008 0c-8.5 -10.1992 -23.5996 -11.5 -33.7998 -3s-11.5996 23.6006 -3.09961 33.8008
c21.5996 25.7998 53.2002 40.5996 86.7998 40.5996zM200 208c0 -17.7002 -14.2998 -32.0996 -32 -32.0996s-32 14.2998 -32 32c0 6.19922 2.2002 11.6992 5.2998 16.5996l-28.2002 8.5c-12.6992 3.7998 -19.8994 17.2002 -16.0996 29.9004
c3.7998 12.6992 17.0996 20 29.9004 16.0996l80 -24c12.6992 -3.7998 19.8994 -17.2002 16.0996 -29.9004c-3.09961 -10.3994 -12.7002 -17.0996 -23 -17.0996zM399 262.9c3.7998 -12.7002 -3.40039 -26.1006 -16.0996 -29.8008l-28.2002 -8.5
c3.09961 -4.89941 5.2998 -10.3994 5.2998 -16.5996c0 -17.7002 -14.2998 -32 -32 -32s-32 14.2998 -32 32c-10.2998 0 -19.9004 6.7002 -23 17.0996c-3.7998 12.7002 3.40039 26.1006 16.0996 29.9004l80 24c12.8008 3.7998 26.1006 -3.40039 29.9004 -16.0996z" />
<glyph glyph-name="dizzy" unicode="&#xf567;" horiz-adv-x="496"
d="M248 440c137 0 248 -111 248 -248s-111 -248 -248 -248s-248 111 -248 248s111 248 248 248zM248 -8c110.3 0 200 89.7002 200 200s-89.7002 200 -200 200s-200 -89.7002 -200 -200s89.7002 -200 200 -200zM214.2 209.9
c-7.90039 -7.90039 -20.5 -7.90039 -28.4004 -0.200195l-17.7998 17.7998l-17.7998 -17.7998c-7.7998 -7.7998 -20.5 -7.7998 -28.2998 0c-7.80078 7.7998 -7.80078 20.5 0 28.2998l17.8994 17.9004l-17.8994 17.8994c-7.80078 7.7998 -7.80078 20.5 0 28.2998
c7.7998 7.80078 20.5 7.80078 28.2998 0l17.7998 -17.7998l17.9004 17.9004c7.7998 7.7998 20.5 7.7998 28.2998 0s7.7998 -20.5 0 -28.2998l-17.9004 -17.9004l17.9004 -17.7998c7.7998 -7.7998 7.7998 -20.5 0 -28.2998zM374.2 302.1
c7.7002 -7.7998 7.7002 -20.3994 0 -28.1992l-17.9004 -17.9004l17.7998 -18c7.80078 -7.7998 7.80078 -20.5 0 -28.2998c-7.7998 -7.7998 -20.5 -7.7998 -28.2998 0l-17.7998 17.7998l-17.7998 -17.7998c-7.7998 -7.7998 -20.5 -7.7998 -28.2998 0
c-7.80078 7.7998 -7.80078 20.5 0 28.2998l17.8994 17.9004l-17.8994 17.8994c-7.80078 7.7998 -7.80078 20.5 0 28.2998c7.7998 7.80078 20.5 7.80078 28.2998 0l17.7998 -17.7998l17.9004 17.7998c7.7998 7.80078 20.5 7.80078 28.2998 0zM248 176
c35.2998 0 64 -28.7002 64 -64s-28.7002 -64 -64 -64s-64 28.7002 -64 64s28.7002 64 64 64z" />
<glyph glyph-name="flushed" unicode="&#xf579;" horiz-adv-x="496"
d="M248 440c137 0 248 -111 248 -248s-111 -248 -248 -248s-248 111 -248 248s111 248 248 248zM248 -8c110.3 0 200 89.7002 200 200s-89.7002 200 -200 200s-200 -89.7002 -200 -200s89.7002 -200 200 -200zM344 304c44.2002 0 80 -35.7998 80 -80s-35.7998 -80 -80 -80
s-80 35.7998 -80 80s35.7998 80 80 80zM344 176c26.5 0 48 21.5 48 48s-21.5 48 -48 48s-48 -21.5 -48 -48s21.5 -48 48 -48zM344 248c13.2998 0 24 -10.7002 24 -24s-10.7002 -24 -24 -24s-24 10.7002 -24 24s10.7002 24 24 24zM232 224c0 -44.2002 -35.7998 -80 -80 -80
s-80 35.7998 -80 80s35.7998 80 80 80s80 -35.7998 80 -80zM152 176c26.5 0 48 21.5 48 48s-21.5 48 -48 48s-48 -21.5 -48 -48s21.5 -48 48 -48zM152 248c13.2998 0 24 -10.7002 24 -24s-10.7002 -24 -24 -24s-24 10.7002 -24 24s10.7002 24 24 24zM312 104
c13.2002 0 24 -10.7998 24 -24s-10.7998 -24 -24 -24h-128c-13.2002 0 -24 10.7998 -24 24s10.7998 24 24 24h128z" />
<glyph glyph-name="frown-open" unicode="&#xf57a;" horiz-adv-x="496"
d="M248 440c137 0 248 -111 248 -248s-111 -248 -248 -248s-248 111 -248 248s111 248 248 248zM248 -8c110.3 0 200 89.7002 200 200s-89.7002 200 -200 200s-200 -89.7002 -200 -200s89.7002 -200 200 -200zM200 240c0 -17.7002 -14.2998 -32 -32 -32s-32 14.2998 -32 32
s14.2998 32 32 32s32 -14.2998 32 -32zM328 272c17.7002 0 32 -14.2998 32 -32s-14.2998 -32 -32 -32s-32 14.2998 -32 32s14.2998 32 32 32zM248 160c35.5996 0 88.7998 -21.2998 95.7998 -61.2002c2 -11.7998 -9.09961 -21.5996 -20.5 -18.0996
c-31.2002 9.59961 -59.3994 15.2998 -75.2998 15.2998s-44.0996 -5.7002 -75.2998 -15.2998c-11.5 -3.40039 -22.5 6.2998 -20.5 18.0996c7 39.9004 60.2002 61.2002 95.7998 61.2002z" />
<glyph glyph-name="grimace" unicode="&#xf57f;" horiz-adv-x="496"
d="M248 440c137 0 248 -111 248 -248s-111 -248 -248 -248s-248 111 -248 248s111 248 248 248zM248 -8c110.3 0 200 89.7002 200 200s-89.7002 200 -200 200s-200 -89.7002 -200 -200s89.7002 -200 200 -200zM168 208c-17.7002 0 -32 14.2998 -32 32s14.2998 32 32 32
s32 -14.2998 32 -32s-14.2998 -32 -32 -32zM328 208c-17.7002 0 -32 14.2998 -32 32s14.2998 32 32 32s32 -14.2998 32 -32s-14.2998 -32 -32 -32zM344 192c26.5 0 48 -21.5 48 -48v-32c0 -26.5 -21.5 -48 -48 -48h-192c-26.5 0 -48 21.5 -48 48v32c0 26.5 21.5 48 48 48
h192zM176 96v24h-40v-8c0 -8.7998 7.2002 -16 16 -16h24zM176 136v24h-24c-8.7998 0 -16 -7.2002 -16 -16v-8h40zM240 96v24h-48v-24h48zM240 136v24h-48v-24h48zM304 96v24h-48v-24h48zM304 136v24h-48v-24h48zM360 112v8h-40v-24h24c8.7998 0 16 7.2002 16 16zM360 136v8
c0 8.7998 -7.2002 16 -16 16h-24v-24h40z" />
<glyph glyph-name="grin" unicode="&#xf580;" horiz-adv-x="496"
d="M248 440c137 0 248 -111 248 -248s-111 -248 -248 -248s-248 111 -248 248s111 248 248 248zM248 -8c110.3 0 200 89.7002 200 200s-89.7002 200 -200 200s-200 -89.7002 -200 -200s89.7002 -200 200 -200zM353.6 143.4c10 3.09961 19.3008 -5.5 17.7002 -15.3008
c-8 -47.0996 -71.2998 -80 -123.3 -80s-115.4 32.9004 -123.3 80c-1.7002 9.90039 7.7998 18.4004 17.7002 15.3008c26 -8.30078 64.3994 -13.1006 105.6 -13.1006s79.7002 4.7998 105.6 13.1006zM168 208c-17.7002 0 -32 14.2998 -32 32s14.2998 32 32 32
s32 -14.2998 32 -32s-14.2998 -32 -32 -32zM328 208c-17.7002 0 -32 14.2998 -32 32s14.2998 32 32 32s32 -14.2998 32 -32s-14.2998 -32 -32 -32z" />
<glyph glyph-name="grin-alt" unicode="&#xf581;" horiz-adv-x="496"
d="M200.3 200c-7.5 -11.4004 -24.5996 -12 -32.7002 0c-12.3994 18.7002 -15.1992 37.2998 -15.6992 56c0.599609 18.7002 3.2998 37.2998 15.6992 56c7.60059 11.4004 24.7002 12 32.7002 0c12.4004 -18.7002 15.2002 -37.2998 15.7002 -56
c-0.599609 -18.7002 -3.2998 -37.2998 -15.7002 -56zM328.3 200c-7.5 -11.4004 -24.5996 -12 -32.7002 0c-12.3994 18.7002 -15.1992 37.2998 -15.6992 56c0.599609 18.7002 3.2998 37.2998 15.6992 56c7.60059 11.4004 24.7002 12 32.7002 0
c12.4004 -18.7002 15.2002 -37.2998 15.7002 -56c-0.599609 -18.7002 -3.2998 -37.2998 -15.7002 -56zM248 440c137 0 248 -111 248 -248s-111 -248 -248 -248s-248 111 -248 248s111 248 248 248zM248 -8c110.3 0 200 89.7002 200 200s-89.7002 200 -200 200
s-200 -89.7002 -200 -200s89.7002 -200 200 -200zM353.6 143.4c10 3.09961 19.3008 -5.5 17.7002 -15.3008c-8 -47.0996 -71.2998 -80 -123.3 -80s-115.4 32.8008 -123.3 80c-1.7002 10 7.7998 18.4004 17.7002 15.3008c26 -8.30078 64.3994 -13.1006 105.6 -13.1006
s79.7002 4.7998 105.6 13.1006z" />
<glyph glyph-name="grin-beam" unicode="&#xf582;" horiz-adv-x="496"
d="M248 440c137 0 248 -111 248 -248s-111 -248 -248 -248s-248 111 -248 248s111 248 248 248zM248 -8c110.3 0 200 89.7002 200 200s-89.7002 200 -200 200s-200 -89.7002 -200 -200s89.7002 -200 200 -200zM353.6 143.4c10 3.09961 19.3008 -5.5 17.7002 -15.3008
c-8 -47.0996 -71.2998 -80 -123.3 -80s-115.4 32.9004 -123.3 80c-1.7002 10 7.89941 18.4004 17.7002 15.3008c26 -8.30078 64.3994 -13.1006 105.6 -13.1006s79.7002 4.7998 105.6 13.1006zM117.7 216.3c-3.60059 1.10059 -6 4.60059 -5.7002 8.2998
c3.2998 42.1006 32.2002 71.4004 56 71.4004s52.7002 -29.2998 56 -71.4004c0.299805 -3.7998 -2.09961 -7.19922 -5.7002 -8.2998c-3.09961 -1 -7.2002 0 -9.2998 3.7002l-9.5 17c-7.7002 13.7002 -19.2002 21.5996 -31.5 21.5996s-23.7998 -7.89941 -31.5 -21.5996
l-9.5 -17c-1.90039 -3.2002 -5.7998 -4.7998 -9.2998 -3.7002zM277.7 216.3c-3.60059 1.10059 -6 4.60059 -5.7002 8.2998c3.2998 42.1006 32.2002 71.4004 56 71.4004s52.7002 -29.2998 56 -71.4004c0.299805 -3.7998 -2.09961 -7.19922 -5.7002 -8.2998
c-3.09961 -1 -7.2002 0 -9.2998 3.7002l-9.5 17c-7.7002 13.7002 -19.2002 21.5996 -31.5 21.5996s-23.7998 -7.89941 -31.5 -21.5996l-9.5 -17c-1.90039 -3.2002 -5.7998 -4.7998 -9.2998 -3.7002z" />
<glyph glyph-name="grin-beam-sweat" unicode="&#xf583;" horiz-adv-x="496"
d="M440 288c-29.5 0 -53.2998 26.2998 -53.2998 58.7002c0 25 31.7002 75.5 46.2002 97.2998c3.5 5.2998 10.5996 5.2998 14.1992 0c14.5 -21.7998 46.2002 -72.2998 46.2002 -97.2998c0 -32.4004 -23.7998 -58.7002 -53.2998 -58.7002zM248 48
c-51.9004 0 -115.3 32.9004 -123.3 80c-1.7002 10 7.89941 18.4004 17.7002 15.2998c26 -8.2998 64.3994 -13.0996 105.6 -13.0996s79.7002 4.7998 105.6 13.0996c10 3.2002 19.4004 -5.39941 17.7002 -15.2998c-8 -47.0996 -71.3994 -80 -123.3 -80zM378.3 216.3
c-3.09961 -0.899414 -7.2002 0.100586 -9.2998 3.7002l-9.5 17c-7.7002 13.7002 -19.2002 21.5996 -31.5 21.5996s-23.7998 -7.89941 -31.5 -21.5996l-9.5 -17c-1.90039 -3.2002 -5.7998 -4.7998 -9.2998 -3.7002c-3.60059 1.10059 -6 4.60059 -5.7002 8.2998
c3.2998 42.1006 32.2002 71.4004 56 71.4004s52.7002 -29.2998 56 -71.4004c0.299805 -3.7998 -2.09961 -7.19922 -5.7002 -8.2998zM483.6 269.2c8 -24.2998 12.4004 -50.2002 12.4004 -77.2002c0 -137 -111 -248 -248 -248s-248 111 -248 248s111 248 248 248
c45.7002 0 88.4004 -12.5996 125.2 -34.2002c-10.9004 -21.5996 -15.5 -36.2002 -17.2002 -45.7002c-31.2002 20.1006 -68.2002 31.9004 -108 31.9004c-110.3 0 -200 -89.7002 -200 -200s89.7002 -200 200 -200s200 89.7002 200 200
c0 22.5 -3.90039 44.0996 -10.7998 64.2998c0.399414 0 21.7998 -2.7998 46.3994 12.9004zM168 258.6c-12.2998 0 -23.7998 -7.7998 -31.5 -21.5996l-9.5 -17c-1.90039 -3.2002 -5.7998 -4.7998 -9.2998 -3.7002c-3.60059 1.10059 -6 4.60059 -5.7002 8.2998
c3.2998 42.1006 32.2002 71.4004 56 71.4004s52.7002 -29.2998 56 -71.4004c0.299805 -3.7998 -2.09961 -7.19922 -5.7002 -8.2998c-3.09961 -1 -7.2002 0 -9.2998 3.7002l-9.5 17c-7.7002 13.7002 -19.2002 21.5996 -31.5 21.5996z" />
<glyph glyph-name="grin-hearts" unicode="&#xf584;" horiz-adv-x="496"
d="M353.6 143.4c10 3.09961 19.3008 -5.5 17.7002 -15.3008c-8 -47.0996 -71.2998 -80 -123.3 -80s-115.4 32.8008 -123.3 80c-1.7002 10 7.89941 18.4004 17.7002 15.3008c26 -8.30078 64.3994 -13.1006 105.6 -13.1006s79.7002 4.7998 105.6 13.1006zM200.8 192.3
l-70.2002 18.1006c-20.3994 5.2998 -31.8994 27 -24.1992 47.1992c6.69922 17.7002 26.6992 26.7002 44.8994 22l7.10059 -1.89941l2 7.09961c5.09961 18.1006 22.8994 30.9004 41.5 27.9004c21.3994 -3.40039 34.3994 -24.2002 28.7998 -44.5l-19.4004 -69.9004
c-1.2998 -4.5 -6 -7.2002 -10.5 -6zM389.6 257.6c7.7002 -20.1992 -3.7998 -41.7998 -24.1992 -47.0996l-70.2002 -18.2002c-4.60059 -1.2002 -9.2998 1.5 -10.5 6l-19.4004 69.9004c-5.59961 20.2998 7.40039 41.0996 28.7998 44.5c18.7002 3 36.5 -9.7998 41.5 -27.9004
l2 -7.09961l7.10059 1.89941c18.2002 4.7002 38.2002 -4.39941 44.8994 -22zM248 440c137 0 248 -111 248 -248s-111 -248 -248 -248s-248 111 -248 248s111 248 248 248zM248 -8c110.3 0 200 89.7002 200 200s-89.7002 200 -200 200s-200 -89.7002 -200 -200
s89.7002 -200 200 -200z" />
<glyph glyph-name="grin-squint" unicode="&#xf585;" horiz-adv-x="496"
d="M248 440c137 0 248 -111 248 -248s-111 -248 -248 -248s-248 111 -248 248s111 248 248 248zM248 -8c110.3 0 200 89.7002 200 200s-89.7002 200 -200 200s-200 -89.7002 -200 -200s89.7002 -200 200 -200zM353.6 143.4c10 3.09961 19.3008 -5.5 17.7002 -15.3008
c-8 -47.0996 -71.2998 -80 -123.3 -80s-115.4 32.9004 -123.3 80c-1.7002 9.90039 7.7998 18.4004 17.7002 15.3008c26 -8.30078 64.3994 -13.1006 105.6 -13.1006s79.7002 4.7998 105.6 13.1006zM118.9 184.2c-3.80078 4.39941 -3.90039 11 -0.100586 15.5l33.6006 40.2998
l-33.6006 40.2998c-3.7002 4.5 -3.7002 11 0.100586 15.5c3.89941 4.40039 10.1992 5.5 15.2998 2.5l80 -48c3.59961 -2.2002 5.7998 -6.09961 5.7998 -10.2998s-2.2002 -8.09961 -5.7998 -10.2998l-80 -48c-5.40039 -3.2002 -11.7002 -1.7002 -15.2998 2.5zM361.8 181.7
l-80 48c-3.59961 2.2002 -5.7998 6.09961 -5.7998 10.2998s2.2002 8.09961 5.7998 10.2998l80 48c5.10059 2.90039 11.5 1.90039 15.2998 -2.5c3.80078 -4.5 3.90039 -11 0.100586 -15.5l-33.6006 -40.2998l33.6006 -40.2998c3.7002 -4.5 3.7002 -11 -0.100586 -15.5
c-3.59961 -4.2002 -9.89941 -5.7002 -15.2998 -2.5z" />
<glyph glyph-name="grin-squint-tears" unicode="&#xf586;"
d="M117.1 63.9004c6.30078 0.899414 11.7002 -4.5 10.9004 -10.9004c-3.7002 -25.7998 -13.7002 -84 -30.5996 -100.9c-22 -21.8994 -57.9004 -21.5 -80.3008 0.900391c-22.3994 22.4004 -22.7998 58.4004 -0.899414 80.2998
c16.8994 16.9004 75.0996 26.9004 100.899 30.6006zM75.9004 105.6c-19.6006 -3.89941 -35.1006 -8.09961 -47.3008 -12.1992c-39.2998 90.5996 -22.0996 199.899 52 274c48.5 48.3994 111.9 72.5996 175.4 72.5996c38.9004 0 77.7998 -9.2002 113.2 -27.4004
c-4 -12.1992 -8.2002 -28 -12 -48.2998c-30.4004 17.9004 -65 27.7002 -101.2 27.7002c-53.4004 0 -103.6 -20.7998 -141.4 -58.5996c-61.5996 -61.5 -74.2998 -153.4 -38.6992 -227.801zM428.2 293.2c20.2998 3.89941 36.2002 8 48.5 12
c47.8994 -93.2002 32.8994 -210.5 -45.2002 -288.601c-48.5 -48.3994 -111.9 -72.5996 -175.4 -72.5996c-33.6992 0 -67.2998 7 -98.6992 20.5996c4.19922 12.2002 8.2998 27.7002 12.1992 47.2002c26.6006 -12.7998 55.9004 -19.7998 86.4004 -19.7998
c53.4004 0 103.6 20.7998 141.4 58.5996c65.6992 65.7002 75.7998 166 30.7998 242.601zM394.9 320.1c-6.30078 -0.899414 -11.7002 4.5 -10.9004 10.9004c3.7002 25.7998 13.7002 84 30.5996 100.9c22 21.8994 57.9004 21.5 80.3008 -0.900391
c22.3994 -22.4004 22.7998 -58.4004 0.899414 -80.2998c-16.8994 -16.9004 -75.0996 -26.9004 -100.899 -30.6006zM207.9 211.8c3 -3 4.19922 -7.2998 3.19922 -11.5l-22.5996 -90.5c-1.40039 -5.39941 -6.2002 -9.09961 -11.7002 -9.09961h-0.899414
c-5.80078 0.5 -10.5 5.09961 -11 10.8994l-4.80078 52.3008l-52.2998 4.7998c-5.7998 0.5 -10.3994 5.2002 -10.8994 11c-0.400391 5.89941 3.39941 11.2002 9.09961 12.5996l90.5 22.7002c4.2002 1 8.40039 -0.200195 11.4004 -3.2002zM247.6 236.9
c-0.0996094 0 -6.39941 -1.80078 -11.3994 3.19922c-3 3 -4.2002 7.30078 -3.2002 11.4004l22.5996 90.5c1.40039 5.7002 7 9.2002 12.6006 9.09961c5.7998 -0.5 10.5 -5.09961 11 -10.8994l4.7998 -52.2998l52.2998 -4.80078c5.7998 -0.5 10.4004 -5.19922 10.9004 -11
c0.399414 -5.89941 -3.40039 -11.1992 -9.10059 -12.5996zM299.6 148.4c29.1006 29.0996 53 59.5996 65.3008 83.7998c4.89941 9.2998 17.5996 9.89941 23.3994 1.7002c27.7002 -38.9004 6.10059 -106.9 -30.5996 -143.7s-104.8 -58.2998 -143.7 -30.6006
c-8.2998 5.90039 -7.5 18.6006 1.7002 23.4004c24.2002 12.5 54.7998 36.2998 83.8994 65.4004z" />
<glyph glyph-name="grin-stars" unicode="&#xf587;" horiz-adv-x="496"
d="M248 440c137 0 248 -111 248 -248s-111 -248 -248 -248s-248 111 -248 248s111 248 248 248zM248 -8c110.3 0 200 89.7002 200 200s-89.7002 200 -200 200s-200 -89.7002 -200 -200s89.7002 -200 200 -200zM353.6 143.4c10 3.09961 19.3008 -5.5 17.7002 -15.3008
c-8 -47.0996 -71.2998 -80 -123.3 -80s-115.4 32.8008 -123.3 80c-1.7002 10 7.89941 18.4004 17.7002 15.3008c26 -8.30078 64.3994 -13.1006 105.6 -13.1006s79.7002 4.7998 105.6 13.1006zM125.7 200.9l6.09961 34.8994l-25.3994 24.6006
c-4.60059 4.59961 -1.90039 12.2998 4.2998 13.1992l34.8994 5l15.5 31.6006c2.90039 5.7998 11 5.7998 13.9004 0l15.5 -31.6006l34.9004 -5c6.19922 -1 8.7998 -8.69922 4.2998 -13.1992l-25.4004 -24.6006l6 -34.8994c1 -6.2002 -5.39941 -11 -11 -7.90039
l-31.2998 16.2998l-31.2998 -16.2998c-5.60059 -3.09961 -12 1.7002 -11 7.90039zM385.4 273.6c6.19922 -1 8.89941 -8.59961 4.39941 -13.1992l-25.3994 -24.6006l6 -34.8994c1 -6.2002 -5.40039 -11 -11 -7.90039l-31.3008 16.2998l-31.2998 -16.2998
c-5.59961 -3.09961 -12 1.7002 -11 7.90039l6 34.8994l-25.3994 24.6006c-4.60059 4.59961 -1.90039 12.2998 4.2998 13.1992l34.8994 5l15.5 31.6006c2.90039 5.7998 11 5.7998 13.9004 0l15.5 -31.6006z" />
<glyph glyph-name="grin-tears" unicode="&#xf588;" horiz-adv-x="640"
d="M117.1 191.9c6.30078 0.899414 11.7002 -4.5 10.9004 -10.9004c-3.7002 -25.7998 -13.7002 -84 -30.5996 -100.9c-22 -21.8994 -57.9004 -21.5 -80.3008 0.900391c-22.3994 22.4004 -22.7998 58.4004 -0.899414 80.2998c16.8994 16.9004 75.0996 26.9004 100.899 30.6006
zM623.8 161.3c21.9004 -21.8994 21.5 -57.8994 -0.799805 -80.2002c-22.4004 -22.3994 -58.4004 -22.7998 -80.2998 -0.899414c-16.9004 16.8994 -26.9004 75.0996 -30.6006 100.899c-0.899414 6.30078 4.5 11.7002 10.8008 10.8008
c25.7998 -3.7002 84 -13.7002 100.899 -30.6006zM497.2 99.5996c12.3994 -37.2998 25.0996 -43.7998 28.2998 -46.5c-44.5996 -65.7998 -120 -109.1 -205.5 -109.1s-160.9 43.2998 -205.5 109.1c3.09961 2.60059 15.7998 9.10059 28.2998 46.5
c33.4004 -63.8994 100.3 -107.6 177.2 -107.6s143.8 43.7002 177.2 107.6zM122.7 223.5c-2.40039 0.299805 -5 2.5 -49.5 -6.90039c12.3994 125.4 118.1 223.4 246.8 223.4s234.4 -98 246.8 -223.5c-44.2998 9.40039 -47.3994 7.2002 -49.5 7
c-15.2002 95.2998 -97.7998 168.5 -197.3 168.5s-182.1 -73.2002 -197.3 -168.5zM320 48c-51.9004 0 -115.3 32.9004 -123.3 80c-1.7002 10 7.89941 18.4004 17.7002 15.2998c26 -8.2998 64.3994 -13.0996 105.6 -13.0996s79.7002 4.7998 105.6 13.0996
c10 3.2002 19.4004 -5.39941 17.7002 -15.2998c-8 -47.0996 -71.3994 -80 -123.3 -80zM450.3 216.3c-3.09961 -0.899414 -7.2002 0.100586 -9.2998 3.7002l-9.5 17c-7.7002 13.7002 -19.2002 21.5996 -31.5 21.5996s-23.7998 -7.89941 -31.5 -21.5996l-9.5 -17
c-1.90039 -3.2002 -5.7998 -4.7998 -9.2998 -3.7002c-3.60059 1.10059 -6 4.60059 -5.7002 8.2998c3.2998 42.1006 32.2002 71.4004 56 71.4004s52.7002 -29.2998 56 -71.4004c0.299805 -3.7998 -2.09961 -7.19922 -5.7002 -8.2998zM240 258.6
c-12.2998 0 -23.7998 -7.7998 -31.5 -21.5996l-9.5 -17c-1.90039 -3.2002 -5.7998 -4.7998 -9.2998 -3.7002c-3.60059 1.10059 -6 4.60059 -5.7002 8.2998c3.2998 42.1006 32.2002 71.4004 56 71.4004s52.7002 -29.2998 56 -71.4004
c0.299805 -3.7998 -2.09961 -7.19922 -5.7002 -8.2998c-3.09961 -1 -7.2002 0 -9.2998 3.7002l-9.5 17c-7.7002 13.7002 -19.2002 21.5996 -31.5 21.5996z" />
<glyph glyph-name="grin-tongue" unicode="&#xf589;" horiz-adv-x="496"
d="M248 440c137 0 248 -111 248 -248s-111 -248 -248 -248s-248 111 -248 248s111 248 248 248zM312 40h0.0996094v43.7998l-17.6992 8.7998c-15.1006 7.60059 -31.5 -1.69922 -34.9004 -16.5l-2.7998 -12.0996c-2.10059 -9.2002 -15.2002 -9.2002 -17.2998 0
l-2.80078 12.0996c-3.39941 14.8008 -19.8994 24 -34.8994 16.5l-17.7002 -8.7998v-42.7998c0 -35.2002 28 -64.5 63.0996 -65c35.8008 -0.5 64.9004 28.4004 64.9004 64zM340.2 14.7002c64 33.3994 107.8 100.3 107.8 177.3c0 110.3 -89.7002 200 -200 200
s-200 -89.7002 -200 -200c0 -77 43.7998 -143.9 107.8 -177.3c-2.2002 8.09961 -3.7998 16.5 -3.7998 25.2998v43.5c-14.2002 12.4004 -24.4004 27.5 -27.2998 44.5c-1.7002 10 7.7998 18.4004 17.7002 15.2998c26 -8.2998 64.3994 -13.0996 105.6 -13.0996
s79.7002 4.7998 105.6 13.0996c10 3.2002 19.4004 -5.39941 17.7002 -15.2998c-2.89941 -17 -13.0996 -32.0996 -27.2998 -44.5v-43.5c0 -8.7998 -1.59961 -17.2002 -3.7998 -25.2998zM168 272c17.7002 0 32 -14.2998 32 -32s-14.2998 -32 -32 -32s-32 14.2998 -32 32
s14.2998 32 32 32zM328 272c17.7002 0 32 -14.2998 32 -32s-14.2998 -32 -32 -32s-32 14.2998 -32 32s14.2998 32 32 32z" />
<glyph glyph-name="grin-tongue-squint" unicode="&#xf58a;" horiz-adv-x="496"
d="M248 440c137 0 248 -111 248 -248s-111 -248 -248 -248s-248 111 -248 248s111 248 248 248zM312 40h0.0996094v43.7998l-17.6992 8.7998c-15.1006 7.60059 -31.5 -1.69922 -34.9004 -16.5l-2.7998 -12.0996c-2.10059 -9.2002 -15.2002 -9.2002 -17.2998 0
l-2.80078 12.0996c-3.39941 14.8008 -19.8994 24 -34.8994 16.5l-17.7002 -8.7998v-42.7998c0 -35.2002 28 -64.5 63.0996 -65c35.8008 -0.5 64.9004 28.4004 64.9004 64zM340.2 14.7002c64 33.3994 107.8 100.3 107.8 177.3c0 110.3 -89.7002 200 -200 200
s-200 -89.7002 -200 -200c0 -77 43.7998 -143.9 107.8 -177.3c-2.2002 8.09961 -3.7998 16.5 -3.7998 25.2998v43.5c-14.2002 12.4004 -24.4004 27.5 -27.2998 44.5c-1.7002 10 7.7998 18.4004 17.7002 15.2998c26 -8.2998 64.3994 -13.0996 105.6 -13.0996
s79.7002 4.7998 105.6 13.0996c10 3.2002 19.4004 -5.39941 17.7002 -15.2998c-2.89941 -17 -13.0996 -32.0996 -27.2998 -44.5v-43.5c0 -8.7998 -1.59961 -17.2002 -3.7998 -25.2998zM377.1 295.8c3.80078 -4.39941 3.90039 -11 0.100586 -15.5l-33.6006 -40.2998
l33.6006 -40.2998c3.7002 -4.5 3.7002 -11 -0.100586 -15.5c-3.59961 -4.2002 -9.89941 -5.7002 -15.2998 -2.5l-80 48c-3.59961 2.2002 -5.7998 6.09961 -5.7998 10.2998s2.2002 8.09961 5.7998 10.2998l80 48c5 3 11.5 1.90039 15.2998 -2.5zM214.2 250.3
c3.59961 -2.2002 5.7998 -6.09961 5.7998 -10.2998s-2.2002 -8.09961 -5.7998 -10.2998l-80 -48c-5.40039 -3.2002 -11.7002 -1.7002 -15.2998 2.5c-3.80078 4.5 -3.90039 11 -0.100586 15.5l33.6006 40.2998l-33.6006 40.2998c-3.7002 4.5 -3.7002 11 0.100586 15.5
c3.89941 4.5 10.2998 5.5 15.2998 2.5z" />
<glyph glyph-name="grin-tongue-wink" unicode="&#xf58b;" horiz-adv-x="496"
d="M152 268c25.7002 0 55.9004 -16.9004 59.7998 -42.0996c0.799805 -5 -1.7002 -10 -6.09961 -12.4004c-5.7002 -3.09961 -11.2002 -0.599609 -13.7002 1.59961l-9.5 8.5c-14.7998 13.2002 -46.2002 13.2002 -61 0l-9.5 -8.5
c-3.7998 -3.39941 -9.2998 -4 -13.7002 -1.59961c-4.39941 2.40039 -6.89941 7.40039 -6.09961 12.4004c3.89941 25.1992 34.0996 42.0996 59.7998 42.0996zM328 320c44.2002 0 80 -35.7998 80 -80s-35.7998 -80 -80 -80s-80 35.7998 -80 80s35.7998 80 80 80zM328 192
c26.5 0 48 21.5 48 48s-21.5 48 -48 48s-48 -21.5 -48 -48s21.5 -48 48 -48zM328 264c13.2998 0 24 -10.7002 24 -24s-10.7002 -24 -24 -24s-24 10.7002 -24 24s10.7002 24 24 24zM248 440c137 0 248 -111 248 -248s-111 -248 -248 -248s-248 111 -248 248s111 248 248 248z
M312 40h0.0996094v43.7998l-17.6992 8.7998c-15.1006 7.60059 -31.5 -1.69922 -34.9004 -16.5l-2.7998 -12.0996c-2.10059 -9.2002 -15.2002 -9.2002 -17.2998 0l-2.80078 12.0996c-3.39941 14.8008 -19.8994 24 -34.8994 16.5l-17.7002 -8.7998v-42.7998
c0 -35.2002 28 -64.5 63.0996 -65c35.8008 -0.5 64.9004 28.4004 64.9004 64zM340.2 14.7002c64 33.3994 107.8 100.3 107.8 177.3c0 110.3 -89.7002 200 -200 200s-200 -89.7002 -200 -200c0 -77 43.7998 -143.9 107.8 -177.3
c-2.2002 8.09961 -3.7998 16.5 -3.7998 25.2998v43.5c-14.2002 12.4004 -24.4004 27.5 -27.2998 44.5c-1.7002 10 7.7998 18.4004 17.7002 15.2998c26 -8.2998 64.3994 -13.0996 105.6 -13.0996s79.7002 4.7998 105.6 13.0996c10 3.2002 19.4004 -5.39941 17.7002 -15.2998
c-2.89941 -17 -13.0996 -32.0996 -27.2998 -44.5v-43.5c0 -8.7998 -1.59961 -17.2002 -3.7998 -25.2998z" />
<glyph glyph-name="grin-wink" unicode="&#xf58c;" horiz-adv-x="496"
d="M328 268c25.6904 0 55.8799 -16.9199 59.8701 -42.1201c1.72949 -11.0898 -11.3506 -18.2695 -19.8301 -10.8398l-9.5498 8.47949c-14.8105 13.1904 -46.1602 13.1904 -60.9707 0l-9.5498 -8.47949c-8.33008 -7.40039 -21.5801 -0.379883 -19.8301 10.8398
c3.98047 25.2002 34.1699 42.1201 59.8604 42.1201zM168 208c-17.6699 0 -32 14.3301 -32 32s14.3301 32 32 32s32 -14.3301 32 -32s-14.3301 -32 -32 -32zM353.55 143.36c10.04 3.13965 19.3906 -5.4502 17.71 -15.3408
c-7.92969 -47.1494 -71.3193 -80.0195 -123.26 -80.0195s-115.33 32.8701 -123.26 80.0195c-1.69043 9.9707 7.76953 18.4707 17.71 15.3408c25.9297 -8.31055 64.3994 -13.0605 105.55 -13.0605s79.6201 4.75977 105.55 13.0605zM248 440c136.97 0 248 -111.03 248 -248
s-111.03 -248 -248 -248s-248 111.03 -248 248s111.03 248 248 248zM248 -8c110.28 0 200 89.7197 200 200s-89.7197 200 -200 200s-200 -89.7197 -200 -200s89.7197 -200 200 -200z" />
<glyph glyph-name="kiss" unicode="&#xf596;" horiz-adv-x="496"
d="M168 272c17.7002 0 32 -14.2998 32 -32s-14.2998 -32 -32 -32s-32 14.2998 -32 32s14.2998 32 32 32zM304 140c0 -13 -13.4004 -27.2998 -35.0996 -36.4004c21.7998 -8.69922 35.1992 -23 35.1992 -36c0 -19.1992 -28.6992 -41.5 -71.5 -44h-0.5
c-3.69922 0 -7 2.60059 -7.7998 6.2002c-0.899414 3.7998 1.10059 7.7002 4.7002 9.2002l17 7.2002c12.9004 5.5 20.7002 13.5 20.7002 21.5s-7.7998 16 -20.7998 21.5l-16.9004 7.2002c-6 2.59961 -5.7002 12.3994 0 14.7998l17 7.2002
c12.9004 5.5 20.7002 13.5 20.7002 21.5s-7.7998 16 -20.7998 21.5l-16.9004 7.19922c-3.59961 1.5 -5.59961 5.40039 -4.7002 9.2002c0.799805 3.7998 4.40039 6.60059 8.2002 6.2002c42.7002 -2.5 71.5 -24.7998 71.5 -44zM248 440c137 0 248 -111 248 -248
s-111 -248 -248 -248s-248 111 -248 248s111 248 248 248zM248 -8c110.3 0 200 89.7002 200 200s-89.7002 200 -200 200s-200 -89.7002 -200 -200s89.7002 -200 200 -200zM328 272c17.7002 0 32 -14.2998 32 -32s-14.2998 -32 -32 -32s-32 14.2998 -32 32s14.2998 32 32 32z
" />
<glyph glyph-name="kiss-beam" unicode="&#xf597;" horiz-adv-x="496"
d="M168 296c23.7998 0 52.7002 -29.2998 55.7998 -71.4004c0.299805 -3.7998 -2 -7.19922 -5.59961 -8.2998c-3.10059 -1 -7.2002 0 -9.2998 3.7002l-9.5 17c-7.7002 13.7002 -19.2002 21.5996 -31.5 21.5996c-12.3008 0 -23.8008 -7.89941 -31.5 -21.5996l-9.5 -17
c-1.80078 -3.2002 -5.80078 -4.7002 -9.30078 -3.7002c-3.59961 1.10059 -5.89941 4.60059 -5.59961 8.2998c3.2998 42.1006 32.2002 71.4004 56 71.4004zM248 440c137 0 248 -111 248 -248s-111 -248 -248 -248s-248 111 -248 248s111 248 248 248zM248 -8
c110.3 0 200 89.7002 200 200s-89.7002 200 -200 200s-200 -89.7002 -200 -200s89.7002 -200 200 -200zM304 140c0 -13 -13.4004 -27.2998 -35.0996 -36.4004c21.7998 -8.69922 35.1992 -23 35.1992 -36c0 -19.1992 -28.6992 -41.5 -71.5 -44h-0.5
c-3.69922 0 -7 2.60059 -7.7998 6.2002c-0.899414 3.7998 1.10059 7.7002 4.7002 9.2002l17 7.2002c12.9004 5.5 20.7002 13.5 20.7002 21.5s-7.7998 16 -20.7998 21.5l-16.9004 7.2002c-6 2.59961 -5.7002 12.3994 0 14.7998l17 7.2002
c12.9004 5.5 20.7002 13.5 20.7002 21.5s-7.7998 16 -20.7998 21.5l-16.9004 7.19922c-3.59961 1.5 -5.59961 5.40039 -4.7002 9.2002c0.799805 3.7998 4.40039 6.60059 8.2002 6.2002c42.7002 -2.5 71.5 -24.7998 71.5 -44zM328 296
c23.7998 0 52.7002 -29.2998 55.7998 -71.4004c0.299805 -3.7998 -2 -7.19922 -5.59961 -8.2998c-3.10059 -1 -7.2002 0 -9.2998 3.7002l-9.5 17c-7.7002 13.7002 -19.2002 21.5996 -31.5 21.5996c-12.3008 0 -23.8008 -7.89941 -31.5 -21.5996l-9.5 -17
c-1.80078 -3.2002 -5.80078 -4.7002 -9.30078 -3.7002c-3.59961 1.10059 -5.89941 4.60059 -5.59961 8.2998c3.2998 42.1006 32.2002 71.4004 56 71.4004z" />
<glyph glyph-name="kiss-wink-heart" unicode="&#xf598;" horiz-adv-x="504"
d="M304 139.5c0 -13 -13.4004 -27.2998 -35.0996 -36.4004c21.7998 -8.69922 35.1992 -23 35.1992 -36c0 -19.1992 -28.6992 -41.5 -71.5 -44h-0.5c-3.69922 0 -7 2.60059 -7.7998 6.2002c-0.899414 3.7998 1.10059 7.7002 4.7002 9.2002l17 7.2002
c12.9004 5.5 20.7002 13.5 20.7002 21.5s-7.7998 16 -20.7998 21.5l-16.9004 7.2002c-6 2.59961 -5.7002 12.3994 0 14.7998l17 7.2002c12.9004 5.5 20.7002 13.5 20.7002 21.5s-7.7998 16 -20.7998 21.5l-16.9004 7.19922c-3.59961 1.5 -5.59961 5.40039 -4.7002 9.2002
c0.799805 3.7998 4.40039 6.60059 8.2002 6.2002c42.7002 -2.5 71.5 -24.7998 71.5 -44zM374.5 223c-14.7998 13.2002 -46.2002 13.2002 -61 0l-9.5 -8.5c-2.5 -2.2998 -7.90039 -4.7002 -13.7002 -1.59961c-4.39941 2.39941 -6.89941 7.39941 -6.09961 12.3994
c3.89941 25.2002 34.2002 42.1006 59.7998 42.1006s55.7998 -16.9004 59.7998 -42.1006c0.799805 -5 -1.7002 -10 -6.09961 -12.3994c-4.40039 -2.40039 -9.90039 -1.7002 -13.7002 1.59961zM136 239.5c0 17.7002 14.2998 32 32 32s32 -14.2998 32 -32s-14.2998 -32 -32 -32
s-32 14.2998 -32 32zM501.1 45.5c9.2002 -23.9004 -4.39941 -49.4004 -28.5 -55.7002l-83 -21.5c-5.39941 -1.39941 -10.8994 1.7998 -12.3994 7.10059l-22.9004 82.5996c-6.59961 24 8.7998 48.5996 34 52.5996c22 3.5 43.1006 -11.5996 49 -33l2.2998 -8.39941
l8.40039 2.2002c21.5996 5.59961 45.0996 -5.10059 53.0996 -25.9004zM334 11.7002c17.7002 -64 10.9004 -39.5 13.4004 -46.7998c-30.5 -13.4004 -64 -20.9004 -99.4004 -20.9004c-137 0 -248 111 -248 248s111 248 248 248s248 -111 247.9 -248
c0 -31.7998 -6.2002 -62.0996 -17.1006 -90c-6 1.5 -12.2002 2.7998 -18.5996 2.90039c-5.60059 9.69922 -13.6006 17.5 -22.6006 23.8994c6.7002 19.9004 10.4004 41.1006 10.4004 63.2002c0 110.3 -89.7002 200 -200 200s-200 -89.7002 -200 -200s89.7002 -200 200 -200
c30.7998 0 59.9004 7.2002 86 19.7002z" />
<glyph glyph-name="laugh" unicode="&#xf599;" horiz-adv-x="496"
d="M248 440c137 0 248 -111 248 -248s-111 -248 -248 -248s-248 111 -248 248s111 248 248 248zM389.4 50.5996c37.7998 37.8008 58.5996 88 58.5996 141.4s-20.7998 103.6 -58.5996 141.4c-37.8008 37.7998 -88 58.5996 -141.4 58.5996s-103.6 -20.7998 -141.4 -58.5996
c-37.7998 -37.8008 -58.5996 -88 -58.5996 -141.4s20.7998 -103.6 58.5996 -141.4c37.8008 -37.7998 88 -58.5996 141.4 -58.5996s103.6 20.7998 141.4 58.5996zM328 224c-17.7002 0 -32 14.2998 -32 32s14.2998 32 32 32s32 -14.2998 32 -32s-14.2998 -32 -32 -32zM168 224
c-17.7002 0 -32 14.2998 -32 32s14.2998 32 32 32s32 -14.2998 32 -32s-14.2998 -32 -32 -32zM362.4 160c8.19922 0 14.5 -7 13.5 -15c-7.5 -59.2002 -58.9004 -105 -121.101 -105h-13.5996c-62.2002 0 -113.601 45.7998 -121.101 105c-1 8 5.30078 15 13.5 15h228.801z" />
<glyph glyph-name="laugh-beam" unicode="&#xf59a;" horiz-adv-x="496"
d="M248 440c137 0 248 -111 248 -248s-111 -248 -248 -248s-248 111 -248 248s111 248 248 248zM389.4 50.5996c37.7998 37.8008 58.5996 88 58.5996 141.4s-20.7998 103.6 -58.5996 141.4c-37.8008 37.7998 -88 58.5996 -141.4 58.5996s-103.6 -20.7998 -141.4 -58.5996
c-37.7998 -37.8008 -58.5996 -88 -58.5996 -141.4s20.7998 -103.6 58.5996 -141.4c37.8008 -37.7998 88 -58.5996 141.4 -58.5996s103.6 20.7998 141.4 58.5996zM328 296c23.7998 0 52.7002 -29.2998 55.7998 -71.4004c0.700195 -8.5 -10.7998 -11.8994 -14.8994 -4.5
l-9.5 17c-7.7002 13.7002 -19.2002 21.6006 -31.5 21.6006c-12.3008 0 -23.8008 -7.90039 -31.5 -21.6006l-9.5 -17c-4.10059 -7.39941 -15.6006 -4.09961 -14.9004 4.5c3.2998 42.1006 32.2002 71.4004 56 71.4004zM127 220.1c-4.2002 -7.39941 -15.7002 -4 -15.0996 4.5
c3.2998 42.1006 32.1992 71.4004 56 71.4004c23.7998 0 52.6992 -29.2998 56 -71.4004c0.699219 -8.5 -10.8008 -11.8994 -14.9004 -4.5l-9.5 17c-7.7002 13.7002 -19.2002 21.6006 -31.5 21.6006s-23.7998 -7.90039 -31.5 -21.6006zM362.4 160c8.19922 0 14.5 -7 13.5 -15
c-7.5 -59.2002 -58.9004 -105 -121.101 -105h-13.5996c-62.2002 0 -113.601 45.7998 -121.101 105c-1 8 5.30078 15 13.5 15h228.801z" />
<glyph glyph-name="laugh-squint" unicode="&#xf59b;" horiz-adv-x="496"
d="M248 440c137 0 248 -111 248 -248s-111 -248 -248 -248s-248 111 -248 248s111 248 248 248zM389.4 50.5996c37.7998 37.8008 58.5996 88 58.5996 141.4s-20.7998 103.6 -58.5996 141.4c-37.8008 37.7998 -88 58.5996 -141.4 58.5996s-103.6 -20.7998 -141.4 -58.5996
c-37.7998 -37.8008 -58.5996 -88 -58.5996 -141.4s20.7998 -103.6 58.5996 -141.4c37.8008 -37.7998 88 -58.5996 141.4 -58.5996s103.6 20.7998 141.4 58.5996zM343.6 252l33.6006 -40.2998c8.59961 -10.4004 -3.90039 -24.7998 -15.4004 -18l-80 48
c-7.7998 4.7002 -7.7998 15.8994 0 20.5996l80 48c11.6006 6.7998 24 -7.7002 15.4004 -18zM134.2 193.7c-11.6006 -6.7998 -24.1006 7.59961 -15.4004 18l33.6006 40.2998l-33.6006 40.2998c-8.59961 10.2998 3.7998 24.9004 15.4004 18l80 -48
c7.7998 -4.7002 7.7998 -15.8994 0 -20.5996zM362.4 160c8.19922 0 14.5 -7 13.5 -15c-7.5 -59.2002 -58.9004 -105 -121.101 -105h-13.5996c-62.2002 0 -113.601 45.7998 -121.101 105c-1 8 5.30078 15 13.5 15h228.801z" />
<glyph glyph-name="laugh-wink" unicode="&#xf59c;" horiz-adv-x="496"
d="M248 440c137 0 248 -111 248 -248s-111 -248 -248 -248s-248 111 -248 248s111 248 248 248zM389.4 50.5996c37.7998 37.8008 58.5996 88 58.5996 141.4s-20.7998 103.6 -58.5996 141.4c-37.8008 37.7998 -88 58.5996 -141.4 58.5996s-103.6 -20.7998 -141.4 -58.5996
c-37.7998 -37.8008 -58.5996 -88 -58.5996 -141.4s20.7998 -103.6 58.5996 -141.4c37.8008 -37.7998 88 -58.5996 141.4 -58.5996s103.6 20.7998 141.4 58.5996zM328 284c25.7002 0 55.9004 -16.9004 59.7002 -42.0996c1.7998 -11.1006 -11.2998 -18.2002 -19.7998 -10.8008
l-9.5 8.5c-14.8008 13.2002 -46.2002 13.2002 -61 0l-9.5 -8.5c-8.30078 -7.39941 -21.5 -0.399414 -19.8008 10.8008c4 25.1992 34.2002 42.0996 59.9004 42.0996zM168 224c-17.7002 0 -32 14.2998 -32 32s14.2998 32 32 32s32 -14.2998 32 -32s-14.2998 -32 -32 -32z
M362.4 160c8.19922 0 14.5 -7 13.5 -15c-7.5 -59.2002 -58.9004 -105 -121.101 -105h-13.5996c-62.2002 0 -113.601 45.7998 -121.101 105c-1 8 5.30078 15 13.5 15h228.801z" />
<glyph glyph-name="meh-blank" unicode="&#xf5a4;" horiz-adv-x="496"
d="M248 440c137 0 248 -111 248 -248s-111 -248 -248 -248s-248 111 -248 248s111 248 248 248zM248 -8c110.3 0 200 89.7002 200 200s-89.7002 200 -200 200s-200 -89.7002 -200 -200s89.7002 -200 200 -200zM168 272c17.7002 0 32 -14.2998 32 -32s-14.2998 -32 -32 -32
s-32 14.2998 -32 32s14.2998 32 32 32zM328 272c17.7002 0 32 -14.2998 32 -32s-14.2998 -32 -32 -32s-32 14.2998 -32 32s14.2998 32 32 32z" />
<glyph glyph-name="meh-rolling-eyes" unicode="&#xf5a5;" horiz-adv-x="496"
d="M248 440c137 0 248 -111 248 -248s-111 -248 -248 -248s-248 111 -248 248s111 248 248 248zM248 -8c110.3 0 200 89.7002 200 200s-89.7002 200 -200 200s-200 -89.7002 -200 -200s89.7002 -200 200 -200zM336 296c39.7998 0 72 -32.2002 72 -72s-32.2002 -72 -72 -72
s-72 32.2002 -72 72s32.2002 72 72 72zM336 184c22.0996 0 40 17.9004 40 40c0 13.5996 -7.2998 25.0996 -17.7002 32.2998c1 -2.59961 1.7002 -5.39941 1.7002 -8.2998c0 -13.2998 -10.7002 -24 -24 -24s-24 10.7002 -24 24c0 3 0.700195 5.7002 1.7002 8.2998
c-10.4004 -7.2002 -17.7002 -18.7002 -17.7002 -32.2998c0 -22.0996 17.9004 -40 40 -40zM232 224c0 -39.7998 -32.2002 -72 -72 -72s-72 32.2002 -72 72s32.2002 72 72 72s72 -32.2002 72 -72zM120 224c0 -22.0996 17.9004 -40 40 -40s40 17.9004 40 40
c0 13.5996 -7.2998 25.0996 -17.7002 32.2998c1 -2.59961 1.7002 -5.39941 1.7002 -8.2998c0 -13.2998 -10.7002 -24 -24 -24s-24 10.7002 -24 24c0 3 0.700195 5.7002 1.7002 8.2998c-10.4004 -7.2002 -17.7002 -18.7002 -17.7002 -32.2998zM312 96
c13.2002 0 24 -10.7998 24 -24s-10.7998 -24 -24 -24h-128c-13.2002 0 -24 10.7998 -24 24s10.7998 24 24 24h128z" />
<glyph glyph-name="sad-cry" unicode="&#xf5b3;" horiz-adv-x="496"
d="M248 440c137 0 248 -111 248 -248s-111 -248 -248 -248s-248 111 -248 248s111 248 248 248zM392 53.5996c34.5996 35.9004 56 84.7002 56 138.4c0 110.3 -89.7002 200 -200 200s-200 -89.7002 -200 -200c0 -53.7002 21.4004 -102.4 56 -138.4v114.4
c0 13.2002 10.7998 24 24 24s24 -10.7998 24 -24v-151.4c28.5 -15.5996 61.2002 -24.5996 96 -24.5996s67.5 9 96 24.5996v151.4c0 13.2002 10.7998 24 24 24s24 -10.7998 24 -24v-114.4zM205.8 213.5c-5.7998 -3.2002 -11.2002 -0.700195 -13.7002 1.59961l-9.5 8.5
c-14.7998 13.2002 -46.1992 13.2002 -61 0l-9.5 -8.5c-3.7998 -3.39941 -9.2998 -4 -13.6992 -1.59961c-4.40039 2.40039 -6.90039 7.40039 -6.10059 12.4004c3.90039 25.1992 34.2002 42.0996 59.7998 42.0996c25.6006 0 55.8008 -16.9004 59.8008 -42.0996
c0.799805 -5 -1.7002 -10 -6.10059 -12.4004zM344 268c25.7002 0 55.9004 -16.9004 59.7998 -42.0996c0.799805 -5 -1.7002 -10 -6.09961 -12.4004c-5.7002 -3.09961 -11.2002 -0.599609 -13.7002 1.59961l-9.5 8.5c-14.7998 13.2002 -46.2002 13.2002 -61 0l-9.5 -8.5
c-3.7998 -3.39941 -9.2002 -4 -13.7002 -1.59961c-4.39941 2.40039 -6.89941 7.40039 -6.09961 12.4004c3.89941 25.1992 34.0996 42.0996 59.7998 42.0996zM248 176c30.9004 0 56 -28.7002 56 -64s-25.0996 -64 -56 -64s-56 28.7002 -56 64s25.0996 64 56 64z" />
<glyph glyph-name="sad-tear" unicode="&#xf5b4;" horiz-adv-x="496"
d="M248 440c137 0 248 -111 248 -248s-111 -248 -248 -248s-248 111 -248 248s111 248 248 248zM248 -8c110.3 0 200 89.7002 200 200s-89.7002 200 -200 200s-200 -89.7002 -200 -200s89.7002 -200 200 -200zM256 144c38.0996 0 74 -16.7998 98.5 -46.0996
c8.5 -10.2002 7.09961 -25.3008 -3.09961 -33.8008c-10.6006 -8.7998 -25.7002 -6.69922 -33.8008 3.10059c-15.2998 18.2998 -37.7998 28.7998 -61.5996 28.7998c-13.2002 0 -24 10.7998 -24 24s10.7998 24 24 24zM168 208c-17.7002 0 -32 14.2998 -32 32s14.2998 32 32 32
s32 -14.2998 32 -32s-14.2998 -32 -32 -32zM328 272c17.7002 0 32 -14.2998 32 -32s-14.2998 -32 -32 -32s-32 14.2998 -32 32s14.2998 32 32 32zM162.4 173.2c2.7998 3.7002 8.39941 3.7002 11.1992 0c11.4004 -15.2998 36.4004 -50.6006 36.4004 -68.1006
c0 -22.6992 -18.7998 -41.0996 -42 -41.0996s-42 18.4004 -42 41.0996c0 17.5 25 52.8008 36.4004 68.1006z" />
<glyph glyph-name="smile-beam" unicode="&#xf5b8;" horiz-adv-x="496"
d="M248 440c137 0 248 -111 248 -248s-111 -248 -248 -248s-248 111 -248 248s111 248 248 248zM248 -8c110.3 0 200 89.7002 200 200s-89.7002 200 -200 200s-200 -89.7002 -200 -200s89.7002 -200 200 -200zM332 135.4c8.5 10.1992 23.5996 11.5 33.7998 3.09961
c10.2002 -8.5 11.6006 -23.5996 3.10059 -33.7998c-30 -36 -74.1006 -56.6006 -120.9 -56.6006s-90.9004 20.6006 -120.9 56.6006c-8.39941 10.2002 -7.09961 25.2998 3.10059 33.7998c10.2002 8.40039 25.2998 7.09961 33.7998 -3.09961
c20.7998 -25.1006 51.5 -39.4004 84 -39.4004s63.2002 14.4004 84 39.4004zM136.5 237l-9.5 -17c-1.90039 -3.2002 -5.90039 -4.7998 -9.2998 -3.7002c-3.60059 1.10059 -6 4.60059 -5.7002 8.2998c3.2998 42.1006 32.2002 71.4004 56 71.4004s52.7002 -29.2998 56 -71.4004
c0.299805 -3.7998 -2.09961 -7.19922 -5.7002 -8.2998c-3.09961 -1 -7.2002 0 -9.2998 3.7002l-9.5 17c-7.7002 13.7002 -19.2002 21.5996 -31.5 21.5996s-23.7998 -7.89941 -31.5 -21.5996zM328 296c23.7998 0 52.7002 -29.2998 56 -71.4004
c0.299805 -3.7998 -2.09961 -7.19922 -5.7002 -8.2998c-3.09961 -1 -7.2002 0 -9.2998 3.7002l-9.5 17c-7.7002 13.7002 -19.2002 21.5996 -31.5 21.5996s-23.7998 -7.89941 -31.5 -21.5996l-9.5 -17c-1.90039 -3.2002 -5.7998 -4.7998 -9.2998 -3.7002
c-3.60059 1.10059 -6 4.60059 -5.7002 8.2998c3.2998 42.1006 32.2002 71.4004 56 71.4004z" />
<glyph glyph-name="surprise" unicode="&#xf5c2;" horiz-adv-x="496"
d="M248 440c137 0 248 -111 248 -248s-111 -248 -248 -248s-248 111 -248 248s111 248 248 248zM248 -8c110.3 0 200 89.7002 200 200s-89.7002 200 -200 200s-200 -89.7002 -200 -200s89.7002 -200 200 -200zM248 168c35.2998 0 64 -28.7002 64 -64s-28.7002 -64 -64 -64
s-64 28.7002 -64 64s28.7002 64 64 64zM200 240c0 -17.7002 -14.2998 -32 -32 -32s-32 14.2998 -32 32s14.2998 32 32 32s32 -14.2998 32 -32zM328 272c17.7002 0 32 -14.2998 32 -32s-14.2998 -32 -32 -32s-32 14.2998 -32 32s14.2998 32 32 32z" />
<glyph glyph-name="tired" unicode="&#xf5c8;" horiz-adv-x="496"
d="M248 440c137 0 248 -111 248 -248s-111 -248 -248 -248s-248 111 -248 248s111 248 248 248zM248 -8c110.3 0 200 89.7002 200 200s-89.7002 200 -200 200s-200 -89.7002 -200 -200s89.7002 -200 200 -200zM377.1 295.8c3.80078 -4.39941 3.90039 -11 0.100586 -15.5
l-33.6006 -40.2998l33.6006 -40.2998c3.7998 -4.5 3.7002 -11 -0.100586 -15.5c-3.5 -4.10059 -9.89941 -5.7002 -15.2998 -2.5l-80 48c-3.59961 2.2002 -5.7998 6.09961 -5.7998 10.2998s2.2002 8.09961 5.7998 10.2998l80 48c5 2.90039 11.5 1.90039 15.2998 -2.5z
M220 240c0 -4.2002 -2.2002 -8.09961 -5.7998 -10.2998l-80 -48c-5.40039 -3.2002 -11.7998 -1.60059 -15.2998 2.5c-3.80078 4.5 -3.90039 11 -0.100586 15.5l33.6006 40.2998l-33.6006 40.2998c-3.7998 4.5 -3.7002 11 0.100586 15.5
c3.7998 4.40039 10.2998 5.5 15.2998 2.5l80 -48c3.59961 -2.2002 5.7998 -6.09961 5.7998 -10.2998zM248 176c45.4004 0 100.9 -38.2998 107.8 -93.2998c1.5 -11.9004 -7 -21.6006 -15.5 -17.9004c-22.7002 9.7002 -56.2998 15.2002 -92.2998 15.2002
s-69.5996 -5.5 -92.2998 -15.2002c-8.60059 -3.7002 -17 6.10059 -15.5 17.9004c6.89941 55 62.3994 93.2998 107.8 93.2998z" />
</font>
</defs></svg>

Before

Width:  |  Height:  |  Size: 141 KiB

BIN
gui_wails_learn/frontend/src/assets/fonts/fa-regular-400.ttf

Binary file not shown.

BIN
gui_wails_learn/frontend/src/assets/fonts/fa-regular-400.woff

Binary file not shown.

BIN
gui_wails_learn/frontend/src/assets/fonts/fa-regular-400.woff2

Binary file not shown.

BIN
gui_wails_learn/frontend/src/assets/fonts/fa-solid-900.eot

Binary file not shown.

4649
gui_wails_learn/frontend/src/assets/fonts/fa-solid-900.svg

File diff suppressed because it is too large

Before

Width:  |  Height:  |  Size: 820 KiB

BIN
gui_wails_learn/frontend/src/assets/fonts/fa-solid-900.ttf

Binary file not shown.

BIN
gui_wails_learn/frontend/src/assets/fonts/fa-solid-900.woff

Binary file not shown.

BIN
gui_wails_learn/frontend/src/assets/fonts/fa-solid-900.woff2

Binary file not shown.

BIN
gui_wails_learn/frontend/src/assets/fonts/glyphicons-halflings-regular.448c34a.woff2

Binary file not shown.

BIN
gui_wails_learn/frontend/src/assets/fonts/glyphicons-halflings-regular.e18bbf6.ttf

Binary file not shown.

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save