Implementation

TODO

editor/controller/save.lua
editor/parts/controller/properties/save.lua

implementation Details

the following functions in editor.controller.index class are mainly used for CRUD of json/lua files in select, save, delete etc. See editor.controller.save.

separate controllers for the audio, group, timer and variable components are implemented i.e

  • useClassEditorProps
  • setValue
  • render
  • save
  • read

select (read)

See editor.parts.controller directory. selectXXX lua reads json and displays a UI table. The data is passed by nanostore such as bookStore:set, pageStore:set.

nanostore is used as an experimental usage

  1. selectApp.lua

    local appFolder = params.appFolder or system.pathForFile( "App", system.ResourceDirectory )
    
    if params.useTinyfiledialogs then
      appFolder = tfd.selectFolderDialog({
        title = "select App folder",
        default_path = path
      })
      -- print("", appFolder)
    end
    
    if success then
      local books = {}
      for file in lfs.dir( appFolder ) do
        if util.isDir(file) then
          -- print("",  "Found file: " .. file )
          -- set them to nanostores
          if file:len() > 3 and file ~="kwikEditor" then
            table.insert(books, {name = file, path= util.PATH(appFolder.."/"..file)})
          end
        end
      end
      if #books > 0 then
        UI.editor.bookStore:set(books)
      end
  2. selectBook.lua

    local path =system.pathForFile( "App/"..bookName.."/models", system.ResourceDirectory)
    UI.editor.currentBook = bookName
    local success = lfs.chdir( path ) -- isDir works with current dir
    if success then
      local pages = {}
      for file in lfs.dir( path ) do
        if util.isDir(file) then
          -- set them to nanostores
          if file:len() > 3 and file ~='assets' then
            table.insert(pages, {name = file, path= util.PATH(path.."/"..file)})
          end
        end
      end
      if #pages > 0 then
        UI.editor.pageStore:set(pages)
      end
    end
  3. selectPage.lua

    if params.page:len() > 0 and UI.page ~= params.page then
      local app = App.get()
      app:showView("components." .. params.page .. ".index", {effect = "slideDown"})
    end
  • selectLayer.lua

    local path = system.pathForFile( "App/"..UI.editor.currentBook.."/models/"..UI.page .."/"..params.path..getFileName(layerName, className)..".json", system.ResourceDirectory)
    ...
    propsTable:setValue(decoded)
    ...

    instead of nanostore, a traditional set/get functions are used here

    • selectTool.lua

      this loads class’s properties of a layer

       if params.layer then -- this measn user clicks one of class, anim, button, drag ...
          UI.editor.currentLayer = params.layer
       end
      
       tool.controller:read(UI.editor.currentBook, UI.page, UI.editor.currentLayer, params.isNew, params.class)

      if params.isNew then it loads default values to propsTable

      • edtior.controller.index

        function M:read(book, page, layer, isNew, class)
          print("read", page, layer, isNew, class)
          -- the values are used in useClassEdtiorProps()
          self.page = page
          self.layer = layer
          self.isNew = isNew
          self.class = class
        
          if isNew then
            local path = "editor.template.components.pageX."..self.layerTool..".defaults."..class
            local template = require(path)
            self:reset()
            self:setValue(template, nil, true)
            self:redraw()
          elseif layer then
            -- this comes from clicking layerTable.class
            local layerName = layer or "index"
            --local path      = page .."/"..layerName.."_"..self.layerTool..".json"
            local path      = system.pathForFile( "App/"..book.."/models/"..page .."/"..layerName.."_"..self.layerTool..".json", system.ResourceDirectory)
            if self.lastSelection ~= path then
              self.lastSelection   = path
              local decoded,  pos, msg = json.decodeFile( path )
              if not decoded then
                  print( "Decode failed at "..tostring(pos)..": "..tostring(msg) )
              else
                  print( "File successfully decoded!" )
              end
              self:reset()
              self:setValue(decoded, 1)
              self:redraw()
            else
              self.view.isNew = true
              toolbar:toogleToolMap()
            end
          end
        end

      For layer’s class, the json is retrived by tool.controller:read above and the read function calls setValue(decoded) inside to display the data to controlProps table.

      • editor.controller.index

        function M:setValue(decoded, index, template)
          if decoded == nil then return end
          if not template then
            print(json.encode(decoded[index]))
            self.selectbox:setValue(decoded, index)  -- "linear 1", "rotation 1" ...
            self.controlProps:setValue(decoded[index].controls)
            self.onCompletebox:setValue(decoded[index].actionName)
          else
            self.selectbox:setTemplate(decoded)  -- "linear 1", "rotation 1" ...
            self.controlProps:setValue(decoded.controls)
            self.onCompletebox:setValue(decoded.actionName)
          end
        end

        generic setValue is implemented in edtior.controller.index, and components can have own setValue for their UI table. for instance,

        • editor/animation/controller.lua
        • editor/replacement/controller/index.lua

  • editor.controller.index

    the command() reads json with util.decode() from params.

    function M:command()
    local instance = require("commands.kwik.baseCommand").new(
    function (params)
      local UI    = params.UI
      local name =  params[params.class] or ""
      local decoded = util.decode(params) -- this reads models/xx.json
      --
      print("From selectors")
      self.controlProps:didHide(UI)
      self.controlProps:destroy(UI)
      self.controlProps:init(UI)
      self.controlProps:setValue(decoded)
      self.controlProps.isNew = params.isNew
      --
      self.controlProps:create(UI)
      self.controlProps:didShow(UI)
    
      --
      -- self:show()
      self.controlProps:show()
      self.onCompletebox:show()
      self.buttons:show()
     ...
    • util.decode
    function  M.decode(params)
      local UI = params.UI
      if params.isNew then
        local path = "editor.template.components.pageX."..params.class..".defaults."..params.class
        return  require(path)
      elseif params.isDelete then
        print(params.class, "delete")
        return {}
      else
        local name = params[params.class] or ""
        if params.subclass then
          name = params.subclass.."/"..name
        end
        local path = system.pathForFile( "App/"..UI.editor.currentBook.."/models/"..UI.page .."/"..params.class.."s/"..name..".json", system.ResourceDirectory)
        decoded, pos, msg = json.decodeFile( path )
        if not decoded then
          print( "Decode failed at "..tostring(pos)..": "..tostring(msg), path )
          decoded = {}
        end
        return decoded or {}
      end
    end
  • selectAudio.lua

    require(“editor.audio.index”).controller:command()

  • selectTimer.lua

    require(“editor.timer.index”).controller:command()

  • selectVariable.lua

    require(“editor.variable.index”).controller:command()


  • selectGroup.lua

    require(“editor.group.controller.selectGroup”)

    selectGroup returns a command() for Group

    local command = function (params)
     local UI    = params.UI
     local name =  params.group or ""
    
     print (params.class)
     print("selectGroup", name, path, params.show)
    
     --print(debug.traceback())
    
     local tableData
    
     if params.isNew then
       local boxData = util.read( UI.editor.currentBook, UI.page)
       --
       tableData = {
         name = "(new-group)",
         layers = {},
         children = {},
         alpha =  nil,
         xScale =  nil,
         yScale =  nil,
         rotation =  nil,
         isLuaTable = nll
       }
    
       UI.editor.groupLayersStore:set(tableData) -- layersTable
       UI.editor.layerJsonStore:set(boxData.layers) -- layersbox
    
     elseif params.isDelete then
     elseif name:len() > 0 then
       --
       -- layersTable
       --
       local path = system.pathForFile( "App/"..UI.editor.currentBook.."/models/"..UI.page .."/groups/"..name..".json", system.ResourceDirectory)
       tableData, pos, msg = json.decodeFile( path )
       if not tableData then
         print( "Decode failed at "..tostring(pos)..": "..tostring(msg), path )
         tableData = {}
       end
       --
       -- layersbox
       --
       local boxData = util.read( UI.editor.currentBook, UI.page, function(parent, name)
         -- let's remove entries of tableData from boxData
         --    layers = ["GroupA.Ellipse", "GroupA.SubA.Triangle"]
         for i=1, #tableData.layers do
           local _name = tableData.layers[i]
           if parent then
             if parent .."."..name == _name then
               return true
             end
           elseif name == _name then
             return true
           end
         end
         return false
       end)
    
       UI.editor.layerJsonStore:set(boxData.layers) -- layersbox
       UI.editor.groupLayersStore:set(tableData) -- layersTable
    
     end
     --
     editor:show()
  • selectPageIcons.lua

    local command = function (params)
      ...
      local path = system.pathForFile( "App/"..UI.editor.currentBook.."/models/settings.json", system.ResourceDirectory)
      ...
      settingsTable:setValue(decoded)
      ...

create a new component from toolbar

  • selectToolbar.lua

UI.scene.model

UI.scene.model is set when selectPage is called

local scene = require('controller.scene').new(sceneName, {
name = "page1",
components = {
  layers = {
        {  bg={
                          } },
        {  gotoBtn={ --class={"animation"}
                          } },
        {  title={ class={"linear"} } },
  },
  audios = {},
  groups = {},
  timers = {},
  variables = {},
  page = { }
},
commands = { "eventOne", "eventTwo", "act01" },
onInit = function(scene) print("onInit") end
})

util.read() function parses “App/”..book.."/models/"..page .."/index.json"

...
ret.layers = parser(decoded)
...
setFiles(ret.audios, "/audios/short")
setFiles(ret.audios, "/audios/long")
setFiles(ret.groups, "/groups")
setFiles(ret.commands, "/commands")
return ret
{
  layers = {
    {name = "layerOne", parent="", children = {
        {name="childOne}, parent="layerOne", children = {}}
      }
    },
    {name = "layerTwo", parent="", children = {}},
  },
  audios = {}
}

save (write)

CRUD operations on json/lua files are mainly for commands and components in a selected page

the editor does not support to create a new entry of App, Book, Page. The properties can be modified for App, Book, Page.

  • create/update a layer class
    • get props
    • render App/booxX/components/pageX/layers/XXX.lua
    • save it as a json file
  • util.createIndexModel returns the updated scene model if a classname for a layer is newly added

layer names must be unique in a scene model.

```lua
util.createIndexModel(UI.scene.model, UI.editor.currentLayer, classname)
```

```lua
scene = {
  name = "canvas",
  components = {
    layers = {
        {  back={
                          } },
        {  butBlue={ class={"button"}, {A={}}, {B={}}
                          } },
        {  butWhite={
                          } },
    },
    audios = {  },
    groups = {  },
    timers = {  },
    variables = {  },
    others = {  }
  },
}
```
- render App.booxX.components.pageX.index.lua
- save json
  • editor.controller.save
local name = ...
local parent, root = parent_root(name)
local util = require("editor.util")
local json = require("json")
--
-- save command performs on one entry.
-- If user switch to another entry without saving, the previous change will be lost.
--
local instance =
  require("commands.kwik.baseCommand").new(
  function(params)
    local UI = params.UI
    local props = params.props
    local tool = UI.editor:getTool(props.class) -- each tool.contoller can overide render/save. So page tools of audio, group, timer should use own render/save
    if tool then
      local files = {}
      local toolName = UI.editor:getToolName(props.class)
      local filename = props.name
      local classname = props.class:lower()
      -------------
      -- save lua
      files[#files+1] = tool.controller:render(UI.editor.currentBook, UI.page, UI.editor.currentLayer, toolName, classname, props)
      -----------
      --- save json
      local decoded = params.decoded or {}
      decoded[props.index] = props
      --
      files[#files+1] = tool.controller:save(UI.editor.currentBook, UI.page, UI.editor.currentLayer,toolName, decoded)
      -----------
      --- Update components/pageX/index.lua model/pageX/index.json
      local updatedModel = util.createIndexModel(UI.scene.model, UI.editor.currentLayer, classname)
      files[#files+1] = tool.controller:renderIndex(UI.editor.currentBook, UI.page, updatedModel)
      files[#files+1] = tool.controller:saveIndex(UI.editor.currentBook, UI.page, UI.editor.currentLayer,classname, updatedModel)
      ----------
      -- publish
      util.executePubish(files)
    else
      print("tool not found for", props.class)
    end
  end
)
--
return instance

assets

media files of audio, layer replacements(video, spritesheet, particle, syncText, web) in App/bookX/assets folder are indexed with linked layers in assets.json

So tool.controller:save() also performs a write operation on assets.json