Skip to content

Commit

Permalink
Fix: issue #5 and add new feat: load YAML from HTTP
Browse files Browse the repository at this point in the history
  • Loading branch information
lucasepe committed Jun 27, 2020
1 parent 5492b5b commit 5b49e2d
Show file tree
Hide file tree
Showing 122 changed files with 327 additions and 157 deletions.
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,17 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.7.0] - 2020-06-27
### Added
- 🎉 load architecture YAML files from remote HTTP sites
- `draft 'http://my.my.domain.com/arch.yml' | dot -Tpng > arch.png`

### Fixed
- 🐛 [issue #5](https://github.com/lucasepe/draft/issues/5) labels for all components and connections

### Changed
- `impl` flag value for Google Cloud is now `google`

## [0.6.1] - 2020-06-22
### Fixed
- 🐛 outline frames not working
Expand Down
58 changes: 27 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ A commandline tool that generate **H**igh **L**evel microservice & serverless **
- Input data in flat YAML text files
- Usable with shell scripts

![](./cli-output.png)


# Why?

I prefer to think in terms of capabilities rather than specific vendor services.
Expand All @@ -29,26 +32,20 @@ I prefer to think in terms of capabilities rather than specific vendor services.
draft backend-for-frontend.yml | dot -Tpng -Gdpi=200 > backend-for-frontend.png
```

Piping the `draft` output to [GraphViz](http://www.graphviz.org/doc/info/output.html/) `dot` you can generate the following output formats:
Piping the `draft` output to [GraphViz](http://www.graphviz.org/doc/info/output.html/) `dot` you can generate different output formats:

| format | command |
|:-------------|:---------------------------------------------------------------|
| GIF | <code>draft input.yml &#124; dot -Tgif > output.gif</code> |
| PNG | <code>draft input.yml &#124; dot -Tpng > output.png</code> |
| JPEG | <code>draft input.yml &#124; dot -Tjpg > output.jpg</code> |
| PostScript | <code>draft input.yml &#124; dot -Tps > output.ps</code> |
| PSD | <code>draft input.yml &#124; dot -Tpsd > output.psd</code> |
| SVG | <code>draft input.yml &#124; dot -Tsvg > output.svg</code> |
| WebP | <code>draft input.yml &#124; dot -Twebp > output.webp</code> |

To install GraphViz to your favorite OS, please, follow this link [https://graphviz.gitlab.io/download/](https://graphviz.gitlab.io/download/).


# Components

> a picture is worth a thousand words
... and this is particularly true in regard to complex IT architectures.

The basic unit of each _draft_ design is the `component`:

```go
Expand All @@ -73,25 +70,25 @@ Eventually you can describe...

Below is a list of all the components currently implemented.

| Component | Kind | YAML | Output |
|:---------------------|:------|:--------------------------------------------|:------------------------:|
| **Client** | `cli` | 👉 [examples/cli.yml](./examples/cli.yml) | ![](./examples/cli.png) |
| **Microservice** | `ser` | 👉 [examples/ser.yml](./examples/ser.yml) | ![](./examples/ser.png) |
| **API Gateway** | `gtw` | 👉 [examples/gtw.yml](./examples/gtw.yml) | ![](./examples/gtw.png) |
| **Firewall** | `waf` | 👉 [examples/waf.yml](./examples/waf.yml) | ![](./examples/waf.png) |
| **K8s Engine** | `kub` | 👉 [examples/kub.yml](./examples/kub.yml) | ![](./examples/kub.png) |
| **Pub / Sub** | `msg` | 👉 [examples/msg.yml](./examples/msg.yml) | ![](./examples/msg.png) |
| **Queue** | `que` | 👉 [examples/que.yml](./examples/que.yml) | ![](./examples/que.png) |
| **Function** | `fun` | 👉 [examples/fun.yml](./examples/fun.yml) | ![](./examples/fun.png) |
| **Relational DB** | `rdb` | 👉 [examples/rdb.yml](./examples/rdb.yml) | ![](./examples/rdb.png) |
| **Document DB** | `doc` | 👉 [examples/doc.yml](./examples/doc.yml) | ![](./examples/doc.png) |
| **Caching** | `mem` | 👉 [examples/mem.yml](./examples/mem.yml) | ![](./examples/mem.png) |
| **Load Balancer** | `lba` | 👉 [examples/lba.yml](./examples/lba.yml) | ![](./examples/lba.png) |
| **CDN** | `cdn` | 👉 [examples/cdn.yml](./examples/cdn.yml) | ![](./examples/cdn.png) |
| **DNS** | `dns` | 👉 [examples/dns.yml](./examples/dns.yml) | ![](./examples/dns.png) |
| **Block Store** | `bst` | 👉 [examples/bst.yml](./examples/bst.yml) | ![](./examples/bst.png) |
| **Object Store** | `ost` | 👉 [examples/ost.yml](./examples/ost.yml) | ![](./examples/ost.png) |
| **File Store** | `fst` | 👉 [examples/fst.yml](./examples/fst.yml) | ![](./examples/fst.png) |
| Component | Kind | YAML | Output |
|:-------------------|:------|:--------------------------------------------|:------------------------:|
| **Client** | `cli` | 👉 [examples/cli.yml](./examples/cli.yml) | ![](./examples/cli.png) |
| **Microservice** | `ser` | 👉 [examples/ser.yml](./examples/ser.yml) | ![](./examples/ser.png) |
| **Gateway** | `gtw` | 👉 [examples/gtw.yml](./examples/gtw.yml) | ![](./examples/gtw.png) |
| **Firewall** | `waf` | 👉 [examples/waf.yml](./examples/waf.yml) | ![](./examples/waf.png) |
| **K8s Engine** | `kub` | 👉 [examples/kub.yml](./examples/kub.yml) | ![](./examples/kub.png) |
| **Pub / Sub** | `msg` | 👉 [examples/msg.yml](./examples/msg.yml) | ![](./examples/msg.png) |
| **Queue** | `que` | 👉 [examples/que.yml](./examples/que.yml) | ![](./examples/que.png) |
| **Function** | `fun` | 👉 [examples/fun.yml](./examples/fun.yml) | ![](./examples/fun.png) |
| **Relational DB** | `rdb` | 👉 [examples/rdb.yml](./examples/rdb.yml) | ![](./examples/rdb.png) |
| **Document DB** | `doc` | 👉 [examples/doc.yml](./examples/doc.yml) | ![](./examples/doc.png) |
| **Caching** | `mem` | 👉 [examples/mem.yml](./examples/mem.yml) | ![](./examples/mem.png) |
| **Load Balancer** | `lba` | 👉 [examples/lba.yml](./examples/lba.yml) | ![](./examples/lba.png) |
| **CDN** | `cdn` | 👉 [examples/cdn.yml](./examples/cdn.yml) | ![](./examples/cdn.png) |
| **DNS** | `dns` | 👉 [examples/dns.yml](./examples/dns.yml) | ![](./examples/dns.png) |
| **Block Store** | `bst` | 👉 [examples/bst.yml](./examples/bst.yml) | ![](./examples/bst.png) |
| **Object Store** | `ost` | 👉 [examples/ost.yml](./examples/ost.yml) | ![](./examples/ost.png) |
| **File Store** | `fst` | 👉 [examples/fst.yml](./examples/fst.yml) | ![](./examples/fst.png) |

## Auto filling the component implementation

Expand All @@ -101,21 +98,20 @@ In your YAML file, leave the `impl` fields empty and run [draft](https://github.
|:------------------------------------------------------------------------------|:------------------------------:|
| <code>draft -impl aws ./examples/dns.yml &#124; dot -Tpng > test.png</code> | ![](./examples/dns_aws.png) |
| <code>draft -impl azure ./examples/dns.yml &#124; dot -Tpng > test.png<code> | ![](./examples/dns_azure.png) |
| <code>draft -impl gcp ./examples/kub.yml &#124; dot -Tpng > test.png<code> | ![](./examples/kub_gcp.png) |
| <code>draft -impl google ./examples/kub.yml &#124; dot -Tpng > test.png<code> | ![](./examples/kub_google.png) |
| <code>draft -impl aws ./examples/kub.yml &#124; dot -Tpng > test.png<code> | ![](./examples/kub_aws.png) |

... and so on for each kind of component!

To render components with specific icons read below.


## Rendering components with specific cloud provider icons

1. Download the <u>PNG</u> icons of your cloud provider [AWS](https://aws.amazon.com/it/architecture/icons/), [GCP](https://cloud.google.com/icons), [Azure](https://www.microsoft.com/en-us/download/details.aspx?id=41937)

2. Take only the icons related to the components supported by [draft](https://github.com/lucasepe/draft/releases/latest)

3. Make a directory with the provider name (i.e. `/draft/icons/aws`, `/draft/icons/gcp`, `/draft/icons/azure`)
3. Make a directory with the provider name (i.e. `/draft/icons/aws`, `/draft/icons/google`, `/draft/icons/azure`)

4. Rename each icon as [draft](https://github.com/lucasepe/draft/releases/latest) components `kind` (i.e. `dns.png`, `cdn.png` and so on...)

Expand All @@ -140,7 +136,7 @@ An auto-generated component `id` has a prefix and a sequential number

You can connect each component by arrows.

To be able to connect an _origin component_ with one or more _target component_ you need to specify each `componentId`.
To be able to connect an _origin component_ with one or more _target component_ you need to specify each `id`.

A `connection` has the following properties:

Expand Down
Binary file added cli-output.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 8 additions & 3 deletions cmd/draft-all.sh
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ do
"$EXE" -verbose "$i" | dot -Tpng -Gdpi=$DPI > "$SRC_DIR/${filename%.*}.png"

"$EXE" -verbose -impl aws "$i" | dot -Tpng -Gdpi=$DPI > "$SRC_DIR/${filename%.*}_aws.png"
"$EXE" -verbose -impl gcp "$i" | dot -Tpng -Gdpi=$DPI > "$SRC_DIR/${filename%.*}_gcp.png"
"$EXE" -verbose -impl google "$i" | dot -Tpng -Gdpi=$DPI > "$SRC_DIR/${filename%.*}_google.png"
"$EXE" -verbose -impl azure "$i" | dot -Tpng -Gdpi=$DPI > "$SRC_DIR/${filename%.*}_azure.png"
done

Expand All @@ -42,11 +42,16 @@ done

"$EXE" -verbose "$SRC_DIR/impl-example.yml" | dot -Tpng -Gdpi=$DPI > "$SRC_DIR/impl-example.png"
"$EXE" -verbose -impl aws "$SRC_DIR/impl-example.yml" | dot -Tpng -Gdpi=$DPI > "$SRC_DIR/impl-example-aws.png"
"$EXE" -verbose -impl gcp "$SRC_DIR/impl-example.yml" | dot -Tpng -Gdpi=$DPI > "$SRC_DIR/impl-example-gcp.png"
"$EXE" -verbose -impl google "$SRC_DIR/impl-example.yml" | dot -Tpng -Gdpi=$DPI > "$SRC_DIR/impl-example-google.png"
"$EXE" -verbose -impl azure "$SRC_DIR/impl-example.yml" | dot -Tpng -Gdpi=$DPI > "$SRC_DIR/impl-example-azure.png"

"$EXE" -verbose "$SRC_DIR/cognito-custom-auth-flow.yml" | dot -Tpng -Gdpi=$DPI > "$SRC_DIR/cognito-custom-auth-flow.png"
"$EXE" -verbose -impl aws "$SRC_DIR/cognito-custom-auth-flow.yml" | dot -Tpng -Gdpi=$DPI > "$SRC_DIR/cognito-custom-auth-flow-aws.png"

"$EXE" -verbose "$SRC_DIR/s3-upload-presigned-url.yml" | dot -Tpng -Gdpi=$DPI > "$SRC_DIR/s3-upload-presigned-url.png"
"$EXE" -verbose -impl aws "$SRC_DIR/s3-upload-presigned-url.yml" | dot -Tpng -Gdpi=$DPI > "$SRC_DIR/s3-upload-presigned-url-aws.png"
"$EXE" -verbose -impl aws "$SRC_DIR/s3-upload-presigned-url.yml" | dot -Tpng -Gdpi=$DPI > "$SRC_DIR/s3-upload-presigned-url-aws.png"

"$EXE" -verbose "$SRC_DIR/demo.yml" | dot -Tpng > "$SRC_DIR/demo.png"
"$EXE" -verbose -impl aws "$SRC_DIR/demo.yml" | dot -Tpng > "$SRC_DIR/demo-aws.png"
"$EXE" -verbose -impl google "$SRC_DIR/demo.yml" | dot -Tpng > "$SRC_DIR/demo-google.png"
"$EXE" -verbose -impl azure "$SRC_DIR/demo.yml" | dot -Tpng > "$SRC_DIR/demo-azure.png"
51 changes: 44 additions & 7 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ import (
"strings"

"github.com/lucasepe/draft"
"github.com/mitchellh/go-homedir"
)

// go run main.go | dot -Tpng -Gdpi=300 > test.png

const (
banner = `
envIconsPath = "DRAFT_ICONS_PATH"
banner = `
______ __ _
| _ \ / _|| | Crafted with passion by Luca Sepe
| | | | _ __ __ _ | |_ | |_
Expand All @@ -35,6 +37,10 @@ var (
)

func main() {
if dir := os.Getenv(envIconsPath); len(dir) == 0 {
setDefaultIconsPath()
}

configureFlags()

if flag.CommandLine.Arg(0) == "" {
Expand All @@ -48,6 +54,7 @@ func main() {
draft.Verbose(flagVerbose),
draft.BottomTop(flagBottomTop),
draft.Provider(flagImpl),
draft.IconsPath(os.Getenv(envIconsPath)),
draft.URI(uri),
)

Expand All @@ -66,32 +73,38 @@ func handleErr(err error, src string) {
}

func configureFlags() {
name := filepath.Base(os.Args[0])
name := appName()

flag.CommandLine.Usage = func() {
printBanner()
fmt.Printf("Generate High Level microservices Architecture diagrams for GraphViz using simple YAML syntax.\n\n")
fmt.Printf("Generate High Level Cloud Architecture diagrams using YAML.\n\n")

fmt.Print("USAGE:\n\n")
fmt.Printf(" %s [options] /path/to/yaml/file\n\n", name)
fmt.Printf(" %s [options] <yaml file or url>\n\n", name)

fmt.Print("EXAMPLE:\n\n")
fmt.Printf(" %s input.yml | dot -Tpng -Gdpi=200 > output.png\n\n", name)
fmt.Print("EXAMPLE(s):\n\n")
fmt.Printf(" %s input.yml | dot -Tpng -Gdpi=200 > output.png\n", name)
fmt.Printf(" %s http://a.domain.com/input.yml | dot -Tpng -Gdpi=200 > output.png\n\n", name)

fmt.Print("OPTIONS:\n\n")
flag.CommandLine.SetOutput(os.Stdout)
flag.CommandLine.PrintDefaults()
flag.CommandLine.SetOutput(ioutil.Discard) // hide flag errors
fmt.Print(" -help\n\tprints this message\n")
fmt.Println()

fmt.Print("ENVIRONMENT:\n\n")
fmt.Fprint(os.Stdout, " DRAFT_ICONS_PATH\n")
fmt.Fprintf(os.Stdout, " \tthe base path for custom icons (default %s)\n", os.Getenv(envIconsPath))
fmt.Println()
}

flag.CommandLine.SetOutput(ioutil.Discard) // hide flag errors
flag.CommandLine.Init(os.Args[0], flag.ExitOnError)

flag.CommandLine.BoolVar(&flagBottomTop, "bottom-top", false, "if true sets layout dir as bottom top")
//flag.CommandLine.BoolVar(&flagOrtho, "ortho", false, "if true edges are drawn as line segments")
flag.CommandLine.StringVar(&flagImpl, "impl", "", "auto fill the specific provider services (aws, gcp or azure)")
flag.CommandLine.StringVar(&flagImpl, "impl", "", "auto fill the specific provider services (aws, google, azure)")
flag.CommandLine.BoolVar(&flagVerbose, "verbose", false, fmt.Sprintf("show some extra info as %s is running", name))

flag.CommandLine.Parse(os.Args[1:])
Expand All @@ -100,3 +113,27 @@ func configureFlags() {
func printBanner() {
fmt.Print(strings.Trim(strings.Replace(banner, "{{VERSION}}", version, 1), "\n"), "\n\n")
}

// setDefaultIconsPath sets the default icons directory
// creating it if does not exists.
func setDefaultIconsPath() error {
home, err := homedir.Dir()
if err != nil {
return err
}

workdir := filepath.Join(home, fmt.Sprintf(".%s", appName()), "icons")
if _, err := os.Stat(workdir); os.IsNotExist(err) {
err = os.MkdirAll(workdir, os.ModePerm)
if err != nil {
return err
}
}

os.Setenv(envIconsPath, workdir)
return nil
}

func appName() string {
return filepath.Base(os.Args[0])
}
6 changes: 5 additions & 1 deletion compute.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ func figContainersManager(ctx Config, com Component) func(gfx *dot.Graph) bool {
return false
}

if lab := strings.TrimSpace(com.Label); len(lab) == 0 {
com.Label = "Containers\nEngine"
}

if fc := strings.TrimSpace(com.FontColor); len(fc) == 0 {
com.FontColor = "#fafafaff"
}
Expand All @@ -92,7 +96,7 @@ func figContainersManager(ctx Config, com Component) func(gfx *dot.Graph) bool {
cl := cluster.New(gfx, com.ID, cluster.BottomTop(ctx.bottomTop), cluster.Label(com.Impl))

node.New(cl, com.ID,
node.Label("<b>Containers<br/>Engine</b>", true),
node.Label(com.Label, false),
node.FontSize(7),
node.FontColor(com.FontColor),
node.FillColor(com.FillColor),
Expand Down
10 changes: 9 additions & 1 deletion config.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func BottomTop(enable bool) func(*Config) {

// Provider sets the cloud implementation (one of 'aws', 'gcp', 'azure')
func Provider(s string) func(*Config) {
provs := map[string]bool{"aws": true, "gcp": true, "azure": true}
provs := map[string]bool{"aws": true, "google": true, "azure": true}

return func(cfg *Config) {
val := strings.ToLower(strings.TrimSpace(s))
Expand All @@ -55,11 +55,19 @@ func URI(s string) func(*Config) {
}
}

// IconsPath sets the custom icons path.
func IconsPath(s string) func(*Config) {
return func(cfg *Config) {
cfg.iconsPath = strings.TrimSpace(s)
}
}

// Config defines the 'draft' configuration.
type Config struct {
bottomTop bool
provider string
verbose bool
iconsPath string
uri string
}

Expand Down
6 changes: 5 additions & 1 deletion database.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ func figRDB(ctx Config, com Component) func(gfx *dot.Graph) bool {
return false
}

if lab := strings.TrimSpace(com.Label); len(lab) == 0 {
com.Label = "RDB"
}

if fc := strings.TrimSpace(com.FontColor); len(fc) == 0 {
com.FontColor = "#000000ff"
}
Expand Down Expand Up @@ -116,7 +120,7 @@ func figCache(ctx Config, com Component) func(gfx *dot.Graph) bool {
</tr>
</table>`, "{{BGCOLOR}}", com.FillColor, -1)

label = fmt.Sprintf(label, caption)
label = fmt.Sprintf(label, strings.ReplaceAll(caption, "\n", "<br/>"))

cl := cluster.New(gfx, com.ID, cluster.BottomTop(ctx.bottomTop), cluster.Label(com.Impl))

Expand Down
29 changes: 16 additions & 13 deletions draft_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,21 +48,24 @@ func TestSketchComponents(t *testing.T) {
t.Error(err)
}

want := `digraph {
subgraph cluster_s5 {
fontname="Fira Mono Bold";fontsize="9.00";label="";labelloc="t";pencolor="transparent";
n6[fillcolor="#d1c8d4ff",fontcolor="#000000ff",fontname="Fira Mono",fontsize="9.00",height="0.5",label="NoSQL",shape="note",style="filled"];
if n, ok := gfx.FindNodeById("gtw1"); ok {
want := "doublecircle"
if got := n.Value("shape"); got != want {
t.Errorf("got [%v] want [%v]", got, want)
}
subgraph cluster_s3 {
fontname="Fira Mono Bold";fontsize="9.00";label="";labelloc="t";pencolor="transparent";
n4[fillcolor="#abd9e9ff",fontcolor="#000000ff",fontname="Fira Mono",fontsize="6",height="0.5",label="",shape="signature",style="filled"];
}

if n, ok := gfx.FindNodeById("fun1"); ok {
want := "signature"
if got := n.Value("shape"); got != want {
t.Errorf("got [%v] want [%v]", got, want)
}
subgraph cluster_s1 {
fontname="Fira Mono Bold";fontsize="9.00";label="";labelloc="t";pencolor="transparent";
n2[color="#ff7f00ff",fillcolor="#ff7f00ff",fontcolor="#f5f5f5ff",fontname="Fira Mono",fontsize="9.00",label=<<b>GTW</b>>,shape="doublecircle",style="filled",width="0.1"];
}

if n, ok := gfx.FindNodeById("doc1"); !ok {
want := "note"
if got := n.Value("shape"); got != want {
t.Errorf("got [%v] want [%v]", got, want)
}
}`
if got := flatten(gfx.String()); got != flatten(want) {
t.Errorf("got [%v] want [%v]", got, want)
}
}
Binary file modified examples/bst.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified examples/bst_aws.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified examples/bst_azure.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed examples/bst_gcp.png
Binary file not shown.
Binary file added examples/bst_google.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified examples/cdn.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified examples/cdn_aws.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified examples/cdn_azure.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed examples/cdn_gcp.png
Binary file not shown.
Binary file added examples/cdn_google.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified examples/cli_aws.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified examples/cli_azure.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
File renamed without changes
Binary file modified examples/cognito-custom-auth-flow-aws.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added examples/demo-aws.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added examples/demo-azure.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added examples/demo-google.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added examples/demo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 5b49e2d

Please sign in to comment.