WASM: Store User Files in localStorage

* In WASM build, user levels and doodads are written to localStorage
  using their userdir path as keys (".config/levels/test.level")
* LoadFile() and WriteFile() for both Levels and Doodads interact with
  the localStorage for WASM build instead of filesystem for desktop.
* userdir.ListLevels() and ListDoodads() for WASM scan the localStorage
  keys for file names.
* userdir.ResolvePath() now works for WASM (previously was dummied out),
  checks for the file in localStorage.
This commit is contained in:
Noah 2019-06-27 15:59:18 -07:00
parent b17ca34de2
commit 35a89e5dbe
8 changed files with 89 additions and 26 deletions

1
.gitignore vendored
View File

@ -6,6 +6,7 @@ dist/
wasm/assets/ wasm/assets/
*.wasm *.wasm
*.doodad *.doodad
*.level
docker/ubuntu docker/ubuntu
docker/debian docker/debian
docker/fedora docker/fedora

View File

@ -43,12 +43,8 @@ func (d *Doodle) EditFile(filename string) error {
return d.EditDrawing(filename) return d.EditDrawing(filename)
} }
// WASM: no filesystem access, can go no further. // Check the user's levels directory. In WASM this will check in
if runtime.GOOS == "js" { // localStorage.
return fmt.Errorf("EditFile(%s): not found for WASM and can go no further", filename)
}
// Check the user's levels directory.
if foundFilename := userdir.ResolvePath(filename, extension, false); foundFilename != "" { if foundFilename := userdir.ResolvePath(filename, extension, false); foundFilename != "" {
log.Info("EditFile: resolved name '%s' to path %s", filename, foundFilename) log.Info("EditFile: resolved name '%s' to path %s", filename, foundFilename)
absPath = foundFilename absPath = foundFilename

View File

@ -68,6 +68,14 @@ func LoadFile(filename string) (*Doodad, error) {
// WASM: try the file over HTTP ajax request. // WASM: try the file over HTTP ajax request.
if runtime.GOOS == "js" { if runtime.GOOS == "js" {
if result, ok := wasm.GetSession(filename); ok {
log.Info("recall doodad data from localStorage")
return FromJSON(filename, []byte(result))
}
// TODO: ajax load for doodads might not work, filesystem.FindFile returns
// the base file for WASM but for now force it to system doodads path
filename = "assets/doodads/" + filename
jsonData, err := wasm.HTTPGet(filename) jsonData, err := wasm.HTTPGet(filename)
if err != nil { if err != nil {
return nil, err return nil, err
@ -90,13 +98,19 @@ func (d *Doodad) WriteFile(filename string) error {
d.Version = 1 d.Version = 1
d.GameVersion = branding.Version d.GameVersion = branding.Version
// bin, err := m.ToBinary()
bin, err := d.ToJSON() bin, err := d.ToJSON()
if err != nil { if err != nil {
return err return err
} }
// Save it to their profile directory. // WASM: place in localStorage.
if runtime.GOOS == "js" {
log.Info("wasm: write %s to localStorage", filename)
wasm.SetSession(filename, string(bin))
return nil
}
// Desktop: write to disk.
filename = userdir.DoodadPath(filename) filename = userdir.DoodadPath(filename)
log.Info("Write Doodad: %s", filename) log.Info("Write Doodad: %s", filename)
err = ioutil.WriteFile(filename, bin, 0644) err = ioutil.WriteFile(filename, bin, 0644)

View File

@ -105,7 +105,7 @@ func FindFile(filename string) (string, error) {
// WASM: can't check the filesystem. Let the caller go ahead and try // WASM: can't check the filesystem. Let the caller go ahead and try
// loading via ajax request. // loading via ajax request.
if runtime.GOOS == "js" { if runtime.GOOS == "js" {
return candidate, nil return filename, nil
} }
// external system level? // external system level?
@ -133,7 +133,7 @@ func FindFile(filename string) (string, error) {
// WASM: can't check the filesystem. Let the caller go ahead and try // WASM: can't check the filesystem. Let the caller go ahead and try
// loading via ajax request. // loading via ajax request.
if runtime.GOOS == "js" { if runtime.GOOS == "js" {
return candidate, nil return filename, nil
} }
// external system doodad? // external system doodad?

View File

@ -61,8 +61,14 @@ func LoadFile(filename string) (*Level, error) {
return FromJSON(filename, jsonData) return FromJSON(filename, jsonData)
} }
// WASM: try the file over HTTP ajax request. // WASM: try the file from localStorage or HTTP ajax request.
if runtime.GOOS == "js" { if runtime.GOOS == "js" {
if result, ok := wasm.GetSession(filename); ok {
log.Info("recall level data from localStorage")
return FromJSON(filename, []byte(result))
}
// Ajax request.
jsonData, err := wasm.HTTPGet(filename) jsonData, err := wasm.HTTPGet(filename)
if err != nil { if err != nil {
return nil, err return nil, err
@ -98,7 +104,6 @@ func (m *Level) WriteFile(filename string) error {
m.Version = 1 m.Version = 1
m.GameVersion = branding.Version m.GameVersion = branding.Version
// bin, err := m.ToBinary()
bin, err := m.ToJSON() bin, err := m.ToJSON()
if err != nil { if err != nil {
return err return err
@ -107,6 +112,15 @@ func (m *Level) WriteFile(filename string) error {
// Save it to their profile directory. // Save it to their profile directory.
filename = userdir.LevelPath(filename) filename = userdir.LevelPath(filename)
log.Info("Write Level: %s", filename) log.Info("Write Level: %s", filename)
// WASM: place in localStorage.
if runtime.GOOS == "js" {
log.Info("wasm: write %s to localStorage", filename)
wasm.SetSession(filename, string(bin))
return nil
}
// Desktop: write to disk.
err = ioutil.WriteFile(filename, bin, 0644) err = ioutil.WriteFile(filename, bin, 0644)
if err != nil { if err != nil {
return fmt.Errorf("level.WriteFile: %s", err) return fmt.Errorf("level.WriteFile: %s", err)

View File

@ -7,7 +7,7 @@ import (
"runtime" "runtime"
"strings" "strings"
"git.kirsle.net/apps/doodle/pkg/log" "git.kirsle.net/apps/doodle/pkg/wasm"
"github.com/kirsle/configdir" "github.com/kirsle/configdir"
) )
@ -77,6 +77,11 @@ func CacheFilename(filename ...string) string {
func ListDoodads() ([]string, error) { func ListDoodads() ([]string, error) {
var names []string var names []string
// WASM: list from localStorage.
if runtime.GOOS == "js" {
return wasm.StorageKeys(DoodadDirectory + "/"), nil
}
files, err := ioutil.ReadDir(DoodadDirectory) files, err := ioutil.ReadDir(DoodadDirectory)
if err != nil { if err != nil {
return names, err return names, err
@ -96,6 +101,11 @@ func ListDoodads() ([]string, error) {
func ListLevels() ([]string, error) { func ListLevels() ([]string, error) {
var names []string var names []string
// WASM: list from localStorage.
if runtime.GOOS == "js" {
return wasm.StorageKeys(LevelDirectory + "/"), nil
}
files, err := ioutil.ReadDir(LevelDirectory) files, err := ioutil.ReadDir(LevelDirectory)
if err != nil { if err != nil {
return names, err return names, err
@ -133,15 +143,11 @@ func resolvePath(directory, filename, extension string) string {
// existed. So the filename should have a ".level" or ".doodad" extension and // existed. So the filename should have a ".level" or ".doodad" extension and
// then this path will resolve the ProfileDirectory of the file. // then this path will resolve the ProfileDirectory of the file.
func ResolvePath(filename, extension string, one bool) string { func ResolvePath(filename, extension string, one bool) string {
// WASM has no file system.
if runtime.GOOS == "js" {
log.Error("userdir.ResolvePath: not supported in WASM build")
return ""
}
// If the filename exists outright, return it. // If the filename exists outright, return it.
if _, err := os.Stat(filename); !os.IsNotExist(err) { if !(runtime.GOOS == "js") {
return filename if _, err := os.Stat(filename); !os.IsNotExist(err) {
return filename
}
} }
var paths []string var paths []string
@ -157,6 +163,15 @@ func ResolvePath(filename, extension string, one bool) string {
} }
for _, test := range paths { for _, test := range paths {
// WASM: check the path in localStorage.
if runtime.GOOS == "js" {
if _, ok := wasm.GetSession(test); ok {
return test
}
continue
}
// Desktop: test the filesystem.
if _, err := os.Stat(test); os.IsNotExist(err) { if _, err := os.Stat(test); os.IsNotExist(err) {
continue continue
} }

View File

@ -2,6 +2,12 @@
package wasm package wasm
// StorageKeys returns the list of localStorage keys matching a prefix.
// This is a no-op when not in wasm.
func StorageKeys(prefix string) []string {
return []string{}
}
// SetSession sets a binary value on sessionStorage. // SetSession sets a binary value on sessionStorage.
// This is a no-op when not in wasm. // This is a no-op when not in wasm.
func SetSession(key string, value string) { func SetSession(key string, value string) {

View File

@ -3,20 +3,37 @@
package wasm package wasm
import ( import (
"strings"
"syscall/js" "syscall/js"
"git.kirsle.net/apps/doodle/pkg/log"
) )
// StorageKeys returns the list of localStorage keys matching a prefix.
func StorageKeys(prefix string) []string {
keys := js.Global().Get("Object").Call("keys", js.Global().Get("localStorage"))
var result []string
for i := 0; i < keys.Length(); i++ {
value := keys.Index(i).String()
if strings.HasPrefix(value, prefix) {
result = append(result,
strings.TrimPrefix(keys.Index(i).String(), prefix),
)
}
}
log.Info("LS KEYS: %+v", result)
return result
}
// SetSession sets a text value on sessionStorage. // SetSession sets a text value on sessionStorage.
func SetSession(key string, value string) { func SetSession(key string, value string) {
// b64 := base64.StdEncoding.EncodeToString(value) js.Global().Get("localStorage").Call("setItem", key, value)
panic("SesSession: " + key)
js.Global().Get("sessionStorage").Call("setItem", key, value)
} }
// GetSession retrieves a text value from sessionStorage. // GetSession retrieves a text value from sessionStorage.
func GetSession(key string) (string, bool) { func GetSession(key string) (string, bool) {
panic("GetSession: " + key)
var value js.Value var value js.Value
value = js.Global().Get("sessionStorage").Call("getItem", key) value = js.Global().Get("localStorage").Call("getItem", key)
return value.String(), value.Type() == js.TypeString return value.String(), value.Type() == js.TypeString
} }