Skip to content
GitLab
    • Explore Projects Groups Snippets
Projects Groups Snippets
  • /
  • Help
    • Help
    • Support
    • Community forum
    • Submit feedback
    • Contribute to GitLab
  • Sign in / Register
  • I iOS application
  • Project information
    • Project information
    • Activity
    • Labels
    • Members
  • Repository
    • Repository
    • Files
    • Commits
    • Branches
    • Tags
    • Contributors
    • Graph
    • Compare
  • Issues 155
    • Issues 155
    • List
    • Boards
    • Service Desk
    • Milestones
  • Merge requests 0
    • Merge requests 0
  • CI/CD
    • CI/CD
    • Pipelines
    • Jobs
    • Schedules
  • Deployments
    • Deployments
    • Environments
    • Releases
  • Packages and registries
    • Packages and registries
    • Package Registry
    • Container Registry
    • Infrastructure Registry
  • Monitor
    • Monitor
    • Incidents
  • Analytics
    • Analytics
    • Value stream
    • CI/CD
    • Repository
  • Wiki
    • Wiki
  • Snippets
    • Snippets
  • Activity
  • Graph
  • Create a new issue
  • Jobs
  • Commits
  • Issue Boards
Collapse sidebar
  • MemriMemri
  • iOS application
  • Merge requests
  • !71

General editor

  • Review changes

  • Download
  • Email patches
  • Plain diff
Merged Koen van der Veen requested to merge general-editor into dev 5 years ago
  • Overview 0
  • Commits 10
  • Pipelines 2
  • Changes 27
Compare
  • version 1
    19e56739
    5 years ago

  • dev (base)

and
  • latest version
    0f9d655e
    10 commits, 5 years ago

  • version 1
    19e56739
    9 commits, 5 years ago

27 files
+ 877
- 134

    Preferences

    File browser
    Compare changes
me‎mri‎
a‎pi‎
api.‎swift‎ +7 -0
con‎trol‎
rend‎erers‎
genera‎lEditor‎
GeneralEd‎itor.swift‎ +98 -0
GeneralEdit‎orRow.swift‎ +91 -0
GeneralEdit‎orView.swift‎ +103 -0
rend‎erer‎
Rendere‎rs.swift‎ +145 -11
Applicat‎ion.swift‎ +4 -0
mo‎del‎
Cache‎.swift‎ +1 -0
model‎.swift‎ +30 -4
schema‎.swift‎ +27 -0
vi‎ew‎
act‎ion‎
ActionNa‎me.swift‎ +7 -0
Views‎.swift‎ +1 -82
AppDeleg‎ate.swift‎ +1 -1
memri.x‎codeproj‎
project‎.pbxproj‎ +90 -30
memri‎Tests‎
da‎ta‎
no‎tes‎
note.0x‎01.json‎ +0 -0
note.0x‎02.json‎ +0 -0
note.0x‎03.json‎ +0 -0
note.0x‎04.json‎ +0 -0
note.0x‎05.json‎ +0 -0
per‎sons‎
person.0‎x01.json‎ +13 -0
person.0‎x02.json‎ +12 -0
person.0‎x03.json‎ +12 -0
default_nav‎igation.json‎ +5 -0
default_se‎ssions.json‎ +6 -1
named_vi‎ews.json‎ +11 -0
persons_from‎_server.json‎ +41 -0
views_from_‎server.json‎ +171 -4
vi‎ew‎
SessionT‎est.swift‎ +1 -1
memri/api/api.swift
+ 7
- 0
  • View file @ 0f9d655e

  • Edit in single-file editor

  • Open in Web IDE


@@ -167,11 +167,18 @@ public class PodAPI {
return
}
if query.query!.prefix(6) == "person" {
callback(nil, try! DataItem.fromJSONFile("persons_from_server"))
return
}
if query.query!.prefix(4) == "note" {
callback(nil, try! DataItem.fromJSONFile("notes_from_server"))
return
}
// TODO do nothing
// let items:[DataItem] = try! DataItem.fromJSONFile("test_dataItems")
// callback(nil, items);
memri/control/renderers/generalEditor/GeneralEditor.swift 0 → 100644
+ 98
- 0
  • View file @ 0f9d655e

  • Edit in single-file editor

  • Open in Web IDE

