Commit fe17b865 authored by Ruben Daniels's avatar Ruben Daniels
Browse files

Merge branch 'merge-toby' into 'dev'

Merge toby

See merge request memri/ios-application!151
parents 77ade2b2 b4960f29
Showing with 791 additions and 764 deletions
+791 -764
This diff is collapsed.
......@@ -42,8 +42,8 @@
"repositoryURL": "https://github.com/realm/realm-cocoa",
"state": {
"branch": null,
"revision": "3857a5e1325d1f16667ea03ce828a9855c412882",
"version": "5.2.0"
"revision": "fa43b8e2909334c79f233ce472332c136ca108da",
"version": "4.4.1"
}
},
{
......@@ -51,8 +51,8 @@
"repositoryURL": "https://github.com/realm/realm-core",
"state": {
"branch": null,
"revision": "8f7b9327111f974ea4961b8641af103fd4f6755a",
"version": "6.0.8"
"revision": "35662ff940e340bf630ad1d1d88acfc7af18bee6",
"version": "5.23.8"
}
}
]
......
......@@ -14,26 +14,25 @@ protocol UniqueString {
var uniqueString: String { get }
}
public class Datasource: SchemaItem, UniqueString {
/// Primary key used in the realm database of this Item
override public static func primaryKey() -> String? {
"uid"
}
public class Datasource: Equatable, UniqueString {
public static func == (lhs: Datasource, rhs: Datasource) -> Bool {
lhs.uniqueString == rhs.uniqueString
}
/// Retrieves the query which is used to load data from the pod
@objc dynamic var query: String?
var query: String?
/// Retrieves the property that is used to sort on
@objc dynamic var sortProperty: String?
var sortProperty: String?
/// Retrieves whether the sort direction
/// - false sort descending
/// - true sort ascending
let sortAscending = RealmOptional<Bool>()
var sortAscending: Bool? = nil
/// Retrieves the number of items per page
let pageCount = RealmOptional<Int>() // Todo move to ResultSet
var pageCount: Int? = nil
let pageIndex = RealmOptional<Int>() // Todo move to ResultSet
var pageIndex: Int? = nil
/// Returns a string representation of the data in QueryOptions that is unique for that data
/// Each QueryOptions object with the same data will return the same uniqueString
var uniqueString: String {
......@@ -42,69 +41,40 @@ public class Datasource: SchemaItem, UniqueString {
result.append((query ?? "").sha256())
result.append(sortProperty ?? "")
let sortAsc = sortAscending.value ?? true
let sortAsc = sortAscending ?? true
result.append(String(sortAsc))
return result.joined(separator: ":")
}
convenience init(query: String) {
self.init()
init(query: String?, sortProperty: String? = nil, sortAscending: Bool? = nil) {
self.query = query
}
required init() {
super.init()
}
required init(from decoder: Decoder) throws {
fatalError("init(from:) has not been implemented")
}
public class func fromCVUDefinition(_ def: CVUParsedDatasourceDefinition,
_ viewArguments: ViewArguments? = nil) throws -> Datasource {
func getValue<T>(_ name: String) throws -> T? {
if let expr = def[name] as? Expression {
do {
let x = try expr.execForReturnType(T.self, args: viewArguments)
return x
} catch {
debugHistory.warn("\(error)")
return nil
}
}
return def[name] as? T
}
return try Cache.createItem(Datasource.self, values: [
"selector": def.selector ?? "[datasource]",
"query": try getValue("query") ?? "",
"sortProperty": try getValue("sortProperty") ?? "",
"sortAscending": try getValue("sortAscending") ?? true,
])
self.sortProperty = sortProperty
self.sortAscending = sortAscending
}
}
public class CascadingDatasource: Cascadable, UniqueString {
public class CascadingDatasource: Cascadable, UniqueString, Subscriptable {
/// Retrieves the query which is used to load data from the pod
var query: String? {
datasource.query ?? cascadeProperty("query")
get { cascadeProperty("query") }
set (value) { setState("query", value) }
}
/// Retrieves the property that is used to sort on
var sortProperty: String? {
datasource.sortProperty ?? cascadeProperty("sortProperty")
get { cascadeProperty("sortProperty") }
set (value) { setState("sortProperty", value) }
}
/// Retrieves whether the sort direction
/// false sort descending
/// true sort ascending
var sortAscending: Bool? {
datasource.sortAscending.value ?? cascadeProperty("sortAscending")
get { cascadeProperty("sortAscending") }
set (value) { setState("sortAscending", value) }
}
let datasource: Datasource
/// Returns a string representation of the data in QueryOptions that is unique for that data
/// Each QueryOptions object with the same data will return the same uniqueString
var uniqueString: String {
......@@ -120,19 +90,19 @@ public class CascadingDatasource: Cascadable, UniqueString {
}
func flattened() -> Datasource {
Datasource(value: [
"query": query,
"sortProperty": sortProperty,
"sortAscending": sortAscending,
])
}
init(_ cascadeStack: [CVUParsedDatasourceDefinition],
_ viewArguments: ViewArguments? = nil,
_ datasource: Datasource) {
self.datasource = datasource
super.init(cascadeStack, viewArguments)
Datasource(
query: query,
sortProperty: sortProperty,
sortAscending: sortAscending
)
}
//
// init(_ cascadeStack: [CVUParsedDatasourceDefinition],
// _ viewArguments: ViewArguments? = nil,
// _ datasource: Datasource) {
// self.datasource = datasource
// super.init(cascadeStack, viewArguments)
// }
subscript(propName: String) -> Any? {
get {
......@@ -145,9 +115,9 @@ public class CascadingDatasource: Cascadable, UniqueString {
}
set(value) {
switch propName {
case "query": return datasource.query = value as? String ?? ""
case "sortProperty": return datasource.sortProperty = value as? String ?? ""
case "sortAscending": return datasource.sortAscending.value = value as? Bool ?? true
case "query": query = value as? String
case "sortProperty": sortProperty = value as? String
case "sortAscending": sortAscending = value as? Bool
default: return
}
}
......
......@@ -25,8 +25,9 @@ public class PodAPI {
private func http(_ method: HTTPMethod = .GET, path: String = "", body: Data? = nil,
_ callback: @escaping (_ error: Error?, _ data: Data?) -> Void) {
let settings = Settings()
let session = URLSession(configuration: .default, delegate: nil, delegateQueue: .main)
let podhost = Settings.get("user/pod/host") ?? ""
let podhost = settings.get("user/pod/host") ?? ""
guard var baseUrl = URL(string: podhost) else {
let message = "Invalid pod host set in settings: \(podhost)"
debugHistory.error(message)
......@@ -41,8 +42,8 @@ public class PodAPI {
// TODO: when the backend sends the correct caching headers
// this can be changed: .reloadIgnoringCacheData
guard let username: String = Settings.get("user/pod/username"),
let password: String = Settings.get("user/pod/password") else {
guard let username: String = settings.get("user/pod/username"),
let password: String = settings.get("user/pod/password") else {
// TODO: User error handling
print("ERROR: Could not find login credentials, so could not authenticate to pod")
return
......@@ -118,7 +119,7 @@ public class PodAPI {
private let MAXDEPTH = 2
private func recursiveSearch(_ item: SchemaItem, removeUID _: Bool = false) throws -> [String: Any] {
if item.syncState?.actionNeeded == nil { throw "No action required" }
if item._action == nil { throw "No action required" }
var createItems = [[String: Any]]()
var updateItems = [[String: Any]]()
......@@ -128,7 +129,7 @@ public class PodAPI {
var deleteEdges = [[String: Any]]()
func recurEdge(_ edge: Edge, forceInclude: Bool = false) throws {
let a = edge.syncState?.actionNeeded
let a = edge._action
if a == nil, !forceInclude { return }
guard let action = a else { return }
......@@ -136,7 +137,8 @@ public class PodAPI {
let properties = item.objectSchema.properties
for prop in properties {
if prop.name == "syncState" || prop.name == "deleted"
if prop.name == "_updated" || prop.name == "_action" || prop.name == "_partial"
|| prop.name == "deleted" || prop.name == "_changedInSession"
|| prop.name == "targetItemType" || prop.name == "targetItemID"
|| prop.name == "sourceItemType" || prop.name == "sourceItemID" {
// Ignore
......@@ -163,24 +165,25 @@ public class PodAPI {
}
func recur(_ item: SchemaItem, forceInclude: Bool = false) throws {
let a = item.syncState?.actionNeeded
let a = item._action
if a == nil, !forceInclude { return }
guard let action = a else { return }
let updatedFields = item.syncState?.updatedFields
let updatedFields = item._updated
var result: [String: Any] = [
"_type": item.genericType,
]
let properties = item.objectSchema.properties
for prop in properties {
if prop.name == "syncState" || prop.name == "deleted" {
if prop.name == "_updated" || prop.name == "_action" || prop.name == "_partial"
|| prop.name == "deleted" || prop.name == "_changedInSession" {
// Ignore
} else if prop.name == "allEdges" {
for edge in item.allEdges {
try recurEdge(edge, forceInclude: action == "create")
}
} else if updatedFields == nil || updatedFields?.contains(prop.name) ?? false {
} else if updatedFields.contains(prop.name) {
if prop.type == .object {
throw "Unexpected object schema"
} else {
......@@ -221,7 +224,7 @@ public class PodAPI {
}
func simplify(_ item: SchemaItem, create: Bool = false) throws -> [String: Any] {
let updatedFields = item.syncState?.updatedFields
let updatedFields = item._updated
var result: [String: Any] = [
"_type": item.genericType,
"uid": item.uid
......@@ -229,11 +232,13 @@ public class PodAPI {
// print("\(item.genericType) \(item.uid.value ?? 0)")
let properties = item.objectSchema.properties
let exclude = ["syncState", "deleted", "allEdges", "uid"]
let exclude = [
"_updated", "_action", "_partial", "_changedInSession", "deleted", "allEdges", "uid"
]
for prop in properties {
if exclude.contains(prop.name) {
// Ignore
} else if create || updatedFields == nil || updatedFields?.contains(prop.name) ?? false {
} else if create || updatedFields.contains(prop.name) {
if prop.type == .object {
debugHistory.warn("Unexpected object schema")
} else if prop.type == .date, let date = item[prop.name] as? Date {
......@@ -255,7 +260,10 @@ public class PodAPI {
var result = [String: Any]()
let properties = edge.objectSchema.properties
let exclude = ["version", "syncState", "deleted", "targetItemType", "targetItemID", "sourceItemType", "sourceItemID"]
let exclude = [
"version", "deleted", "targetItemType", "targetItemID", "sourceItemType",
"sourceItemID", "_updated", "_action", "_partial", "_changedInSession"
]
for prop in properties {
if exclude.contains(prop.name) {
// Ignore
......@@ -477,7 +485,7 @@ public class PodAPI {
/// - Parameters:
/// - memriID: The memriID of the data item to remove
/// - callback: Function that is called when the task is completed either with a result, or an error
public func runImporterRun(_ uid: Int,
public func runImporter(_ uid: Int,
_ callback: @escaping (_ error: Error?, _ success: Bool) -> Void) {
http(.PUT, path: "import/\(uid)") { error, _ in
callback(error, error == nil)
......@@ -488,7 +496,7 @@ public class PodAPI {
/// - Parameters:
/// - memriID: The memriID of the data item to remove
/// - callback: Function that is called when the task is completed either with a result, or an error
public func runIndexerRun(_ uid: Int,
public func runIndexer(_ uid: Int,
_ callback: @escaping (_ error: Error?, _ success: Bool) -> Void) {
http(.POST, path: "run_service/indexers/\(uid)") { error, _ in
callback(error, error == nil)
......
......@@ -20,14 +20,23 @@ import SwiftUI
// TODO: Remove this and find a solution for Edges
var globalCache: Cache?
public class MemriContext: ObservableObject {
protocol Subscriptable {
subscript(propName: String) -> Any? { get set }
}
public class MemriContext: ObservableObject, Subscriptable {
public var name: String = ""
@Published public var sessions: Sessions
/// The current session that is active in the application
@Published public var currentSession: Session?
@Published public var cascadingView: CascadingView?
public var currentSession: Session? {
sessions.currentSession
}
@Published public var sessions: Sessions?
public var currentView: CascadableView? {
sessions.currentSession?.currentView
}
public var views: Views
......@@ -41,15 +50,13 @@ public class MemriContext: ObservableObject {
public var cache: Cache
public var realm: Realm
public var navigation: MainNavigation
public var renderers: Renderers
public var items: [Item] {
get {
cascadingView?.resultSet.items ?? []
currentView?.resultSet.items ?? []
}
set {
// Do nothing
......@@ -59,7 +66,7 @@ public class MemriContext: ObservableObject {
public var item: Item? {
get {
cascadingView?.resultSet.singletonItem
currentView?.resultSet.singletonItem
}
set {
// Do nothing
......@@ -77,177 +84,45 @@ public class MemriContext: ObservableObject {
closeStack = Array(closeStack.prefix(upTo: lastVisibleIndex))
}
}
private var scheduled: Bool = false
private var scheduledComputeView: Bool = false
private var uiUpdateSubject = PassthroughSubject<Void, Never>()
private var uiUpdateCancellable: AnyCancellable?
private var cascadableViewUpdateSubject = PassthroughSubject<Void, Never>()
private var cascadableViewUpdateCancellable: AnyCancellable?
func scheduleUIUpdate(immediate: Bool = false, _ check: ((_ context: MemriContext) -> Bool)? = nil) { // Update UI
let outcome = {
// Reset scheduled
self.scheduled = false
// Update UI
self.objectWillChange.send()
}
if immediate {
#warning("@Toby how can we prevent the uiUpdateSubject from firing immediate after this?")
// Do this straight away, usually for the sake of correct animation
outcome()
DispatchQueue.main.async {
// Update UI
self.objectWillChange.send()
}
return
}
if let check = check {
guard check(self) else { return }
}
// Don't schedule when we are already scheduled
guard !scheduled else { return }
// Prevent multiple calls to the dispatch queue
scheduled = true
// Schedule update
DispatchQueue.main.async {
outcome()
}
uiUpdateSubject.send()
}
func scheduleCascadingViewUpdate(immediate: Bool = false) {
let outcome = {
// Reset scheduled
self.scheduledComputeView = false
// Update UI
do { try self.updateCascadingView() }
catch {
// TODO: User error handling
// TODO: Error Handling
debugHistory.error("Could not update CascadingView: \(error)")
}
}
func scheduleCascadableViewUpdate(immediate: Bool = false) {
if immediate {
// Do this straight away, usually for the sake of correct animation
outcome()
do { try self.currentSession?.setCurrentView() }
catch {
// TODO: User error handling
// TODO: Error Handling
debugHistory.error("Could not update CascadableView: \(error)")
}
return
}
// Don't schedule when we are already scheduled
if !scheduledComputeView {
// Prevent multiple calls to the dispatch queue
scheduledComputeView = true
// Schedule update
DispatchQueue.main.async {
outcome()
}
}
}
public func updateCascadingView() throws {
try maybeLogUpdate()
// Fetch datasource if not yet parsed yet
guard let currentView = sessions?.currentView else {
throw "Exception: currentView is not set"
}
if currentView.datasource == nil {
if let parsedDef = try views.parseDefinition(currentView.viewDefinition) {
if let ds = parsedDef["datasourceDefinition"] as? CVUParsedDatasourceDefinition {
// TODO: this is at the wrong moment. Should be done after cascading
currentView.set("datasource",
try Datasource.fromCVUDefinition(ds, currentView.viewArguments))
} else {
throw "Exception: Missing datasource in session view"
}
} else {
throw "Exception: Unable to parse view definition"
}
}
guard let datasource = currentView.datasource else {
throw "Exception: Missing datasource in session view"
}
// Fetch the resultset associated with the current view
let resultSet = cache.getResultSet(datasource)
// If we can guess the type of the result based on the query, let's compute the view
if resultSet.determinedType != nil {
if self is RootContext { // if type(of: self) == RootMain.self {
debugHistory.info("Computing view "
+ (currentView.name ?? currentView.viewDefinition?.selector ?? ""))
}
do {
// Calculate cascaded view
let cascadingView = try views.createCascadingView() // TODO: handle errors better
// Update current session
currentSession = sessions?.currentSession // TODO: filter to a single property
// Set the newly cascading view
self.cascadingView = cascadingView
// Load data in the resultset of the computed view
try self.cascadingView?.resultSet.load { error in
if let error = error {
// TODO: Refactor: Log warning to user
print("Error: could not load result: \(error)")
} else {
try maybeLogRead()
// Update the UI
scheduleUIUpdate()
}
}
} catch {
// TODO: Error handling
// TODO: User Error handling
debugHistory.error("\(error)")
}
// Update the UI
scheduleUIUpdate()
}
// Otherwise let's execute the query first
else {
// Updating the data in the resultset of the session view
try resultSet.load { error in
// Only update when data was retrieved successfully
if let error = error {
// TODO: Error handling
print("Error: could not load result: \(error)")
} else {
// Update the current view based on the new info
scheduleUIUpdate() // TODO: shouldn't this be setCurrentView??
}
}
}
}
private func maybeLogRead() throws {
if let item = cascadingView?.resultSet.singletonItem {
let auditItem = try Cache.createItem(AuditItem.self, values: ["action": "read"])
_ = try item.link(auditItem, type: "changelog")
}
}
private func maybeLogUpdate() throws {
if cascadingView?.context == nil { return }
let syncState = cascadingView?.resultSet.singletonItem?.syncState
if let syncState = syncState, syncState.changedInThisSession {
let fields = syncState.updatedFields
// TODO: serialize
if let item = cascadingView?.resultSet.singletonItem {
let auditItem = try Cache.createItem(AuditItem.self, values: [
"contents": try serialize(AnyCodable(Array(fields))),
"action": "update",
])
_ = try item.link(auditItem, type: "changelog")
realmWriteIfAvailable(realm) { syncState.changedInThisSession = false }
} else {
print("Could not log update, no Item found")
}
}
} else {
cascadableViewUpdateSubject.send()
}
}
public func getPropertyValue(_ name: String) -> Any {
......@@ -323,17 +198,20 @@ public class MemriContext: ObservableObject {
get { self["showNavigation"] as? Bool == true }
set(value) { self["showNavigation"] = value }
}
public func setSelection(_ selection: [Item]) {
currentView?.userState.set("selection", selection)
scheduleUIUpdate()
}
init(
name: String,
podAPI: PodAPI,
cache: Cache,
realm: Realm,
settings: Settings,
installer: Installer,
sessions: Sessions? = nil,
sessions: Sessions,
views: Views,
cascadingView: CascadingView? = nil,
navigation: MainNavigation,
renderers: Renderers,
indexerAPI: IndexerAPI
......@@ -341,27 +219,41 @@ public class MemriContext: ObservableObject {
self.name = name
self.podAPI = podAPI
self.cache = cache
self.realm = realm
self.settings = settings
self.installer = installer
self.sessions = sessions
self.views = views
self.cascadingView = cascadingView
self.navigation = navigation
self.renderers = renderers
self.indexerAPI = indexerAPI
// TODO: FIX
self.cascadingView?.context = self
self.currentView?.context = self
self.indexerAPI.context = self
// Setup update publishers
self.uiUpdateCancellable = uiUpdateSubject
.throttle(for: .milliseconds(300), scheduler: RunLoop.main, latest: true)
.receive(on: DispatchQueue.main)
.sink { [weak self] in
self?.objectWillChange.send()
}
// Setup update publishers
self.cascadableViewUpdateCancellable = cascadableViewUpdateSubject
.throttle(for: .milliseconds(500), scheduler: RunLoop.main, latest: true)
.receive(on: DispatchQueue.main)
.sink { [weak self] in
try? self?.currentSession?.setCurrentView()
}
}
}
public class SubContext: MemriContext {
let parent: MemriContext
init(name: String, _ context: MemriContext, _ session: Session) throws {
let views = Views(context.realm)
init(name: String, _ context: MemriContext, _ state: CVUStateDefinition?) throws {
let views = Views()
parent = context
......@@ -369,12 +261,10 @@ public class SubContext: MemriContext {
name: name,
podAPI: context.podAPI,
cache: context.cache,
realm: context.realm,
settings: context.settings,
installer: context.installer,
sessions: try Cache.createItem(Sessions.self),
sessions: try Sessions(state),
views: views,
// cascadingView: context.cascadingView,
navigation: context.navigation,
renderers: context.renderers,
indexerAPI: context.indexerAPI
......@@ -383,8 +273,8 @@ public class SubContext: MemriContext {
closeStack = context.closeStack
views.context = self
sessions?.setCurrentSession(session)
try sessions.load(self)
}
}
......@@ -401,48 +291,43 @@ public class RootContext: MemriContext {
init(name: String, key: String) throws {
let podAPI = PodAPI(key)
let cache = try Cache(podAPI)
let realm = cache.realm
let views = Views()
globalCache = cache // TODO: remove this and fix edges
MapHelper.shared.realm = realm // TODO: How to access realm in a better way?
let sessionState = try Cache.createItem(
CVUStateDefinition.self,
values: ["uid": try Cache.getDeviceID()]
)
super.init(
name: name,
podAPI: podAPI,
cache: cache,
realm: realm,
settings: Settings(realm),
installer: Installer(realm),
sessions: try Cache.createItem(Sessions.self, values: ["uid": try Cache.getDeviceID()]),
views: Views(realm),
navigation: MainNavigation(realm),
settings: Settings(),
installer: Installer(),
sessions: try Sessions(sessionState),
views: views,
navigation: MainNavigation(),
renderers: Renderers(),
indexerAPI: IndexerAPI()
)
cascadingView?.context = self
let takeScreenShot = { () -> Void in
// Make sure to record a screenshot prior to session switching
self.currentSession?.takeScreenShot() // Optimize by only doing this when a property in session/view/dataitem has changed
}
currentView?.context = self
// TODO: Refactor: This is a mess. Create a nice API, possible using property wrappers
// Optimize by only doing this when a property in session/view/dataitem has changed
aliases = [
"showSessionSwitcher": Alias(key: "device/gui/showSessionSwitcher", type: "bool", on: takeScreenShot),
"showNavigation": Alias(key: "device/gui/showNavigation", type: "bool", on: takeScreenShot),
"showSessionSwitcher": Alias(key: "device/gui/showSessionSwitcher", type: "bool", on: { self.currentSession?.takeScreenShot(immediate:true) }),
"showNavigation": Alias(key: "device/gui/showNavigation", type: "bool", on: { self.currentSession?.takeScreenShot() }),
]
cache.scheduleUIUpdate = { [weak self] in self?.scheduleUIUpdate($0) }
navigation.scheduleUIUpdate = { [weak self] in self?.scheduleUIUpdate($0) }
// Make settings global so it can be reached everywhere
globalSettings = settings
}
public func createSubContext(_ session: Session) throws -> MemriContext {
let subContext = try SubContext(name: "Proxy", self, session)
public func createSubContext(_ state: CVUStateDefinition? = nil) throws -> MemriContext {
let subContext = try SubContext(name: "Proxy", self, state)
subContexts.append(subContext)
return subContext
}
......@@ -458,16 +343,17 @@ public class RootContext: MemriContext {
// Load views configuration
try self.views.load(self) {
// Load session
try sessions.load(self)
// Update view when sessions changes
self.cancellable = self.sessions?.objectWillChange.sink { _ in
self.cancellable = self.sessions.objectWillChange.sink { _ in
self.scheduleUIUpdate()
}
self.currentSession?.access()
self.currentSession?.currentView?.access()
// Load current view
try self.updateCascadingView()
try self.currentSession?.setCurrentView()
callback?()
}
......
......@@ -3,9 +3,9 @@
"name": "filter-starred",
"fromTemplate": "{view}",
"datasource": {
"query": "{cascadingView.datasource.query} AND starred = true"
"query": "{cascadableView.datasource.query} AND starred = true"
},
"title": "Starred {cascadingView.title}"
"title": "Starred {cascadableView.title}"
},
{
......
.filter-starred {
inherit: {{view}}
/* title: "Starred {view.title}"*/
title: "Starred"
title: "Starred {view.title}"
[datasource = pod] {
query: "{view.datasource.query} AND starred = true"
......
......@@ -3,9 +3,9 @@
"name": "filter-starred",
"fromTemplate": "{view}",
"datasource": {
"query": "{cascadingView.datasource.query} AND starred = true"
"query": "{cascadableView.datasource.query} AND starred = true"
},
"title": "Starred {cascadingView.title}"
"title": "Starred {cascadableView.title}"
},
{
......
[renderer = calendar] {
dateTime: {{.dateModified}}
}
[renderer = calendar.timeline] {
press: openView
}
......@@ -4,7 +4,7 @@
{
section: other
fields: *
exclude: version deleted syncState uid externalID allEdges
exclude: version deleted _updated _action _partial _changedInSession uid externalID allEdges
}
{
section: dates
......
[renderer = messages] {
press: openView /* open the message when pressing it */
}
......@@ -22,7 +22,7 @@ Address[] {
}
Spacer
Map {
addressKey: self
address: {{.}}
maxWidth: 150
minHeight: 150
maxHeight: 150
......@@ -34,7 +34,8 @@ Address[] {
}
[renderer = map] {
addressKey: self
address: {{.self}}
label: "{.computedTitle()}"
}
[renderer = generalEditor] {
......@@ -51,7 +52,7 @@ Address[] {
}
Spacer
Map {
addressKey: self
address: {{.}}
maxWidth: 150
minHeight: 150
maxHeight: 150
......@@ -106,7 +107,7 @@ Address {
showTitle: false
Map {
addressKey: self
address: {{.}}
minHeight: 150
maxHeight: 150
}
......
......@@ -35,41 +35,51 @@ Importer {
showContextPane
]
contextButtons: star schedule
navigateItems: [
openView {
title: "Timeline of this importer"
arguments {
view: {
defaultRenderer: timeline
[datasource = pod] {
query: "AuditItem appliesTo:{.id}"
sortProperty: dateCreated
sortAscending: true
contextPane {
buttons: star schedule
actions: [
showOverlay { title: "Share with..." }
addToPanel { title: "Add to list..." }
duplicate { title: "Duplicate Note" }
]
navigate: [
openView {
title: "Timeline of this importer"
arguments {
view: {
defaultRenderer: timeline
[datasource = pod] {
query: "AuditItem appliesTo:{.id}"
sortProperty: dateCreated
sortAscending: true
}
[renderer = timeline] {
timeProperty: dateCreated
}
}
[renderer = timeline] {
timeProperty: dateCreated
}
}
openViewByName {
title: "Starred importers"
arguments: {
name: "filter-starred"
viewArguments: {
include: "all-notes"
}
}
}
}
openViewByName {
title: "Starred importers"
arguments: {
name: "filter-starred"
viewArguments: {
include: "all-notes"
openSessionByName {
title: "All importers"
arguments: {
name: "all-importers"
}
}
}
openViewByName {
title: "All importers"
name: "all-importers"
}
]
]
}
}
Importer[] {
......
......@@ -35,41 +35,51 @@ ImporterRun {
showContextPane
]
contextButtons: star schedule
navigateItems: [
openView {
title: "Timeline of this importer run"
arguments {
view: {
defaultRenderer: timeline
[datasource = pod] {
query: "AuditItem appliesTo:{.id}"
sortProperty: dateCreated
sortAscending: true
contextPane {
buttons: star schedule
actions: [
showOverlay { title: "Share with..." }
addToPanel { title: "Add to list..." }
duplicate { title: "Duplicate Note" }
]
navigate: [
openView {
title: "Timeline of this importer run"
arguments {
view: {
defaultRenderer: timeline
[datasource = pod] {
query: "AuditItem appliesTo:{.id}"
sortProperty: dateCreated
sortAscending: true
}
[renderer = timeline] {
timeProperty: dateCreated
}
}
[renderer = timeline] {
timeProperty: dateCreated
}
}
openViewByName {
title: "Starred importer runs"
arguments: {
name: "filter-starred"
viewArguments: {
include: "all-notes"
}
}
}
}
openViewByName {
title: "Starred importer runs"
arguments: {
name: "filter-starred"
viewArguments: {
include: "all-notes"
openSessionByName {
title: "All importer runs"
arguments: {
name: "all-importer-instances"
}
}
}
openViewByName {
title: "All importer runs"
name: "all-importer-instances"
}
]
]
}
[renderer = generalEditor] {
layout: [
......@@ -123,7 +133,7 @@ ImporterRun {
Button {
margin: 5 5 5 35
press: runImporterRun {
press: runImporter {
arguments {
importerRun: {{.}}
}
......
......@@ -35,41 +35,51 @@ Indexer {
showContextPane
]
contextButtons: star schedule
navigateItems: [
openView {
title: "Timeline of this indexer"
arguments {
view: {
defaultRenderer: timeline
[datasource = pod] {
query: "AuditItem appliesTo:{.id}"
sortProperty: dateCreated
sortAscending: true
contextPane {
buttons: star schedule
actions: [
showOverlay { title: "Share with..." }
addToPanel { title: "Add to list..." }
duplicate { title: "Duplicate Note" }
]
navigateItems: [
openView {
title: "Timeline of this indexer"
arguments {
view: {
defaultRenderer: timeline
[datasource = pod] {
query: "AuditItem appliesTo:{.id}"
sortProperty: dateCreated
sortAscending: true
}
[renderer = timeline] {
timeProperty: dateCreated
}
}
[renderer = timeline] {
timeProperty: dateCreated
}
}
openViewByName {
title: "Starred indexer"
arguments: {
name: "filter-starred"
viewArguments: {
include: "all-indexers"
}
}
}
}
openViewByName {
title: "Starred indexer"
arguments: {
name: "filter-starred"
viewArguments: {
include: "all-indexers"
openSessionByName {
title: "All indexers"
arguments: {
name: "all-indexers"
}
}
}
openViewByName {
title: "All indexers"
name: "all-indexers"
}
]
]
}
}
Indexer[] {
......
......@@ -35,41 +35,51 @@ IndexerRun {
showContextPane
]
contextButtons: star schedule
navigateItems: [
openView {
title: "Timeline of this indexer run"
arguments {
view: {
defaultRenderer: timeline
[datasource = pod] {
query: "AuditItem appliesTo:{.id}"
sortProperty: dateCreated
sortAscending: true
contextPane {
buttons: star schedule
actions: [
showOverlay { title: "Share with..." }
addToPanel { title: "Add to list..." }
duplicate { title: "Duplicate Note" }
]
navigateItems: [
openView {
title: "Timeline of this indexer run"
arguments {
view: {
defaultRenderer: timeline
[datasource = pod] {
query: "AuditItem appliesTo:{.id}"
sortProperty: dateCreated
sortAscending: true
}
[renderer = timeline] {
timeProperty: dateCreated
}
}
[renderer = timeline] {
timeProperty: dateCreated
}
}
openViewByName {
title: "Starred indexer runs"
arguments: {
name: "filter-starred"
viewArguments: {
include: "all-notes"
}
}
}
}
openViewByName {
title: "Starred indexer runs"
arguments: {
name: "filter-starred"
viewArguments: {
include: "all-notes"
openSessionByName {
title: "All indexer runs"
arguments: {
name: "all-indexer-instances"
}
}
}
openViewByName {
title: "All indexer runs"
name: "all-indexer-instances"
}
]
]
}
[renderer = generalEditor] {
layout: [
......@@ -145,7 +155,7 @@ IndexerRun {
Button {
margin: 0 5 10 35
press: runIndexerRun {
press: runIndexer {
arguments {
indexerRun: {{.}}
}
......
......@@ -35,47 +35,51 @@ Note {
showContextPane
]
contextButtons: star schedule
actionItems: [
showOverlay { title: "Share with..." }
addToPanel { title: "Add to list..." }
duplicate { title: "Duplicate Note" }
]
navigateItems: [
openView {
title: "Timeline of this note"
arguments {
view: {
defaultRenderer: timeline
[datasource = pod] {
query: "AuditItem appliesTo:{.id}"
sortProperty: dateCreated
sortAscending: true
contextPane {
buttons: star schedule
actions: [
showOverlay { title: "Share with..." }
addToPanel { title: "Add to list..." }
duplicate { title: "Duplicate Note" }
]
navigate: [
openView {
title: "Timeline of this note"
arguments {
view: {
defaultRenderer: timeline
[datasource = pod] {
query: "AuditItem appliesTo:{.id}"
sortProperty: dateCreated
sortAscending: true
}
[renderer = timeline] {
timeProperty: dateCreated
}
}
[renderer = timeline] {
timeProperty: dateCreated
}
}
openViewByName {
title: "Starred notes"
arguments: {
name: "filter-starred"
viewArguments: {
include: "all-notes"
}
}
}
}
openViewByName {
title: "Starred notes"
arguments: {
name: "filter-starred"
viewArguments: {
include: "all-notes"
openSessionByName {
title: "All notes"
arguments: {
name: "all-notes"
}
}
}
openViewByName {
title: "All notes"
name: "all-notes"
}
]
]
}
[renderer = custom] {
RichTextfield {
......@@ -196,4 +200,25 @@ Note[] {
}
}
}
[renderer = calendar.timeline] {
dateTime: {{.dateModified}}
TimelineItem {
icon: "square.and.pencil"
title: {{.title or "Untitled Note"}}
text: {{.content.plainString}}
}
}
[renderer = messages] {
sort: dateModified
MessageBubble {
sender: {{.title}}
content: {{.content.plainString}}
dateTime: {{.dateModified}}
isOutgoing: {{.title = "To read list"}} /* This is just a demo until we have a Message type in the schema... */
}
}
}
......@@ -52,47 +52,49 @@
showContextPane
]
contextButtons: star schedule
actionItems: [
showOverlay { title: "{$sharewith}" }
addToPanel { title: "{$addtolist}" }
duplicate { title: "{$duplicate} {type}" }
]
navigateItems: [
openView {
title: "{$timelineof} {type.lowercased()}"
arguments: {
view: {
defaultRenderer: timeline
[datasource = pod] {
query: "AuditItem appliesTo:{.id}"
sortProperty: dateCreated
sortAscending: true
}
[renderer = timeline] {
timeProperty: dateCreated
contextPane {
buttons: star schedule
actions: [
showOverlay { title: "{$sharewith}" }
addToPanel { title: "{$addtolist}" }
duplicate { title: "{$duplicate} {type}" }
]
navigate: [
openView {
title: "{$timelineof} {type.lowercased()}"
arguments: {
view: {
defaultRenderer: timeline
[datasource = pod] {
query: "AuditItem appliesTo:{.id}"
sortProperty: dateCreated
sortAscending: true
}
[renderer = timeline] {
timeProperty: dateCreated
}
}
}
}
}
openViewByName {
title: "{$starred} {type.plural()}"
arguments: {
name: "filter-starred"
viewArguments: {
include: "all-{type}"
openViewByName {
title: "{$starred} {type.plural()}"
arguments: {
name: "filter-starred"
viewArguments: {
include: "all-{type}"
}
}
}
}
openViewByName {
title: "{$all} {type.lowercased().plural()}"
name: "all-{type}"
}
]
openViewByName {
title: "{$all} {type.lowercased().plural()}"
name: "all-{type}"
}
]
}
}
Person {
......@@ -127,47 +129,51 @@ Person {
showContextPane
]
contextButtons: star schedule
actionItems: [
showOverlay { title: "{$sharewith}" }
addToPanel { title: "{$addtolist}" }
duplicate { title: "{$duplicate} person" }
]
navigateItems: [
openView {
title: "{$timelineof} person"
arguments: {
view: {
defaultRenderer: timeline
[datasource = pod] {
query: "AuditItem appliesTo:{.id}"
sortProperty: dateCreated
sortAscending: true
contextPane {
buttons: star schedule
action: [
showOverlay { title: "{$sharewith}" }
addToPanel { title: "{$addtolist}" }
duplicate { title: "{$duplicate} person" }
]
navigate: [
openView {
title: "{$timelineof} person"
arguments: {
view: {
defaultRenderer: timeline
[datasource = pod] {
query: "AuditItem appliesTo:{.id}"
sortProperty: dateCreated
sortAscending: true
}
[renderer = timeline] {
timeProperty: dateCreated
}
}
[renderer = timeline] {
timeProperty: dateCreated
}
}
openViewByName {
title: "{$starred} persons"
arguments: {
name: "filter-starred"
viewArguments: {
include: "all-person"
}
}
}
}
openViewByName {
title: "{$starred} persons"
arguments: {
name: "filter-starred"
viewArguments: {
include: "all-person"
openSessionByName {
title: "{$all} persons"
arguments: {
name: "all-person"
}
}
}
openViewByName {
title: "{$all} persons"
name: "all-person"
}
]
]
}
/* end include */
......@@ -279,8 +285,8 @@ Person {
}
}
arguments: {
toolbar: false
searchbar: false
showToolbar: false
showSearchbar: false
readOnly: true
}
}
......@@ -357,7 +363,7 @@ Person[] {
defaultRenderer: thumbnail
emptyResultText: "There are no people here yet"
[datasource = pod] {
[datasource = pod] {
query: "Person"
sortProperty: dateModified
}
......@@ -440,15 +446,15 @@ Person[] {
}
[renderer = map] {
addressKey: address
labelKey: firstName
address: {{.address[]}}
label: "{.firstName}"
}
[renderer = chart] {
chartTitle: "People by height" /* Use this to override the auto-generated chart title */
label: "{.firstName}" /* used for bar chart (also provides label on line chart) */
yAxis: "{.height}"
yAxis: {{.height}}
hideGridlines: true
yAxisStartAtZero: true /* use this to force y-Axis to start at zero (vs fitting data)*/
......@@ -461,8 +467,8 @@ Person[] {
chartTitle: "Height vs. Age"
chartSubtitle: "A demo of dynamically graphing your data."
xAxis: "{.age}" /* Feeling old? Try "{.age - 10}" to give everyone back a decade ;) */
yAxis: "{.height}"
xAxis: {{.age()}} /* Feeling old? Try {{.age() - 10}} to give everyone back a decade ;) */
yAxis: {{.height}}
label: "{.firstName}" /* The label to show over the point */
/* line chart ignores sort (automatically sorted by x-axis) */
......
......@@ -56,4 +56,13 @@ Photo[]: {
background: #AAA
}
}
[renderer = calendar] {
dateTime: {{.dateCreated}}
TimelineItem {
icon: "camera"
title: "Photo"
}
}
}
This diff is collapsed.
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment