├── .github └── workflows │ └── ci.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── api.go ├── filewatch └── filewatch.go ├── go.mod ├── go.sum └── maputnik.go /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v3 12 | 13 | - name: Set up Go 14 | uses: actions/setup-go@v3 15 | with: 16 | go-version: 1.18 17 | 18 | - name: Make 19 | run: make 20 | 21 | - name: Test --help 22 | run: ./bin/linux/maputnik --help 23 | 24 | - name: Test --version 25 | run: ./bin/linux/maputnik --version 26 | 27 | - name: Get style 28 | run: wget https://maputnik.github.io/osm-liberty/style.json 29 | 30 | - name: Test --watch 31 | run: ./bin/linux/maputnik --watch --file style.json & sleep 5; kill $! 32 | 33 | - name: Artifacts/linux 34 | uses: actions/upload-artifact@v3 35 | with: 36 | name: maputnik-linux 37 | path: bin/linux/ 38 | 39 | - name: Artifacts/darwin 40 | uses: actions/upload-artifact@v3 41 | with: 42 | name: maputnik-darwin 43 | path: bin/darwin/ 44 | 45 | - name: Artifacts/windows 46 | uses: actions/upload-artifact@v3 47 | with: 48 | name: maputnik-windows 49 | path: bin/windows/ 50 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | editor 10 | 11 | # Architecture specific extensions/prefixes 12 | *.[568vq] 13 | [568vq].out 14 | 15 | *.cgo1.go 16 | *.cgo2.c 17 | _cgo_defun.c 18 | _cgo_gotypes.go 19 | _cgo_export.* 20 | 21 | _testmain.go 22 | 23 | *.exe 24 | *.test 25 | *.prof 26 | 27 | # Binary version of pubilic/editor 28 | rice-box.go 29 | 30 | # Built binary 31 | maputnik 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Maputnik 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SOURCEDIR=. 2 | SOURCES := $(shell find $(SOURCEDIR) -name '*.go') 3 | BINARY=maputnik 4 | EDITOR_VERSION ?= v1.7.0 5 | GOPATH := $(if $(GOPATH),$(GOPATH),$(HOME)/go) 6 | GOBIN := $(if $(GOBIN),$(GOBIN),$(HOME)/go/bin) 7 | 8 | all: $(BINARY) 9 | 10 | $(BINARY): $(GOBIN)/gox $(SOURCES) rice-box.go 11 | $(GOBIN)/gox -osarch "windows/amd64 linux/amd64 darwin/amd64" -output "bin/{{.OS}}/${BINARY}" 12 | 13 | editor/create_folder: 14 | mkdir -p editor 15 | 16 | editor/pull_release: editor/create_folder 17 | # if the directory /home/runner/work/editor/editor/build/build exists, we assume that we are are running the makefile within the editor ci workflow 18 | test -d /home/runner/work/editor/editor/build/build && echo "exists" && cd editor && cp -R /home/runner/work/editor/editor/build/build public/ || (echo "does not exist" && cd editor && rm -rf public && curl -L https://github.com/maputnik/editor/releases/download/$(EDITOR_VERSION)/public.zip --output public.zip && unzip public.zip && rm public.zip) 19 | 20 | $(GOBIN)/gox: 21 | go install github.com/mitchellh/gox@v1.0.1 22 | 23 | $(GOBIN)/rice: 24 | go install github.com/GeertJohan/go.rice/rice@v1.0.3 25 | 26 | rice-box.go: $(GOBIN)/rice editor/pull_release 27 | $(GOBIN)/rice embed-go 28 | 29 | .PHONY: clean 30 | clean: 31 | rm -rf editor/public && rm -f rice-box.go && rm -rf bin 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Maputnik Desktop [![GitHub CI status](https://github.com/maputnik/desktop/workflows/ci/badge.svg)](https://github.com/maputnik/desktop/actions?query=workflow%3Aci) 2 | 3 | --- 4 | 5 | A Golang based cross platform executable for integrating Maputnik locally. 6 | This binary packages up the JavaScript and CSS bundle produced by [maputnik/editor](https://github.com/maputnik/desktop) 7 | and embeds it in the program for easy distribution. It also allows 8 | exposing a local style file and work on it both in Maputnik and with your favorite 9 | editor. 10 | 11 | Report issues on [maputnik/editor](https://github.com/maputnik/editor). 12 | 13 | ## Install 14 | 15 | You can download a single binary for Linux, OSX or Windows from [the latest releases of **maputnik/editor**](https://github.com/maputnik/editor/releases/latest). 16 | 17 | ### Usage 18 | 19 | Simply start up a web server and access the Maputnik editor GUI at `localhost:8000`. 20 | 21 | ```bash 22 | maputnik 23 | ``` 24 | 25 | Expose a local style file to Maputnik allowing the web based editor 26 | to save to the local filesystem. 27 | 28 | ```bash 29 | maputnik --file basic-v9.json 30 | ``` 31 | 32 | Watch the local style for changes and inform the editor via web socket. 33 | This makes it possible to edit the style with a local text editor and still 34 | use Maputnik. 35 | 36 | ```bash 37 | maputnik --watch --file basic-v9.json 38 | ``` 39 | 40 | Choose a local port to listen on, instead of using the default port 8000. 41 | 42 | ```bash 43 | maputnik --port 8001 44 | ``` 45 | 46 | Specify a path to a directory which, if it exists, will be served under http://localhost:8000/static/ . 47 | Could be used to serve sprites and glyphs. 48 | 49 | ```bash 50 | maputnik --static ./localFolder 51 | ``` 52 | 53 | ### API 54 | 55 | `maputnik` exposes the configured styles via a HTTP API. 56 | 57 | | Method | Description 58 | |---------------------------------|--------------------------------------- 59 | | `GET /styles` | List the ID of all configured style files 60 | | `GET /styles/{filename}` | Get contents of a single style file 61 | | `PUT /styles/{filename}` | Update contents of a style file 62 | | `WEBSOCKET /ws` | Listen to change events for the configured style files 63 | 64 | ### Build 65 | 66 | Clone the repository. Make sure you clone it into the correct directory `$GOPATH/src/github.com/maputnik`. 67 | 68 | ``` 69 | git clone git@github.com:maputnik/desktop.git 70 | ``` 71 | 72 | Run `make` to install the 3rd party dependencies and build the `maputnik` binary embedding the editor. 73 | 74 | ``` 75 | make 76 | ``` 77 | 78 | You should now find the `maputnik` binary in your `bin` directory. 79 | -------------------------------------------------------------------------------- /api.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io/ioutil" 8 | "log" 9 | "net/http" 10 | 11 | "github.com/gorilla/mux" 12 | ) 13 | 14 | func StyleFileAccessor(filename string) styleFileAccessor { 15 | return styleFileAccessor{filename, styleId(filename)} 16 | } 17 | 18 | func styleId(filename string) string { 19 | raw, err := ioutil.ReadFile(filename) 20 | if err != nil { 21 | log.Panicln(err) 22 | } 23 | 24 | var spec styleSpec 25 | err = json.Unmarshal(raw, &spec) 26 | if err != nil { 27 | log.Panicln(err) 28 | } 29 | 30 | if spec.Id == "" { 31 | fmt.Println("No id in style") 32 | } 33 | return spec.Id 34 | } 35 | 36 | type styleSpec struct { 37 | Id string `json:"id"` 38 | } 39 | 40 | // Allows access to a single style file 41 | type styleFileAccessor struct { 42 | filename string 43 | id string 44 | } 45 | 46 | func (fa styleFileAccessor) ListFiles(w http.ResponseWriter, r *http.Request) { 47 | w.Header().Set("Content-Type", "application/json") 48 | encoder := json.NewEncoder(w) 49 | encoder.Encode([]string{fa.id}) 50 | } 51 | 52 | func (fa styleFileAccessor) ReadFile(w http.ResponseWriter, r *http.Request) { 53 | vars := mux.Vars(r) 54 | _ = vars["styleId"] 55 | 56 | //TODO: Choose right file 57 | // right now we just return the single file we know of 58 | w.Header().Set("Content-Type", "application/json") 59 | 60 | raw, err := ioutil.ReadFile(fa.filename) 61 | if err != nil { 62 | log.Panicln(err) 63 | } 64 | w.Write(raw) 65 | } 66 | 67 | func (fa styleFileAccessor) SaveFile(w http.ResponseWriter, r *http.Request) { 68 | vars := mux.Vars(r) 69 | _ = vars["styleId"] 70 | 71 | //TODO: Save to right file 72 | w.Header().Set("Content-Type", "application/json") 73 | 74 | body, _ := ioutil.ReadAll(r.Body) 75 | var out bytes.Buffer 76 | json.Indent(&out, body, "", " ") 77 | 78 | if err := ioutil.WriteFile(fa.filename, out.Bytes(), 0666); err != nil { 79 | log.Fatalf("Can not copy from request to file: %s", err.Error()) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /filewatch/filewatch.go: -------------------------------------------------------------------------------- 1 | package filewatch 2 | 3 | import ( 4 | "io/ioutil" 5 | "log" 6 | "net/http" 7 | 8 | "github.com/fsnotify/fsnotify" 9 | "github.com/gorilla/websocket" 10 | ) 11 | 12 | var upgrader = websocket.Upgrader{ 13 | ReadBufferSize: 1024, 14 | WriteBufferSize: 1024, 15 | CheckOrigin: func(r *http.Request) bool { return true }, 16 | } 17 | 18 | func writer(ws *websocket.Conn, filename string) { 19 | watcher, err := fsnotify.NewWatcher() 20 | if err != nil { 21 | log.Fatal(err) 22 | } 23 | defer watcher.Close() 24 | 25 | done := make(chan bool) 26 | go func() { 27 | for { 28 | select { 29 | case event := <-watcher.Events: 30 | if event.Op&fsnotify.Write == fsnotify.Write { 31 | log.Println("Modified file:", event.Name) 32 | var p []byte 33 | var err error 34 | 35 | p, err = ioutil.ReadFile(filename) 36 | if err != nil { 37 | log.Fatal(err) 38 | } 39 | 40 | if p != nil { 41 | if err := ws.WriteMessage(websocket.TextMessage, p); err != nil { 42 | return 43 | } 44 | } 45 | } 46 | case err := <-watcher.Errors: 47 | log.Println("Watch error:", err) 48 | } 49 | } 50 | }() 51 | 52 | if err = watcher.Add(filename); err != nil { 53 | log.Fatal(err) 54 | } 55 | <-done 56 | } 57 | 58 | func ServeWebsocketFileWatcher(filename string, w http.ResponseWriter, r *http.Request) { 59 | ws, err := upgrader.Upgrade(w, r, nil) 60 | if err != nil { 61 | if _, ok := err.(websocket.HandshakeError); !ok { 62 | log.Println(err) 63 | } 64 | return 65 | } 66 | 67 | writer(ws, filename) 68 | defer ws.Close() 69 | } 70 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module maputnik/desktop 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/GeertJohan/go.rice v1.0.3 7 | github.com/fsnotify/fsnotify v1.6.0 8 | github.com/gorilla/handlers v1.5.1 9 | github.com/gorilla/mux v1.8.0 10 | github.com/gorilla/websocket v1.5.0 11 | github.com/maputnik/desktop v1.0.7 12 | github.com/urfave/cli v1.22.12 13 | ) 14 | 15 | require ( 16 | github.com/GeertJohan/go.incremental v1.0.0 // indirect 17 | github.com/akavel/rsrc v0.8.0 // indirect 18 | github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect 19 | github.com/daaku/go.zipexe v1.0.2 // indirect 20 | github.com/felixge/httpsnoop v1.0.1 // indirect 21 | github.com/jessevdk/go-flags v1.4.0 // indirect 22 | github.com/nkovacs/streamquote v1.0.0 // indirect 23 | github.com/russross/blackfriday/v2 v2.1.0 // indirect 24 | github.com/valyala/bytebufferpool v1.0.0 // indirect 25 | github.com/valyala/fasttemplate v1.0.1 // indirect 26 | golang.org/x/sys v0.0.0-20220908164124-27713097b956 // indirect 27 | ) 28 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= 2 | github.com/GeertJohan/go.incremental v1.0.0 h1:7AH+pY1XUgQE4Y1HcXYaMqAI0m9yrFqo/jt0CW30vsg= 3 | github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0= 4 | github.com/GeertJohan/go.rice v1.0.3 h1:k5viR+xGtIhF61125vCE1cmJ5957RQGXG6dmbaWZSmI= 5 | github.com/GeertJohan/go.rice v1.0.3/go.mod h1:XVdrU4pW00M4ikZed5q56tPf1v2KwnIKeIdc9CBYNt4= 6 | github.com/akavel/rsrc v0.8.0 h1:zjWn7ukO9Kc5Q62DOJCcxGpXC18RawVtYAGdz2aLlfw= 7 | github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c= 8 | github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= 9 | github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 10 | github.com/daaku/go.zipexe v1.0.2 h1:Zg55YLYTr7M9wjKn8SY/WcpuuEi+kR2u4E8RhvpyXmk= 11 | github.com/daaku/go.zipexe v1.0.2/go.mod h1:5xWogtqlYnfBXkSB1o9xysukNP9GTvaNkqzUZbt3Bw8= 12 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 13 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 14 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 15 | github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= 16 | github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= 17 | github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= 18 | github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= 19 | github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= 20 | github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= 21 | github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= 22 | github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= 23 | github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= 24 | github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 25 | github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA= 26 | github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= 27 | github.com/maputnik/desktop v1.0.7 h1:rdFg7emIJOT3YsZpwqSChmWtMOvu+T4h6WwVQAZP9n4= 28 | github.com/maputnik/desktop v1.0.7/go.mod h1:wmDjHUztx9jOBz0I22589yWguAGdV/sEM57YANpN8oQ= 29 | github.com/nkovacs/streamquote v1.0.0 h1:PmVIV08Zlx2lZK5fFZlMZ04eHcDTIFJCv/5/0twVUow= 30 | github.com/nkovacs/streamquote v1.0.0/go.mod h1:BN+NaZ2CmdKqUuTUXUEm9j95B2TRbpOWpxbJYzzgUsc= 31 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 32 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 33 | github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= 34 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 35 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 36 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 37 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 38 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 39 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 40 | github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= 41 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 42 | github.com/urfave/cli v1.22.12 h1:igJgVw1JdKH+trcLWLeLwZjU9fEfPesQ+9/e4MQ44S8= 43 | github.com/urfave/cli v1.22.12/go.mod h1:sSBEIC79qR6OvcmsD4U3KABeOTxDqQtdDnaFuUN30b8= 44 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= 45 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 46 | github.com/valyala/fasttemplate v1.0.1 h1:tY9CJiPnMXf1ERmG2EyK7gNUd+c6RKGD0IfU8WdUSz8= 47 | github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= 48 | golang.org/x/sys v0.0.0-20220908164124-27713097b956 h1:XeJjHH1KiLpKGb6lvMiksZ9l0fVUh+AmGcm0nOMEBOY= 49 | golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 50 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 51 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 52 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 53 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 54 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 55 | -------------------------------------------------------------------------------- /maputnik.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "os" 7 | "path/filepath" 8 | 9 | "github.com/GeertJohan/go.rice" 10 | "github.com/gorilla/handlers" 11 | "github.com/gorilla/mux" 12 | "github.com/maputnik/desktop/filewatch" 13 | "github.com/urfave/cli" 14 | ) 15 | 16 | func main() { 17 | app := cli.NewApp() 18 | app.Name = "maputnik" 19 | app.Usage = "Server for integrating Maputnik locally" 20 | app.Version = "Editor: 1.7.0; Desktop: 1.1.0" 21 | 22 | app.Flags = []cli.Flag{ 23 | &cli.StringFlag{ 24 | Name: "file, f", 25 | Usage: "Allow access to JSON style from web client", 26 | }, 27 | &cli.BoolFlag{ 28 | Name: "watch", 29 | Usage: "Notify web client about JSON style file changes", 30 | }, 31 | &cli.IntFlag{ 32 | Name: "port", 33 | Value: 8000, 34 | Usage: "TCP port to listen on", 35 | }, 36 | &cli.StringFlag{ 37 | Name: "static", 38 | Usage: "Serve directory under /static/", 39 | }, 40 | } 41 | 42 | app.Action = func(c *cli.Context) error { 43 | gui := http.FileServer(rice.MustFindBox("editor/public").HTTPBox()) 44 | 45 | router := mux.NewRouter().StrictSlash(true) 46 | 47 | filename := c.String("file") 48 | if filename != "" { 49 | fmt.Printf("%s is accessible via Maputnik\n", filename) 50 | // Allow access to reading and writing file on the local system 51 | path, _ := filepath.Abs(filename) 52 | accessor := StyleFileAccessor(path) 53 | router.Path("/styles").Methods("GET").HandlerFunc(accessor.ListFiles) 54 | router.Path("/styles/{styleId}").Methods("GET").HandlerFunc(accessor.ReadFile) 55 | router.Path("/styles/{styleId}").Methods("PUT").HandlerFunc(accessor.SaveFile) 56 | 57 | // Register websocket to notify we clients about file changes 58 | if c.Bool("watch") { 59 | router.Path("/ws").HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 60 | filewatch.ServeWebsocketFileWatcher(filename, w, r) 61 | }) 62 | } 63 | } 64 | 65 | staticDir := c.String("static") 66 | if staticDir != "" { 67 | h := http.StripPrefix("/static/", http.FileServer(http.Dir(staticDir))) 68 | router.PathPrefix("/static/").Handler(h) 69 | } 70 | 71 | router.PathPrefix("/").Handler(http.StripPrefix("/", gui)) 72 | loggedRouter := handlers.LoggingHandler(os.Stdout, router) 73 | corsRouter := handlers.CORS(handlers.AllowedHeaders([]string{"Content-Type"}), handlers.AllowedMethods([]string{"GET", "PUT"}), handlers.AllowedOrigins([]string{"*"}), handlers.AllowCredentials())(loggedRouter) 74 | 75 | fmt.Printf("Exposing Maputnik on http://localhost:%d\n", c.Int("port")) 76 | return http.ListenAndServe(fmt.Sprintf(":%d", c.Int("port")), corsRouter) 77 | } 78 | 79 | app.Run(os.Args) 80 | } 81 | --------------------------------------------------------------------------------