//
// generalEditor.swift
// memri
//
// Created by Koen van der Veen on 14/04/2020.
// Copyright © 2020 memri. All rights reserved.
//
import Foundation
import RealmSwift
class GeneralEditor: Renderer{
required init(){
super.init()
self.name = "generalEditor"
self.icon = "pencil.circle.fill"
}
override func canDisplayResultSet(items: [DataItem]) -> Bool{
return items.count == 1
}
}
class GeneralEditorConfig: RenderConfig{
@objc dynamic var type: String? = "generalEditor"
@objc dynamic var _groups: String? = nil
let readOnly = List<String>()
let excluded = List<String>()
var groups: [String:[String]]? {
if self._groups != nil{
if let groups:[String:[String]] = renderCache.get(self._groups!) {
return groups
}
else if let description = self._groups {
if let groups:[String:[String]] = unserialize(description) {
renderCache.set(description, groups)
return groups
}
}
return nil
}
else{
return nil
}
}
public func allGroupValues() -> [String]{
if let all_groups = self.groups{
return all_groups.values.flatMap{ Array($0)}
}else{
return []
}
}
public convenience required init(from decoder: Decoder) throws {
self.init()
jsonErrorHandling(decoder) {
self.type = try decoder.decodeIfPresent("type") ?? self.type
decodeIntoList(decoder, "readOnly", self.readOnly)
decodeIntoList(decoder, "excluded", self.excluded)
if let parsedJSON:[String:AnyCodable] = try decoder.decodeIfPresent("groups") {
self._groups = String(
data: try! MemriJSONEncoder.encode(parsedJSON), encoding: .utf8)!
}
try! self.superDecode(from: decoder)
}
}
required init() {
super.init()
}
public func merge(_ generalEditorConfig:GeneralEditorConfig) {
self.type = generalEditorConfig.type ?? self.type
if let otherGroups = generalEditorConfig.groups, otherGroups.count > 0 {
var groups = self.groups ?? [:]
for (key, value) in otherGroups {
groups[key] = value
}
self._groups = serialize(AnyCodable(groups))
}
self.excluded.append(objectsIn: generalEditorConfig.excluded)
self.readOnly.append(objectsIn: generalEditorConfig.readOnly)
super.superMerge(generalEditorConfig)
}
}
memri/control/renderers/generalEditor/GeneralEditorRow.swift 0 → 100644
+ 91
- 0
  • View file @ 0f9d655e

  • Edit in single-file editor

  • Open in Web IDE

//
// GeneralEditorRow.swift
// memri
//
// Created by Koen van der Veen on 16/04/2020.
// Copyright © 2020 memri. All rights reserved.
//
import SwiftUI
import RealmSwift
struct GeneralEditorRow: View {
@EnvironmentObject var main: Main
var item: DataItem? = nil
var prop: String = ""
var horizontalPadding: CGFloat = CGFloat(0)
var readOnly: Bool = false
var body: some View {
VStack(alignment: .leading, spacing: 12){
Text(prop
.camelCaseToWords()
.lowercased()
.capitalizingFirstLetter()
)
.foregroundColor(Color(red: 0.21, green: 0.46, blue: 0.11))
.offset(y: 8)
.padding(.bottom, 8)
if self.item![prop] is String {
if !self.readOnly{
VStack<TextField<Text>> {
let binding = Binding<String>(
get: { self.item!.getString(self.prop) },
set: {
if self.main.currentSession.isEditMode == .active {
self.item!.set(self.prop, $0)
}
}
)
return TextField("", text: binding)
}
}else{
Text(self.item!.getString(self.prop))
}
}
else if self.item![prop] is Bool{
Button(action: {
self.item!.toggle(self.prop)
self.main.objectWillChange.send()
}) {
Image(systemName: self.item![prop]! as! Bool ? "checkmark.square" : "square")
.font(Font.system(size: 24, weight: .semibold))
.padding(.bottom, 8)
.foregroundColor(Color(hex: "#333"))
}
}
else if self.item![prop] is Date {
Text(self.item!.getString(prop))
.padding(.bottom, 8)
.font(Font.system(size: 14, weight: .medium))
.foregroundColor(Color(hex: "#333"))
}
else if self.item![prop] is RealmSwift.List<Label>{
ForEach(self.item![prop] as! RealmSwift.List<Label>){ label in
Text(label.name)
.padding(.bottom, 8)
}
}else{
Text(prop.camelCaseToWords().lowercased().capitalizingFirstLetter())
.padding(.bottom, 8)
.foregroundColor(Color(UIColor.systemGray))
}
}
.fullWidth()
.padding(.horizontal, self.horizontalPadding)
.background(Color(UIColor.systemGreen).opacity(0.1))
.border(Color(UIColor.systemGray).opacity(0.2), width: 1)
}
}
struct GeneralEditorRow_Previews: PreviewProvider {
static var previews: some View {
GeneralEditorRow()
}
}
memri/control/renderers/generalEditor/GeneralEditorView.swift 0 → 100644
+ 103
- 0
  • View file @ 0f9d655e

  • Edit in single-file editor

  • Open in Web IDE

//
// GeneralEditorView.swift
// memri
//
// Created by Koen van der Veen on 14/04/2020.
// Copyright © 2020 memri. All rights reserved.
//
import SwiftUI
import RealmSwift
struct _GeneralEditorView: View {
@EnvironmentObject var main: Main
var name: String="generalEditor"
var item: DataItem? = nil
let horizontalPadding = CGFloat(36)
var renderConfig: GeneralEditorConfig {
return self.main.computedView.renderConfigs[name] as? GeneralEditorConfig ?? GeneralEditorConfig()
}
var body: some View {
return VStack{
if self.item != nil{
ScrollView{
VStack(alignment: .leading){
if renderConfig.groups != nil{
ForEach(Array(renderConfig.groups!.keys), id: \.self){key in
VStack(alignment: .leading){
HStack{
Text("\(key)".uppercased())
.font(Font.system(size: 18, weight: .medium))
}.padding(.top, 24)
.padding(.horizontal, self.horizontalPadding)
.foregroundColor(Color(hex: "#333"))
ForEach(self.renderConfig.groups![key]!, id: \.self){ prop in
GeneralEditorRow(item: self.item!,
prop: prop,
horizontalPadding: self.horizontalPadding,
readOnly: self.renderConfig.readOnly.contains(prop))
}
}
}
}
HStack(){
Text("MISC")
.font(Font.system(size: 18, weight: .medium))
}.padding(.top, 24)
.padding(.horizontal, self.horizontalPadding)
.foregroundColor(Color(hex: "#333"))
ForEach(getProperties(), id: \.self){prop in
VStack(alignment: .leading){
if !self.renderConfig.excluded.contains(prop) && !self.renderConfig.allGroupValues().contains(prop){
GeneralEditorRow(item: self.item!,
prop: prop,
horizontalPadding: self.horizontalPadding,
readOnly: self.renderConfig.readOnly.contains(prop))
}
}
}
}
}
} else{
EmptyView()
}
}
}
init(item: DataItem){
self.item = item
}
func getProperties() -> [String]{
let properties = item!.objectSchema.properties
return properties.map({$0.name})
}
}
struct GeneralEditorView: View {
@EnvironmentObject var main: Main
var body: some View {
_GeneralEditorView(item: main.computedView.resultSet.item!)
}
}
struct GeneralEditorView_Previews: PreviewProvider {
static var previews: some View {
GeneralEditorView().environmentObject(Main(name: "", key: "").mockBoot())
}
}
memri/control/renderers/renderer/Renderers.swift
+ 145
- 11
  • View file @ 0f9d655e

  • Edit in single-file editor

  • Open in Web IDE


@@ -14,13 +14,15 @@ public class Renderers {
var all: [String: Renderer] = [
"list": ListRenderer(),
"richTextEditor": RichTextRenderer(),
"thumbnail": ThumbnailRenderer()
"thumbnail": ThumbnailRenderer(),
"generalEditor": GeneralEditor()
]
var allViews: [String: AnyView] = [
"list": AnyView(ListRendererView()),
"richTextEditor": AnyView(RichTextRendererView()),
"thumbnail": AnyView(ThumbnailRendererView())
"thumbnail": AnyView(ThumbnailRendererView()),
"generalEditor": AnyView(GeneralEditorView())
]
var tuples: [(key: String, value: Renderer)] {
@@ -62,10 +64,12 @@ public class RenderConfigs: Object, Codable {
*
*/
@objc dynamic var thumbnail: ThumbnailConfig? = nil
/**
*
*/
@objc dynamic var generalEditor: GeneralEditorConfig? = nil
public func merge(_ renderConfigs:RenderConfigs) {
if let config = renderConfigs.list {
if self.list == nil { self.list = ListConfig() }
@@ -75,6 +79,10 @@ public class RenderConfigs: Object, Codable {
if self.thumbnail == nil { self.thumbnail = ThumbnailConfig() }
self.thumbnail!.merge(config)
}
if let config = renderConfigs.generalEditor {
if self.generalEditor == nil { self.generalEditor = GeneralEditorConfig() }
self.generalEditor!.merge(config)
}
}
public convenience required init(from decoder: Decoder) throws {
@@ -83,6 +91,7 @@ public class RenderConfigs: Object, Codable {
jsonErrorHandling(decoder) {
self.list = try decoder.decodeIfPresent("list") ?? self.list
self.thumbnail = try decoder.decodeIfPresent("thumbnail") ?? self.thumbnail
self.generalEditor = try decoder.decodeIfPresent("generalEditor") ?? self.generalEditor
}
}
}
@@ -113,23 +122,29 @@ public class RenderConfig: Object, Codable {
*/
let options2 = RealmSwift.List<ActionDescription>()
/**
*
*/
@objc dynamic var _renderDescription: String? = nil
/**
*
*/
var renderDescription: [String:GUIElementDescription]? {
if let itemRenderer = renderCache.get(self._renderDescription!) {
return itemRenderer
if let renderDescription:[String: GUIElementDescription]
= renderCache.get(self._renderDescription!) {
return renderDescription
}
else if var description = self._renderDescription {
if let itemRenderer:[String: GUIElementDescription] = unserialize(description) {
renderCache.set(description, itemRenderer)
return itemRenderer
else if let description = self._renderDescription {
if let renderDescription:[String: GUIElementDescription] = unserialize(description) {
renderCache.set(description, renderDescription)
return renderDescription
}
}
return nil
}
@objc dynamic var _renderDescription: String? = nil
/**
*
@@ -164,16 +179,135 @@ public class RenderConfig: Object, Codable {
self.name = try decoder.decodeIfPresent("name") ?? self.name
self.icon = try decoder.decodeIfPresent("icon") ?? self.icon
self.category = try decoder.decodeIfPresent("category") ?? self.category
self._renderDescription = try decoder.decodeIfPresent("renderDescription") ?? self._renderDescription
// Receiving a string from the preprocessed view description for storage in realm
if let parsedJSON:[String:AnyCodable] = try decoder.decodeIfPresent("renderDescription") {
self._renderDescription = String(
data: try! MemriJSONEncoder.encode(parsedJSON), encoding: .utf8)!
}
decodeIntoList(decoder, "items", self.items)
decodeIntoList(decoder, "options1", self.options1)
decodeIntoList(decoder, "options2", self.options2)
}
/*
Loading views from disk
1. read user defined json string from disk and puts it in a DynamicView that can be stored in realm
The first time a view is instantiated (with the goal of creating a SessionView)
2. Load json string in CompiledView and parse it into [String:Any] object tree structure
3. Pre-process object tree into an optimized json string so that its very fast to generate a SessionView by replacing all dynamic properties (e.g. {.title}) as needed in step (4) and preprocessing what is needed (i.e. parseRenderDescription() that turns the renderDescription into a string)
How is the json string optimized?
- Looks up all the variables and puts them in a dict
- Skips over variables that need to be calculated at runtime (i.e. ActionDescription.actionStateName)
- Replaces the variables that need to be replaces with {$0} (0 being a auto-increment)
- At the end you have a dict and a json string
Any time a view is instantiated
4. During CompiledView.generateView() the optimized json string (using the dict) is decoded using Codable into a SessionView, and its class hierarchy. (i.e. SessionView.renderConfigs.* is the RenderConfig). N.B. in the class hierarchy we have a _renderDescription that is a string.
5. RenderConfig gets the renderDescription string in that process from the json and stores it in _renderDescription for potential storage in realm
When .render() is called
6. First time, the _renderDescription json string is decoded into [String:GUIElement] and added to the RenderCache
7. Next time .render() is called simply get the [String:GUIElement] from the cache
*/
// How users type it
// [ "VStack", { "padding": 5 }, [
// "Text", { "value": "{.content}" },
// "Button", { "press": {"actionName": "back"} }, ["Text", "Back"],
// "Button", { "press": {"actionName": "openView"} }, [
// "Image", {"systemName": "star.fill"}
// ],
// "Text", { "value": "{.content}" }
// ]]
// How codable wants it - the above is transformed into below in parseRenderDescription()
// {
// "type": "vstack",
// "children": [
// {
// "type": "text",
// "properties": {
// "value": "{.title}",
// "bold": true
// }
// },
// {
// "type": "text",
// "properties": {
// "values": "{.content}",
// "bold": false,
// "removeWhiteSpace": true,
// "maxChar": 100
// }
// }
// ]
// }
// This is called from CompiledView when pre-processing the view
public class func parseRenderDescription(_ parsed: Any) -> Any {
var pDict:[String:Any]
var result:[String:Any] = [:]
// Make sure the description is in a dict, otherwise wrap the array in one
if let pList = parsed as? [Any] { pDict = ["*": pList] }
else { pDict = parsed as! [String:Any] }
for (key, value) in pDict {
result[key] = try! parseSingleRenderDescription(value as! [Any])
}
// Returning a string to optimize savin as a string in realm
return result
}
private class func parseSingleRenderDescription(_ parsed:[Any]) throws -> Any {
var result:[Any] = []
func walkParsed(_ parsed:[Any], _ result:inout [Any]) throws {
var currentItem:[String:Any] = [:]
for item in parsed {
if let item = item as? String {
if currentItem["type"] != nil { result.append(currentItem) }
currentItem = ["type": item.lowercased()]
}
else if let item = item as? [String: Any] {
currentItem["properties"] = item
}
else if let item = item as? [Any] {
var children:[Any] = []
try! walkParsed(item, &children)
currentItem["children"] = children
}
else {
throw "Exception: Could not parse render description"
}
}
if currentItem["type"] != nil { result.append(currentItem) }
}
try! walkParsed(parsed, &result)
return result[0]
}
}
class RenderCache {
var cache:[String:[String:GUIElementDescription]] = [:]
var dictCache:[String:[String:[String]]] = [:]
public func get(_ key:String) -> [String:[String]]? {
return dictCache[key]
}
public func set(_ key:String, _ dict: [String:[String]]) {
dictCache[key] = dict
}
public func get(_ key:String) -> [String:GUIElementDescription]? {
return cache[key]
0 Assignees
None
Assign to
0 Reviewers
None
Request review from
Labels
0
None
0
None
    Assign labels
  • Manage project labels

Milestone
No milestone
None
None
Time tracking
No estimate or time spent
Lock merge request
Unlocked
2
2 participants
Ruben Daniels
Koen van der Veen
Reference: memri/ios-application-fixed-schema!71
Source branch: general-editor

Menu

Explore Projects Groups Snippets