make fileders load just-in-time
s-ol
3 years ago
8 | 8 | |
9 | 9 | require 'mmm' |
10 | 10 | require 'lfs' |
11 | import Fileder, Key from require 'mmm.mmmfs.fileder' | |
11 | import Key from require 'mmm.mmmfs.fileder' | |
12 | 12 | import SQLStore from require 'mmm.mmmfs.stores.sql' |
13 | 13 | |
14 | 14 | -- usage: |
7 | 7 | add '?/init.server' |
8 | 8 | |
9 | 9 | require 'mmm' |
10 | import load_tree from require 'mmm.mmmfs.fileder' | |
10 | import Fileder from require 'mmm.mmmfs.fileder' | |
11 | 11 | import get_store from require 'mmm.mmmfs.stores' |
12 | 12 | import render from require 'mmm.mmmfs.layout' |
13 | 13 | |
19 | 19 | STATIC = true |
20 | 20 | |
21 | 21 | store = get_store store |
22 | tree = load_tree store | |
22 | tree = Fileder store | |
23 | 23 | tree = tree\walk startpath if startpath |
24 | 24 | |
25 | 25 | for fileder in coroutine.wrap tree\iterate |
8 | 8 | |
9 | 9 | require 'mmm' |
10 | 10 | |
11 | import Key, dir_base, load_tree from require 'mmm.mmmfs.fileder' | |
11 | import dir_base, Key, Fileder from require 'mmm.mmmfs.fileder' | |
12 | 12 | import convert from require 'mmm.mmmfs.conversion' |
13 | 13 | import get_store from require 'mmm.mmmfs.stores' |
14 | 14 | import render from require 'mmm.mmmfs.layout' |
37 | 37 | assert @server\loop! |
38 | 38 | |
39 | 39 | handle: (method, path, facet) => |
40 | fileder = load_tree @store, path -- @tree\walk path | |
40 | fileder = Fileder @store, path | |
41 | 41 | |
42 | 42 | if not fileder |
43 | 43 | -- fileder not found |
49 | 49 | when '?interactive' |
50 | 50 | export BROWSER |
51 | 51 | |
52 | root = load_tree @store | |
52 | root = Fileder @store | |
53 | 53 | BROWSER = Browser root, path |
54 | 54 | render BROWSER\todom!, fileder, noview: true, scripts: " |
55 | 55 | <script type=\"application/lua\"> |
214 | 214 | with select :onchange |
215 | 215 | \append option '(none)', value: '', disabled: true, selected: not value |
216 | 216 | if fileder |
217 | for key, _ in pairs fileder.facets | |
217 | for i, key in ipairs fileder\get_facets! | |
218 | 218 | value = key\tostring! |
219 | 219 | \append option value, :value, selected: value == current |
220 | 220 | @inspect\map (enabled) -> |
0 | 0 | require = relative ..., 1 |
1 | 1 | import get_conversions, apply_conversions from require '.conversion' |
2 | ||
3 | -- split filename into dirname + basename | |
4 | dir_base = (path) -> | |
5 | dir, base = path\match '(.-)([^/]-)$' | |
6 | if dir and #dir > 0 | |
7 | dir = dir\sub 1, #dir - 1 | |
8 | ||
9 | dir, base | |
2 | 10 | |
3 | 11 | -- Key of a Fileder Facet |
4 | 12 | -- contains: |
37 | 45 | -- * @facets - Facet Map (Key to Value) |
38 | 46 | -- * @children - Children Array |
39 | 47 | class Fileder |
40 | -- instantiate from facets and children tables | |
41 | -- or mix in one table (numeric keys are children, remainder facets) | |
42 | -- facet-keys are passed to Key constructor | |
43 | new: (facets, children) => | |
44 | if not children | |
45 | children = for i, child in ipairs facets | |
46 | facets[i] = nil | |
47 | child | |
48 | ||
48 | new: (@store, @path='') => | |
49 | @loaded = false | |
50 | ||
51 | -- lazy-load children, | |
52 | -- allow indexing by name as well as numeric index, | |
49 | 53 | -- automatically mount children on insert |
50 | 54 | @children = setmetatable {}, { |
55 | __len: (t) -> | |
56 | @load! unless @loaded | |
57 | rawlen t | |
58 | ||
59 | __ipairs: (t) -> | |
60 | @load! unless @loaded | |
61 | ipairs t | |
62 | ||
51 | 63 | __index: (t, k) -> |
52 | return rawget t, k unless 'string' == type k | |
53 | ||
54 | @walk "#{@path}/#{k}" | |
64 | @load! unless @loaded | |
65 | ||
66 | if 'string' == type k | |
67 | @walk "#{@path}/#{k}" | |
68 | else | |
69 | rawget t, k | |
55 | 70 | |
56 | 71 | __newindex: (t, k, child) -> |
57 | 72 | rawset t, k, child |
73 | ||
58 | 74 | if @path == '/' |
59 | 75 | child\mount '/' |
60 | 76 | elseif @path |
61 | 77 | child\mount @path .. '/' |
62 | 78 | } |
63 | 79 | |
64 | -- copy children | |
65 | for i, child in ipairs children | |
66 | @children[i] = child | |
67 | ||
68 | -- automatically reify string keys on insert | |
69 | @facets = setmetatable {}, __newindex: (t, key, v) -> | |
70 | rawset t, (Key key), v | |
71 | ||
72 | -- copy facets | |
73 | for k, v in pairs facets | |
74 | @facets[k] = v | |
80 | -- lazy-load facets, | |
81 | -- allow indexing by name as well as numeric index, | |
82 | -- automatically mount children on insert | |
83 | ||
84 | -- we need to store the presence of facets separately from the actual (cached) value, | |
85 | -- because we want to lazily load the tree (index) *and* facet contents. | |
86 | -- @facet_keys maps from key-strings to Key instances ('canonical Key instances') | |
87 | -- @facets maps from canonical keys to cached values and lazy-loads. | |
88 | -- both maps automatically rewrite __index and __newindex for both Key instances and strings. | |
89 | @facet_keys = setmetatable {}, { | |
90 | __pairs: (t) -> | |
91 | @load! unless @loaded | |
92 | next, t | |
93 | ||
94 | __index: (t, k) -> | |
95 | canonical = rawget t, tostring k | |
96 | canonical or= Key k | |
97 | canonical | |
98 | ||
99 | __newindex: (t, k, v) -> | |
100 | k = Key k | |
101 | rawset t, (tostring k), v | |
102 | } | |
103 | @facets = setmetatable {}, { | |
104 | __index: (t, k) -> | |
105 | @load! unless @loaded | |
106 | ||
107 | -- get canonical Key instance | |
108 | k = @facet_keys[k] | |
109 | ||
110 | -- if cached, return | |
111 | if v = rawget t, k | |
112 | return v | |
113 | ||
114 | with v = @store\load_facet @path, k.name, k.type | |
115 | rawset t, k, v | |
116 | ||
117 | __pairs: (t) -> | |
118 | @load! unless @loaded | |
119 | ||
120 | -- force cache all facets | |
121 | for k, v in pairs @facet_keys | |
122 | t[v] | |
123 | ||
124 | pairs @facets | |
125 | ||
126 | __newindex: (t, k, v) -> | |
127 | -- get canonical Key instance | |
128 | k = @facet_keys[k] | |
129 | ||
130 | rawset t, k, v | |
131 | ||
132 | v = k unless v == nil | |
133 | rawset @facet_keys, k, v | |
134 | } | |
135 | ||
136 | load: => | |
137 | @loaded = true | |
138 | ||
139 | for path in @store\list_fileders_in @path | |
140 | table.insert @children, Fileder @store, path | |
141 | ||
142 | for name, type in @store\list_facets @path | |
143 | key = Key name, type | |
144 | @facet_keys[key] = key | |
145 | ||
146 | _, name = dir_base @path | |
147 | @facets['name: alpha'] = name | |
148 | ||
149 | -- -- instantiate from facets and children tables | |
150 | -- -- or mix in one table (numeric keys are children, remainder facets) | |
151 | -- -- facet-keys are passed to Key constructor | |
152 | -- new: (facets, children) => | |
153 | -- if not children | |
154 | -- children = for i, child in ipairs facets | |
155 | -- facets[i] = nil | |
156 | -- child | |
157 | ||
158 | -- -- automatically mount children on insert | |
159 | -- @children = setmetatable {}, { | |
160 | -- __index: (t, k) -> | |
161 | -- return rawget t, k unless 'string' == type k | |
162 | ||
163 | -- @walk "#{@path}/#{k}" | |
164 | ||
165 | -- __newindex: (t, k, child) -> | |
166 | -- rawset t, k, child | |
167 | -- if @path == '/' | |
168 | -- child\mount '/' | |
169 | -- elseif @path | |
170 | -- child\mount @path .. '/' | |
171 | -- } | |
172 | ||
173 | -- -- copy children | |
174 | -- for i, child in ipairs children | |
175 | -- @children[i] = child | |
176 | ||
177 | -- -- automatically reify string keys on insert | |
178 | -- @facets = setmetatable {}, __newindex: (t, key, v) -> | |
179 | -- rawset t, (Key key), v | |
180 | ||
181 | -- -- copy facets | |
182 | -- for k, v in pairs facets | |
183 | -- @facets[k] = v | |
75 | 184 | |
76 | 185 | -- recursively walk to and return the fileder with @path == path |
77 | 186 | -- * path - the path to walk to |
116 | 225 | -- get all facet names (list) |
117 | 226 | get_facets: => |
118 | 227 | names = {} |
119 | for key in pairs @facets | |
228 | for str, key in pairs @facet_keys | |
120 | 229 | names[key.name] = true |
121 | 230 | |
122 | 231 | [name for name in pairs names] |
123 | 232 | |
233 | -- get an index table, listing path, facets and children | |
234 | -- optionally get recursive index | |
124 | 235 | get_index: (recursive=false) => |
125 | 236 | { |
126 | 237 | path: @path |
127 | facets: [{k.name, k.type} for k,v in pairs @facets] | |
238 | facets: [key for str, key in pairs @facet_keys] | |
128 | 239 | children: if recursive |
129 | 240 | [child\get_index true for child in *@children] |
130 | 241 | else |
132 | 243 | } |
133 | 244 | |
134 | 245 | -- check whether a facet is directly available |
135 | -- when passing a Key, set type to false to check for name only | |
136 | 246 | has: (...) => |
137 | 247 | want = Key ... |
138 | 248 | |
139 | for key in pairs @facets | |
140 | continue if key.original | |
141 | ||
142 | if key.name == want.name and key.type == want.type | |
143 | return key | |
249 | @facet_keys[want] | |
144 | 250 | |
145 | 251 | -- check whether any facet with that name exists |
146 | 252 | has_facet: (want) => |
147 | for key in pairs @facets | |
253 | for str, key in pairs @facet_keys | |
148 | 254 | continue if key.original |
149 | 255 | |
150 | 256 | if key.name == want |
156 | 262 | want = Key ... |
157 | 263 | |
158 | 264 | -- filter facets by name |
159 | matching = [ key for key in pairs @facets when key.name == want.name ] | |
265 | matching = [ key for str, key in pairs @facet_keys when key.name == want.name ] | |
160 | 266 | return unless #matching > 0 |
161 | 267 | |
162 | 268 | -- get shortest conversion path |
191 | 297 | |
192 | 298 | __tostring: => "Fileder:#{@path}" |
193 | 299 | |
194 | -- split filename into dirname + basename | |
195 | dir_base = (path) -> | |
196 | dir, base = path\match '(.-)([^/]-)$' | |
197 | if dir and #dir > 0 | |
198 | dir = dir\sub 1, #dir - 1 | |
199 | ||
200 | dir, base | |
201 | ||
202 | -- load tree from a store instance | |
203 | -- optionally load subtree starting at 'root' path | |
204 | load_tree = (store, root='') -> | |
205 | fileders = setmetatable {}, | |
206 | __index: (path) => | |
207 | with val = Fileder {} | |
208 | .path = path | |
209 | rawset @, path, val | |
210 | ||
211 | root = fileders[root] | |
212 | root.facets['name: alpha'] = '' | |
213 | for fn, ft in store\list_facets root.path | |
214 | val = store\load_facet root.path, fn, ft | |
215 | root.facets[Key fn, ft] = val | |
216 | ||
217 | for path in store\list_all_fileders root.path | |
218 | fileder = fileders[path] | |
219 | ||
220 | parent, name = dir_base path | |
221 | fileder.facets['name: alpha'] = name | |
222 | table.insert fileders[parent].children, fileder | |
223 | ||
224 | for fn, ft in store\list_facets path | |
225 | val = store\load_facet path, fn, ft | |
226 | fileder.facets[Key fn, ft] = val | |
227 | ||
228 | root | |
229 | ||
230 | 300 | { |
231 | 301 | :Key |
232 | 302 | :Fileder |
233 | 303 | :dir_base |
234 | :load_tree | |
235 | 304 | } |
0 | import elements from require 'mmm.component' | |
1 | import h1, h2, p, a, i, div, ol, li, br, hr, span, button, section, article from elements | |
2 | ||
3 | _content = div! | |
4 | append = _content\append | |
5 | ||
6 | if MODE == 'SERVER' | |
7 | export ^ | |
8 | class Diagram | |
9 | style = { | |
10 | display: 'inline-block', | |
11 | width: '150px', | |
12 | height: '80px', | |
13 | 'line-height': '80px', | |
14 | color: '#fff', | |
15 | background: '#666', | |
16 | } | |
17 | ||
18 | @id = 1 | |
19 | new: (@func) => | |
20 | @id = "diagram-#{@@id}" | |
21 | @@id += 1 | |
22 | ||
23 | render: => | |
24 | rplc = with div id: @id, :style | |
25 | \append '(diagram goes here)' | |
26 | -- \append "<script type=\"application/lua\"> | |
27 | -- local rplc = js.global.document:getElementById('#{@id}'); | |
28 | -- local fn = #{compile @func} | |
29 | -- diag = Diagram(fn) | |
30 | -- rplc.parentNode:replaceChild(diag.node, rplc) | |
31 | -- </script>" | |
32 | rplc\render! | |
33 | ||
34 | if MODE == 'CLIENT' | |
35 | export ^ | |
36 | export o | |
37 | eval = js.global\eval | |
38 | GRID_W = 50 | |
39 | GRID_H = 40 | |
40 | ||
41 | SVG = | |
42 | doc: eval "(function() { return SVG(document.createElement('svg')); })", | |
43 | G: eval "(function() { return new SVG.G(); })", | |
44 | setmetatable SVG, __call: => @doc! | |
45 | ||
46 | o = do | |
47 | mkobj = eval "(function () { return {}; })" | |
48 | (tbl) -> | |
49 | with obj = mkobj! | |
50 | for k,v in pairs(tbl) | |
51 | obj[k] = v | |
52 | ||
53 | class Diagram | |
54 | new: (f) => | |
55 | @svg = SVG! | |
56 | @arrows = SVG.G! | |
57 | @width, @height = 0, 0 | |
58 | @y = 0 | |
59 | ||
60 | f @ | |
61 | ||
62 | txtattr = o { | |
63 | fill: 'white', | |
64 | 'font-size': '14px', | |
65 | 'text-anchor': 'middle', | |
66 | } | |
67 | block: (color, label, h=1) => | |
68 | @svg\add with SVG.G! | |
69 | with \rect GRID_W, h * GRID_H | |
70 | \attr o fill: color | |
71 | if label | |
72 | with \plain label | |
73 | \move GRID_W/2, 0 | |
74 | \attr txtattr | |
75 | ||
76 | \move @width * GRID_W, (@y + h) * -GRID_H | |
77 | @y += h | |
78 | if @y > @height | |
79 | @height = @y | |
80 | ||
81 | arrattr = o { | |
82 | fill: 'white', | |
83 | 'font-size': '18px', | |
84 | 'text-anchor': 'middle', | |
85 | } | |
86 | arrow: (char, x, y) => | |
87 | with @arrows\plain char | |
88 | \attr arrattr | |
89 | \move (x + 1) * GRID_W, (y - 0.5) * -GRID_H - 11 | |
90 | ||
91 | -- inout: (x=@width, y=@y) => @arrow '⇋', x, y -- U+21CB | |
92 | -- inn: (x=@width, y=@y) => @arrow '↼', x, y+0.25 -- U+21BC | |
93 | -- out: (x=@width, y=@y) => @arrow '⇁', x, y-0.25 -- U+21C1 | |
94 | inout: (x=@width, y=@y) => @arrow '⇆', x, y -- U+21C6 | |
95 | inn: (x=@width, y=@y) => @arrow '←', x, y+0.25 -- U+2190 | |
96 | out: (x=@width, y=@y) => @arrow '→', x, y-0.25 -- U+2192 | |
97 | ||
98 | mind: (label='mind', ...) => @block '#fac710', label, ... | |
99 | phys: (label='phys', ...) => @block '#8fd13f', label, ... | |
100 | digi: (label='digi', ...) => @block '#9510ac', label, ... | |
101 | ||
102 | next: => | |
103 | @y = 0 | |
104 | @width += 1 | |
105 | ||
106 | finish: => | |
107 | return if @node | |
108 | @svg\add @arrows | |
109 | ||
110 | @width += 1 | |
111 | w, h = @width * GRID_W, @height * GRID_H | |
112 | ||
113 | l = GRID_W / 6.5 | |
114 | @svg\add with @svg\line 0, -GRID_H, w, -GRID_H | |
115 | \stroke o width: 2, color: '#ffffff', dasharray: "#{l}, #{l}" | |
116 | ||
117 | @svg\size w, h | |
118 | @svg\viewbox 0, -h, w, h | |
119 | @node = @svg.node | |
120 | ||
121 | addlabel = (label, diagram) -> | |
122 | with div style: { display: 'inline-block', margin: '20px', 'text-align': 'center' } | |
123 | \append diagram | |
124 | \append div label | |
125 | ||
126 | figures = do | |
127 | style = | |
128 | display: 'flex' | |
129 | 'align-items': 'flex-end' | |
130 | 'justify-content': 'space-evenly' | |
131 | (...) -> div { :style, ... } | |
132 | ||
133 | sources = do | |
134 | short = => "#{@id} #{@year}" | |
135 | long = => @names, " (#{@year}): ", (i @title), ", #{@published}" | |
136 | { | |
137 | { | |
138 | id: 'Milgram', | |
139 | title: 'Augmented Reality: A class of displays on the reality-virtuality continuum', | |
140 | published: 'in SPIE Vol. 2351', | |
141 | names: 'P. Milgram, H. Takemura, A. Utsumi, F. Kishino', | |
142 | year: 1994 | |
143 | :long, :short, | |
144 | }, | |
145 | { | |
146 | id: 'Marsh', | |
147 | title: 'Nested Immersion: Describing and Classifying Augmented Virtual Reality', | |
148 | published: 'IEEE Virtual Reality Conference 2015', | |
149 | names: 'W. Marsh, F. Mérienne', | |
150 | year: 2015 | |
151 | :long, :short, | |
152 | }, | |
153 | { | |
154 | id: 'Billinghurst', | |
155 | title: 'The MagicBook: a transitional AR interface', | |
156 | published: 'in Computer & Graphics 25', | |
157 | names: 'M. Billinghurst, H. Kato, I. Poupyrev', | |
158 | year: 2001, | |
159 | :long, :short, | |
160 | }, | |
161 | { | |
162 | id: 'Matrix', | |
163 | title: 'The Matrix', | |
164 | year: 1999, | |
165 | names: 'L. Wachowski, A. Wachowski', | |
166 | long: => @names, " (#{@year}): ", (i @title), " (movie)" | |
167 | short: => tostring @year | |
168 | }, | |
169 | { | |
170 | id: 'Naam', | |
171 | title: 'Nexus', | |
172 | published: 'Angry Robot (novel)', | |
173 | names: 'R. Naam', | |
174 | year: 2012, | |
175 | :long, :short, | |
176 | } | |
177 | } | |
178 | ||
179 | ref = do | |
180 | fmt = (id) -> | |
181 | ||
182 | local src | |
183 | for _src in *sources | |
184 | if _src.id == id | |
185 | src = _src | |
186 | break | |
187 | ||
188 | if src | |
189 | a { src\short!, href: "##{src.id}" } | |
190 | else | |
191 | span id | |
192 | ||
193 | ref = (...) -> | |
194 | refs = { ... } | |
195 | with span "(", fmt refs[1] | |
196 | for i=2, #refs | |
197 | \append ", " | |
198 | \append fmt refs[i] | |
199 | \append ")" | |
200 | ||
201 | references = -> | |
202 | with ol! | |
203 | for src in *sources | |
204 | \append li { id: src.id, src\long! } | |
205 | ||
206 | sect = (label) -> | |
207 | with section style: 'page-break-inside': 'avoid' | |
208 | \append h2 label | |
209 | ||
210 | append with article style: { margin: 'auto', 'max-width': '750px' } | |
211 | \append div 'Sol Bekic', style: 'text-align': 'right' | |
212 | ||
213 | \append h1 { | |
214 | style: { 'text-align': 'center', 'font-size': '2em' }, | |
215 | "Reality Stacks", | |
216 | div "a Taxonomy for Multi-Reality Experiences", style: 'font-size': '0.6em' | |
217 | } | |
218 | ||
219 | \append with sect "Abstract" | |
220 | \append p "With the development of mixed-reality experiences and the corresponding interface devices | |
221 | multiple frameworks for classification of these experiences have been proposed. However these past | |
222 | attempts have mostly been developed alongside and with the intent of capturing specific projects ", | |
223 | (ref 'Marsh', 'Billinghurst'), " or are nevertheless very focused on existing methods and technologies ", | |
224 | (ref 'Milgram'), ". The existing taxonomies also all assume physical reality as a fixpoint and constant and are | |
225 | thereby not suited to describe many fictional mixed-reality environments and altered states of consciousness. | |
226 | In this paper we describe a new model for describing such experiences and examplify it's use with currently | |
227 | existing as well as idealized technologies from popular culture." | |
228 | ||
229 | \append with sect "Terminology" | |
230 | \append p "We propose the following terms and definitions that will be used extensively for the remainder of the paper:" | |
231 | for definition in *{ | |
232 | { "layer of reality": "a closed system consisting of a world model and a set of rules or dynamics operating on and | |
233 | constraining said model." }, | |
234 | { "world model": "describes a world state containing objects, agents and/or concepts on an arbitrary abstraction level." }, | |
235 | '------', | |
236 | { "reality stack": "structure consisting of all layers of reality encoding an agent's interaction with his environment | |
237 | in their world model at a given moment, as well as all layers supporting these respectively." }, | |
238 | '------', | |
239 | { "physical reality": "layer of reality defined by physical matter and the physical laws acting upon it. | |
240 | While the emergent phenomena of micro- and macro physics as well as layers of social existence etc. may be seen | |
241 | as separate layers, for the purpose of this paper we will group these together under the term of physical reality." }, | |
242 | { "mental reality": "layer of reality perceived and processed by the brain of a human agent." }, | |
243 | { "digital reality": "layer of reality created and simulated by a digital system, e.g. a virtual reality game." }, | |
244 | { "phys, mind, digi": "abbreviations for physical, mental and digital reality respectively." }, | |
245 | } | |
246 | if 'string' == type definition | |
247 | \append hr! | |
248 | continue | |
249 | \append with div style: { 'margin-left': '2rem' } | |
250 | term = next definition | |
251 | \append span term, style: { | |
252 | display: 'inline-block', | |
253 | 'margin-left': '-2rem', | |
254 | 'font-weight': 'bold', | |
255 | 'min-width': '140px' | |
256 | } | |
257 | \append span definition[term] | |
258 | ||
259 | \append with sect "Introduction" | |
260 | \append p "We identify two different types of relationships between layers in multi-reality environments. | |
261 | The first is layer nesting. Layer nesting describes how some layers are contained in other layers; i.e. they exist | |
262 | within and can be represented fully by the parent layer's world model and the child layer's rules emerge natively from | |
263 | the parent layer's dynamics. Layer nesting is visualized on the vertical axis in the following diagrams. | |
264 | For each layer of reality on the bottom of the diagram the nested parent layers can be found by tracing a line upwards | |
265 | to the top of the diagram. Following a materialistic point of view, physical reality therefore must completely encompass | |
266 | the top of each diagram." | |
267 | ||
268 | \append p "The second type of relationship describes the information flow between a subject and the layers of reality | |
269 | the subject is immersed in. In a multi-reality experience the subject has access to multiple layers of reality and | |
270 | their corresponding world models simultaneously.", br!, | |
271 | "Depending on the specific experience, different types of and directions for information exchange | |
272 | can exist between these layers and the subject's internal representation of the experience. | |
273 | For the sake of this paper we distinguish only between ", (i "input"), " and ", (i "output"), " data flow (from the | |
274 | perspective of the subject); categorized loosely as information the subject receives from the environment | |
275 | (", (i "input"), ", e.g. visual stimuli) and actions the subject can take to influence the state of the world model | |
276 | (", (i "output"), ", e.g. motor actions) respectively." | |
277 | ||
278 | \append p "In the following diagrams, information flow is visualized horizontally, in the region below the dashed line | |
279 | at the bottom of the diagram. The subject's internal mental model and layer of reality are placed on the bottom left | |
280 | side of the diagram. | |
281 | The layers of reality that the subject experiences directly and that mirror it's internal representations are placed | |
282 | on the far right. There may be multiple layers of reality sharing this space, visualized as a vertical stack of | |
283 | layers. Since the subject must necessarily have a complete internal model of the multi-reality experience around | |
284 | him to feel immersed, the subject's mental layer of reality must span the full height of all the layers visible | |
285 | on the right side of the diagram.", br!, | |
286 | "Information flow itself is now visualized concretely using arrows that cross layer boundaries in the lower part of | |
287 | the diagram as described above. Arrows pointing leftwards denote ", (i "input"), " flow, whilst arrows pointing | |
288 | rightwards denote ", (i "output"), "-directed information flow. In some cases information doesn't flow directly | |
289 | between the layers the subject is directly aware of and the subject's internal representation and instead | |
290 | traverses ", (i "intermediate layers"), " first." | |
291 | ||
292 | \append p "Before we take a look at some reality stacks corresponding to current VR and AR technology, | |
293 | we can take a look at waking life as a baseline stack. To illustrate the format of the diagram we will compare it | |
294 | to the stack corresponding to a dreaming state:" | |
295 | ||
296 | \append with figures! | |
297 | \append addlabel "Waking Life", Diagram => | |
298 | @mind! | |
299 | @inout! | |
300 | @phys! | |
301 | ||
302 | @next! | |
303 | @phys '', 2 | |
304 | @finish! | |
305 | ||
306 | \append addlabel "Dreaming", Diagram => | |
307 | @mind! | |
308 | @phys! | |
309 | @finish! | |
310 | ||
311 | \append p "In both cases, the top of the diagram is fully occupied by the physical layer of reality, colored in green. | |
312 | This is due to the fact that, according to the materialistic theory of mind, human consciousness owes its existance | |
313 | to the physical and chemical dynamics of neurons in our brains. Therefore our mental reality must be considered | |
314 | fully embedded in the physical reality, and consequently it may only appear underneath it in the diagram." | |
315 | ||
316 | \append p "During waking life, we concern ourselves mostly with the physical reality surrounding us. | |
317 | For this reason the physical reality is placed in the lower right corner of the diagram as the layer holding the | |
318 | external world model relevant to the subject. Information flows in both directions between the physical world model | |
319 | and the subject's mental model, as denoted by the two white arrows: Information about the state of the world model | |
320 | enter the subjects mind via the senses (top arrow, pointing leftwards), and choices the subject makes inside of and | |
321 | based on his mental model can feed back into the physical layer through movements (lower arrow, pointing rightwards)." | |
322 | ||
323 | \append p "In the dreaming state on the other hand, the subject is unaware of the physical layer of reality, though | |
324 | the mind remains embedded inside it. When dreaming, subjects' mental models don't depend on external models, hence | |
325 | the mental layer of reality must be the only layer along the bottom of the diagram." | |
326 | ||
327 | \append with sect "Current Technologies" | |
328 | \append p "Since recent technological advancements have enabled the development of VR and AR consumer devices, | |
329 | AR and VR have been established as the potential next frontier of digital entertainment.", br!, | |
330 | "As the names imply, the notion of reality is at the core of both technologies. | |
331 | In the following section we will take a look at the respective stacks of both experience types:" | |
332 | ||
333 | \append with figures! | |
334 | \append addlabel "VR", Diagram => | |
335 | @mind! | |
336 | @phys! | |
337 | @inout nil, 1 | |
338 | ||
339 | @next! | |
340 | @phys '', 2 | |
341 | @inout nil, 1 | |
342 | ||
343 | @next! | |
344 | @digi! | |
345 | @phys '' | |
346 | @finish! | |
347 | ||
348 | ||
349 | \append addlabel "AR", Diagram => | |
350 | @mind! | |
351 | @inout nil, 1.25 | |
352 | @inn nil, 0.5 | |
353 | @phys! | |
354 | ||
355 | @next! | |
356 | @phys '', 2 | |
357 | @inn nil, .5 | |
358 | ||
359 | @next! | |
360 | @digi nil, .5 | |
361 | @phys '', 1.5 | |
362 | @finish! | |
363 | ||
364 | \append p "In both cases we find the physical layer of reality as an ", (i "intermediate layer"), " between the mental | |
365 | and digital layers. Actions taken by the subject have to be acted out physically (corresponding to the | |
366 | information traversing the barrier between mental and physical reality) before they can be again digitized using | |
367 | the various tracking and input technologies (which in turn carry the information across the boundary of the physical | |
368 | and digital spaces)." | |
369 | ||
370 | \append p "The difference between AR and VR lies in the fact that in AR the subject experiences a mixture of the | |
371 | digital and physical world models. This can be seen in the diagram, where we find that right of the diagram origin | |
372 | and the mental model, the diagram splits and terminates in both layers: while information reaches the subject both | |
373 | from the digital reality through the physical one, as well as directly from the physical reality, the subject only | |
374 | directly manipulates state in the physical reality." | |
375 | ||
376 | \append p "The data conversions necessary at layer boundaries incur at the least losses in quality and accuracy of | |
377 | information for purely technical reasons. However ", (i "intermediate layers"), " come at a cost larger than just | |
378 | an additional step of conversion: | |
379 | For information to flow through a layer, it must be encodable within that layer’s world model. | |
380 | This means that the 'weakest link' in a given reality stack determines the upper bound of information possible to | |
381 | encode within said stack and thereby limits the overall expressivity of the stack.", br!, | |
382 | "As a practical example we can consider creating an hypothetical VR application that allows users to traverse a | |
383 | large virtual space by flying. While the human mind is perfectly capable of imagining to fly and control the motion | |
384 | appropriately, it is extremely hard to devise and implement a satisfying setup and control scheme because the | |
385 | physical body of the user needs to be taken into account and it, unlike the corresponding representations in the | |
386 | mental and digital world models, cannot float around freely." | |
387 | ||
388 | \append with sect "Future Developments" | |
389 | \append p "In the previous section we found that the presence of the physical layer in the information path of | |
390 | VR and AR stacks limits the experience as a whole. It follows that the removal of that indirection should be | |
391 | an obvious goal for future developments:" | |
392 | ||
393 | \append figures addlabel "holy grail of VR: 'The Matrix'", Diagram => | |
394 | @mind! | |
395 | @inout! | |
396 | @phys! | |
397 | ||
398 | @next! | |
399 | @digi! | |
400 | @phys '' | |
401 | @finish! | |
402 | ||
403 | \append p "In the action movie 'The Matrix' ", (ref 'Matrix'), ", users of the titular VR environment interface with it | |
404 | by plugging cables into implanted sockets that connect the simulation directly to their central nervous system.", br!, | |
405 | "While these cables and implanted devices are physical devices, they don't constitute the presence of the | |
406 | physical layer of reality in the information path because while they do transmit information, the information | |
407 | remains in either the encoding of the mental model (neural firing patterns) or the encoding of the digital model | |
408 | (e.g. a numeric encoding of a player character's movement in digital space) and the conversion is made directly | |
409 | between those two - the data never assumes the native encoding of the physical layer (e.g. as a physical motion)." | |
410 | ||
411 | \append p "While we are currently far from being able to read arbitrary high-level information from the brain | |
412 | or to synthesize sensual input in human perception by bypassing the sensory organs, brain-computer interfaces (BCI) | |
413 | are a very active area of research with high hopes for comparable achievements in the near future." | |
414 | ||
415 | \append p "Applying this same step of removing the physical layer of reality from AR, we end up with something similar | |
416 | to the nano-particle drug in ", (i "Nexus"), " ", (ref 'Naam'), ". However this does not grant the user a similar | |
417 | amount of control over his experience as the holy grail of VR does, since the user and the physical part of the | |
418 | environment remain bound by the physical layer of reality's laws.", br!, | |
419 | "Instead the holy grail of AR is reached with the creation of a god machine that can manipulate the state of the | |
420 | physical world according to the user's wishes. In this way the digital and physical realities become unified and | |
421 | fully 'augmented'." | |
422 | ||
423 | \append with figures! | |
424 | \append addlabel "'Nexus'", Diagram => | |
425 | @mind! | |
426 | @inout nil, 0.75 | |
427 | @inout nil, 1.25 | |
428 | @phys! | |
429 | ||
430 | @next! | |
431 | @digi nil, .5 | |
432 | @phys '', 1.5 | |
433 | @finish! | |
434 | ||
435 | \append addlabel "holy grail of AR: 'Deus Machina'", Diagram => | |
436 | col = '#92807c' | |
437 | ||
438 | @mind! | |
439 | @inout! | |
440 | @block col, '' | |
441 | ||
442 | @next! | |
443 | @block col, '', 2 | |
444 | @svg\plain('phys + digi')\attr(o fill: 'white', 'font-size': '14px')\move 6, -2 * GRID_H | |
445 | @finish! | |
446 | ||
447 | \append p "Despite the similarities of VR and AR, the two can be considered polar opposites, as becomes evident when | |
448 | we compare their respective utopian implementations: they share the goal of allowing us to experience realities | |
449 | different from the one we naturally inhabit, but while VR seeks to accomplish this by creating a new, nested reality | |
450 | inside ours, thus giving us full control over it. | |
451 | AR, on the other hand, is instead an attempt to retrofit our specific needs directly into the very reality we exist | |
452 | in.", br!, | |
453 | "This is in direct contrast with the popular notion of the 'reality-virtuality continuum' ", (ref 'Milgram'), ": | |
454 | the reality-virtuality continuum places common reality and VR (virtuality) as the two extreme poles, while AR | |
455 | is represented as an intermediate state between the two. Here however we propose to view instead AR and VR as the | |
456 | respective poles and find instead reality at the centerpoint, where the two opposing influences 'cancel out'." | |
457 | ||
458 | \append with sect "Conclusion and Further Work" | |
459 | \append p "In this paper we have proposed a taxonomy and visualization style for multi-reality experiences, as well | |
460 | as demonstrated it's flexibility by applying them as examples. Through the application of the proposed theory, | |
461 | we have also gained a new and contrasting view on preceding work such as the reality-virtuality-continuum. | |
462 | We have also found that the taxonomy can be used outside the research field of media studies and its use may extend | |
463 | as far as philosophy of consciousness (see Appendix below)." | |
464 | ||
465 | \append p "Further research could enhance the proposed theory with better and more concrete definitions. | |
466 | In the future, the proposed taxonomy might be used to create a more extensive and complete classification | |
467 | of reality stacks and to analyse the relationships between them." | |
468 | ||
469 | \append with sect 'References' | |
470 | \append references! | |
471 | ||
472 | \append with sect "Appendix: Relation to Theories of Mind" | |
473 | \append p "This paper starts from a deeply materialistic point of view that borders on microphysicalism. | |
474 | However it should be noted that the diagram style introduced above lends itself also to display other | |
475 | philosophical theories of mind. As an example, the following graphics show a typical VR stack as interpreted by | |
476 | Materialism, Cartesian Dualism and Solipsism respectively:" | |
477 | ||
478 | \append with figures! | |
479 | \append addlabel "VR in Materialism", Diagram => | |
480 | @mind! | |
481 | @inout nil, 1 | |
482 | @phys! | |
483 | ||
484 | @next! | |
485 | @phys '', 2 | |
486 | @inout nil, 1 | |
487 | ||
488 | @next! | |
489 | @digi! | |
490 | @phys '' | |
491 | ||
492 | @finish! | |
493 | ||
494 | \append addlabel "VR in Solipsism", Diagram => | |
495 | @mind nil, 2 | |
496 | @inout nil, 1 | |
497 | ||
498 | @next! | |
499 | @digi! | |
500 | @mind '' | |
501 | @finish! | |
502 | ||
503 | \append addlabel "VR in Cartesian Dualism", Diagram => | |
504 | @mind nil, 2 | |
505 | @inout nil, 1 | |
506 | @next! | |
507 | ||
508 | @phys nil, 2 | |
509 | @inout nil, 1 | |
510 | @next! | |
511 | ||
512 | @digi! | |
513 | @phys '' | |
514 | @finish! | |
515 | ||
516 | \append p "However these philosophical theories of minds also constitute reality stacks by themselves and as such can | |
517 | be compared directly:" | |
518 | ||
519 | \append with figures! | |
520 | \append addlabel "Materialism", Diagram => | |
521 | @mind! | |
522 | @inout! | |
523 | @phys! | |
524 | ||
525 | @next! | |
526 | @phys '', 2 | |
527 | @finish! | |
528 | ||
529 | \append addlabel "Solipsism", Diagram => | |
530 | @mind! | |
531 | @finish! | |
532 | ||
533 | \append addlabel "Cartesian Dualism", Diagram => | |
534 | @mind! | |
535 | @inout! | |
536 | @next! | |
537 | ||
538 | @phys! | |
539 | @finish! | |
540 | ||
541 | _content |
0 | import elements from require 'mmm.component' | |
1 | import h1, h2, p, a, i, div, ol, li, br, hr, span, button, section, article from elements | |
2 | ||
3 | _content = div! | |
4 | append = _content\append | |
5 | ||
6 | if MODE == 'SERVER' | |
7 | export ^ | |
8 | class Diagram | |
9 | style = { | |
10 | display: 'inline-block', | |
11 | width: '150px', | |
12 | height: '80px', | |
13 | 'line-height': '80px', | |
14 | color: '#fff', | |
15 | background: '#666', | |
16 | } | |
17 | ||
18 | @id = 1 | |
19 | new: (@func) => | |
20 | @id = "diagram-#{@@id}" | |
21 | @@id += 1 | |
22 | ||
23 | render: => | |
24 | rplc = with div id: @id, :style | |
25 | \append '(diagram goes here)' | |
26 | -- \append "<script type=\"application/lua\"> | |
27 | -- local rplc = js.global.document:getElementById('#{@id}'); | |
28 | -- local fn = #{compile @func} | |
29 | -- diag = Diagram(fn) | |
30 | -- rplc.parentNode:replaceChild(diag.node, rplc) | |
31 | -- </script>" | |
32 | rplc\render! | |
33 | ||
34 | if MODE == 'CLIENT' | |
35 | export ^ | |
36 | export o | |
37 | eval = js.global\eval | |
38 | GRID_W = 50 | |
39 | GRID_H = 40 | |
40 | ||
41 | SVG = | |
42 | doc: eval "(function() { return SVG(document.createElement('svg')); })", | |
43 | G: eval "(function() { return new SVG.G(); })", | |
44 | setmetatable SVG, __call: => @doc! | |
45 | ||
46 | o = do | |
47 | mkobj = eval "(function () { return {}; })" | |
48 | (tbl) -> | |
49 | with obj = mkobj! | |
50 | for k,v in pairs(tbl) | |
51 | obj[k] = v | |
52 | ||
53 | class Diagram | |
54 | new: (f) => | |
55 | @svg = SVG! | |
56 | @arrows = SVG.G! | |
57 | @width, @height = 0, 0 | |
58 | @y = 0 | |
59 | ||
60 | f @ | |
61 | ||
62 | txtattr = o { | |
63 | fill: 'white', | |
64 | 'font-size': '14px', | |
65 | 'text-anchor': 'middle', | |
66 | } | |
67 | block: (color, label, h=1) => | |
68 | @svg\add with SVG.G! | |
69 | with \rect GRID_W, h * GRID_H | |
70 | \attr o fill: color | |
71 | if label | |
72 | with \plain label | |
73 | \move GRID_W/2, 0 | |
74 | \attr txtattr | |
75 | ||
76 | \move @width * GRID_W, (@y + h) * -GRID_H | |
77 | @y += h | |
78 | if @y > @height | |
79 | @height = @y | |
80 | ||
81 | arrattr = o { | |
82 | fill: 'white', | |
83 | 'font-size': '18px', | |
84 | 'text-anchor': 'middle', | |
85 | } | |
86 | arrow: (char, x, y) => | |
87 | with @arrows\plain char | |
88 | \attr arrattr | |
89 | \move (x + 1) * GRID_W, (y - 0.5) * -GRID_H - 11 | |
90 | ||
91 | -- inout: (x=@width, y=@y) => @arrow '⇋', x, y -- U+21CB | |
92 | -- inn: (x=@width, y=@y) => @arrow '↼', x, y+0.25 -- U+21BC | |
93 | -- out: (x=@width, y=@y) => @arrow '⇁', x, y-0.25 -- U+21C1 | |
94 | inout: (x=@width, y=@y) => @arrow '⇆', x, y -- U+21C6 | |
95 | inn: (x=@width, y=@y) => @arrow '←', x, y+0.25 -- U+2190 | |
96 | out: (x=@width, y=@y) => @arrow '→', x, y-0.25 -- U+2192 | |
97 | ||
98 | mind: (label='mind', ...) => @block '#fac710', label, ... | |
99 | phys: (label='phys', ...) => @block '#8fd13f', label, ... | |
100 | digi: (label='digi', ...) => @block '#9510ac', label, ... | |
101 | ||
102 | next: => | |
103 | @y = 0 | |
104 | @width += 1 | |
105 | ||
106 | finish: => | |
107 | return if @node | |
108 | @svg\add @arrows | |
109 | ||
110 | @width += 1 | |
111 | w, h = @width * GRID_W, @height * GRID_H | |
112 | ||
113 | l = GRID_W / 6.5 | |
114 | @svg\add with @svg\line 0, -GRID_H, w, -GRID_H | |
115 | \stroke o width: 2, color: '#ffffff', dasharray: "#{l}, #{l}" | |
116 | ||
117 | @svg\size w, h | |
118 | @svg\viewbox 0, -h, w, h | |
119 | @node = @svg.node | |
120 | ||
121 | addlabel = (label, diagram) -> | |
122 | with div style: { display: 'inline-block', margin: '20px', 'text-align': 'center' } | |
123 | \append diagram | |
124 | \append div label | |
125 | ||
126 | figures = do | |
127 | style = | |
128 | display: 'flex' | |
129 | 'align-items': 'flex-end' | |
130 | 'justify-content': 'space-evenly' | |
131 | (...) -> div { :style, ... } | |
132 | ||
133 | sources = do | |
134 | short = => "#{@id} #{@year}" | |
135 | long = => @names, " (#{@year}): ", (i @title), ", #{@published}" | |
136 | { | |
137 | { | |
138 | id: 'Milgram', | |
139 | title: 'Augmented Reality: A class of displays on the reality-virtuality continuum', | |
140 | published: 'in SPIE Vol. 2351', | |
141 | names: 'P. Milgram, H. Takemura, A. Utsumi, F. Kishino', | |
142 | year: 1994 | |
143 | :long, :short, | |
144 | }, | |
145 | { | |
146 | id: 'Marsh', | |
147 | title: 'Nested Immersion: Describing and Classifying Augmented Virtual Reality', | |
148 | published: 'IEEE Virtual Reality Conference 2015', | |
149 | names: 'W. Marsh, F. Mérienne', | |
150 | year: 2015 | |
151 | :long, :short, | |
152 | }, | |
153 | { | |
154 | id: 'Billinghurst', | |
155 | title: 'The MagicBook: a transitional AR interface', | |
156 | published: 'in Computer & Graphics 25', | |
157 | names: 'M. Billinghurst, H. Kato, I. Poupyrev', | |
158 | year: 2001, | |
159 | :long, :short, | |
160 | }, | |
161 | { | |
162 | id: 'Matrix', | |
163 | title: 'The Matrix', | |
164 | year: 1999, | |
165 | names: 'L. Wachowski, A. Wachowski', | |
166 | long: => @names, " (#{@year}): ", (i @title), " (movie)" | |
167 | short: => tostring @year | |
168 | }, | |
169 | { | |
170 | id: 'Naam', | |
171 | title: 'Nexus', | |
172 | published: 'Angry Robot (novel)', | |
173 | names: 'R. Naam', | |
174 | year: 2012, | |
175 | :long, :short, | |
176 | } | |
177 | } | |
178 | ||
179 | ref = do | |
180 | fmt = (id) -> | |
181 | ||
182 | local src | |
183 | for _src in *sources | |
184 | if _src.id == id | |
185 | src = _src | |
186 | break | |
187 | ||
188 | if src | |
189 | a { src\short!, href: "##{src.id}" } | |
190 | else | |
191 | span id | |
192 | ||
193 | ref = (...) -> | |
194 | refs = { ... } | |
195 | with span "(", fmt refs[1] | |
196 | for i=2, #refs | |
197 | \append ", " | |
198 | \append fmt refs[i] | |
199 | \append ")" | |
200 | ||
201 | references = -> | |
202 | with ol! | |
203 | for src in *sources | |
204 | \append li { id: src.id, src\long! } | |
205 | ||
206 | sect = (label) -> | |
207 | with section style: 'page-break-inside': 'avoid' | |
208 | \append h2 label | |
209 | ||
210 | append with article style: { margin: 'auto', 'max-width': '750px' } | |
211 | \append div 'Sol Bekic', style: 'text-align': 'right' | |
212 | ||
213 | \append h1 { | |
214 | style: { 'text-align': 'center', 'font-size': '2em' }, | |
215 | "Reality Stacks", | |
216 | div "a Taxonomy for Multi-Reality Experiences", style: 'font-size': '0.6em' | |
217 | } | |
218 | ||
219 | \append with sect "Abstract" | |
220 | \append p "With the development of mixed-reality experiences and the corresponding interface devices | |
221 | multiple frameworks for classification of these experiences have been proposed. However these past | |
222 | attempts have mostly been developed alongside and with the intent of capturing specific projects ", | |
223 | (ref 'Marsh', 'Billinghurst'), " or are nevertheless very focused on existing methods and technologies ", | |
224 | (ref 'Milgram'), ". The existing taxonomies also all assume physical reality as a fixpoint and constant and are | |
225 | thereby not suited to describe many fictional mixed-reality environments and altered states of consciousness. | |
226 | In this paper we describe a new model for describing such experiences and examplify it's use with currently | |
227 | existing as well as idealized technologies from popular culture." | |
228 | ||
229 | \append with sect "Terminology" | |
230 | \append p "We propose the following terms and definitions that will be used extensively for the remainder of the paper:" | |
231 | for definition in *{ | |
232 | { "layer of reality": "a closed system consisting of a world model and a set of rules or dynamics operating on and | |
233 | constraining said model." }, | |
234 | { "world model": "describes a world state containing objects, agents and/or concepts on an arbitrary abstraction level." }, | |
235 | '------', | |
236 | { "reality stack": "structure consisting of all layers of reality encoding an agent's interaction with his environment | |
237 | in their world model at a given moment, as well as all layers supporting these respectively." }, | |
238 | '------', | |
239 | { "physical reality": "layer of reality defined by physical matter and the physical laws acting upon it. | |
240 | While the emergent phenomena of micro- and macro physics as well as layers of social existence etc. may be seen | |
241 | as separate layers, for the purpose of this paper we will group these together under the term of physical reality." }, | |
242 | { "mental reality": "layer of reality perceived and processed by the brain of a human agent." }, | |
243 | { "digital reality": "layer of reality created and simulated by a digital system, e.g. a virtual reality game." }, | |
244 | { "phys, mind, digi": "abbreviations for physical, mental and digital reality respectively." }, | |
245 | } | |
246 | if 'string' == type definition | |
247 | \append hr! | |
248 | continue | |
249 | \append with div style: { 'margin-left': '2rem' } | |
250 | term = next definition | |
251 | \append span term, style: { | |
252 | display: 'inline-block', | |
253 | 'margin-left': '-2rem', | |
254 | 'font-weight': 'bold', | |
255 | 'min-width': '140px' | |
256 | } | |
257 | \append span definition[term] | |
258 | ||
259 | \append with sect "Introduction" | |
260 | \append p "We identify two different types of relationships between layers in multi-reality environments. | |
261 | The first is layer nesting. Layer nesting describes how some layers are contained in other layers; i.e. they exist | |
262 | within and can be represented fully by the parent layer's world model and the child layer's rules emerge natively from | |
263 | the parent layer's dynamics. Layer nesting is visualized on the vertical axis in the following diagrams. | |
264 | For each layer of reality on the bottom of the diagram the nested parent layers can be found by tracing a line upwards | |
265 | to the top of the diagram. Following a materialistic point of view, physical reality therefore must completely encompass | |
266 | the top of each diagram." | |
267 | ||
268 | \append p "The second type of relationship describes the information flow between a subject and the layers of reality | |
269 | the subject is immersed in. In a multi-reality experience the subject has access to multiple layers of reality and | |
270 | their corresponding world models simultaneously.", br!, | |
271 | "Depending on the specific experience, different types of and directions for information exchange | |
272 | can exist between these layers and the subject's internal representation of the experience. | |
273 | For the sake of this paper we distinguish only between ", (i "input"), " and ", (i "output"), " data flow (from the | |
274 | perspective of the subject); categorized loosely as information the subject receives from the environment | |
275 | (", (i "input"), ", e.g. visual stimuli) and actions the subject can take to influence the state of the world model | |
276 | (", (i "output"), ", e.g. motor actions) respectively." | |
277 | ||
278 | \append p "In the following diagrams, information flow is visualized horizontally, in the region below the dashed line | |
279 | at the bottom of the diagram. The subject's internal mental model and layer of reality are placed on the bottom left | |
280 | side of the diagram. | |
281 | The layers of reality that the subject experiences directly and that mirror it's internal representations are placed | |
282 | on the far right. There may be multiple layers of reality sharing this space, visualized as a vertical stack of | |
283 | layers. Since the subject must necessarily have a complete internal model of the multi-reality experience around | |
284 | him to feel immersed, the subject's mental layer of reality must span the full height of all the layers visible | |
285 | on the right side of the diagram.", br!, | |
286 | "Information flow itself is now visualized concretely using arrows that cross layer boundaries in the lower part of | |
287 | the diagram as described above. Arrows pointing leftwards denote ", (i "input"), " flow, whilst arrows pointing | |
288 | rightwards denote ", (i "output"), "-directed information flow. In some cases information doesn't flow directly | |
289 | between the layers the subject is directly aware of and the subject's internal representation and instead | |
290 | traverses ", (i "intermediate layers"), " first." | |
291 | ||
292 | \append p "Before we take a look at some reality stacks corresponding to current VR and AR technology, | |
293 | we can take a look at waking life as a baseline stack. To illustrate the format of the diagram we will compare it | |
294 | to the stack corresponding to a dreaming state:" | |
295 | ||
296 | \append with figures! | |
297 | \append addlabel "Waking Life", Diagram => | |
298 | @mind! | |
299 | @inout! | |
300 | @phys! | |
301 | ||
302 | @next! | |
303 | @phys '', 2 | |
304 | @finish! | |
305 | ||
306 | \append addlabel "Dreaming", Diagram => | |
307 | @mind! | |
308 | @phys! | |
309 | @finish! | |
310 | ||
311 | \append p "In both cases, the top of the diagram is fully occupied by the physical layer of reality, colored in green. | |
312 | This is due to the fact that, according to the materialistic theory of mind, human consciousness owes its existance | |
313 | to the physical and chemical dynamics of neurons in our brains. Therefore our mental reality must be considered | |
314 | fully embedded in the physical reality, and consequently it may only appear underneath it in the diagram." | |
315 | ||
316 | \append p "During waking life, we concern ourselves mostly with the physical reality surrounding us. | |
317 | For this reason the physical reality is placed in the lower right corner of the diagram as the layer holding the | |
318 | external world model relevant to the subject. Information flows in both directions between the physical world model | |
319 | and the subject's mental model, as denoted by the two white arrows: Information about the state of the world model | |
320 | enter the subjects mind via the senses (top arrow, pointing leftwards), and choices the subject makes inside of and | |
321 | based on his mental model can feed back into the physical layer through movements (lower arrow, pointing rightwards)." | |
322 | ||
323 | \append p "In the dreaming state on the other hand, the subject is unaware of the physical layer of reality, though | |
324 | the mind remains embedded inside it. When dreaming, subjects' mental models don't depend on external models, hence | |
325 | the mental layer of reality must be the only layer along the bottom of the diagram." | |
326 | ||
327 | \append with sect "Current Technologies" | |
328 | \append p "Since recent technological advancements have enabled the development of VR and AR consumer devices, | |
329 | AR and VR have been established as the potential next frontier of digital entertainment.", br!, | |
330 | "As the names imply, the notion of reality is at the core of both technologies. | |
331 | In the following section we will take a look at the respective stacks of both experience types:" | |
332 | ||
333 | \append with figures! | |
334 | \append addlabel "VR", Diagram => | |
335 | @mind! | |
336 | @phys! | |
337 | @inout nil, 1 | |
338 | ||
339 | @next! | |
340 | @phys '', 2 | |
341 | @inout nil, 1 | |
342 | ||
343 | @next! | |
344 | @digi! | |
345 | @phys '' | |
346 | @finish! | |
347 | ||
348 | ||
349 | \append addlabel "AR", Diagram => | |
350 | @mind! | |
351 | @inout nil, 1.25 | |
352 | @inn nil, 0.5 | |
353 | @phys! | |
354 | ||
355 | @next! | |
356 | @phys '', 2 | |
357 | @inn nil, .5 | |
358 | ||
359 | @next! | |
360 | @digi nil, .5 | |
361 | @phys '', 1.5 | |
362 | @finish! | |
363 | ||
364 | \append p "In both cases we find the physical layer of reality as an ", (i "intermediate layer"), " between the mental | |
365 | and digital layers. Actions taken by the subject have to be acted out physically (corresponding to the | |
366 | information traversing the barrier between mental and physical reality) before they can be again digitized using | |
367 | the various tracking and input technologies (which in turn carry the information across the boundary of the physical | |
368 | and digital spaces)." | |
369 | ||
370 | \append p "The difference between AR and VR lies in the fact that in AR the subject experiences a mixture of the | |
371 | digital and physical world models. This can be seen in the diagram, where we find that right of the diagram origin | |
372 | and the mental model, the diagram splits and terminates in both layers: while information reaches the subject both | |
373 | from the digital reality through the physical one, as well as directly from the physical reality, the subject only | |
374 | directly manipulates state in the physical reality." | |
375 | ||
376 | \append p "The data conversions necessary at layer boundaries incur at the least losses in quality and accuracy of | |
377 | information for purely technical reasons. However ", (i "intermediate layers"), " come at a cost larger than just | |
378 | an additional step of conversion: | |
379 | For information to flow through a layer, it must be encodable within that layer’s world model. | |
380 | This means that the 'weakest link' in a given reality stack determines the upper bound of information possible to | |
381 | encode within said stack and thereby limits the overall expressivity of the stack.", br!, | |
382 | "As a practical example we can consider creating an hypothetical VR application that allows users to traverse a | |
383 | large virtual space by flying. While the human mind is perfectly capable of imagining to fly and control the motion | |
384 | appropriately, it is extremely hard to devise and implement a satisfying setup and control scheme because the | |
385 | physical body of the user needs to be taken into account and it, unlike the corresponding representations in the | |
386 | mental and digital world models, cannot float around freely." | |
387 | ||
388 | \append with sect "Future Developments" | |
389 | \append p "In the previous section we found that the presence of the physical layer in the information path of | |
390 | VR and AR stacks limits the experience as a whole. It follows that the removal of that indirection should be | |
391 | an obvious goal for future developments:" | |
392 | ||
393 | \append figures addlabel "holy grail of VR: 'The Matrix'", Diagram => | |
394 | @mind! | |
395 | @inout! | |
396 | @phys! | |
397 | ||
398 | @next! | |
399 | @digi! | |
400 | @phys '' | |
401 | @finish! | |
402 | ||
403 | \append p "In the action movie 'The Matrix' ", (ref 'Matrix'), ", users of the titular VR environment interface with it | |
404 | by plugging cables into implanted sockets that connect the simulation directly to their central nervous system.", br!, | |
405 | "While these cables and implanted devices are physical devices, they don't constitute the presence of the | |
406 | physical layer of reality in the information path because while they do transmit information, the information | |
407 | remains in either the encoding of the mental model (neural firing patterns) or the encoding of the digital model | |
408 | (e.g. a numeric encoding of a player character's movement in digital space) and the conversion is made directly | |
409 | between those two - the data never assumes the native encoding of the physical layer (e.g. as a physical motion)." | |
410 | ||
411 | \append p "While we are currently far from being able to read arbitrary high-level information from the brain | |
412 | or to synthesize sensual input in human perception by bypassing the sensory organs, brain-computer interfaces (BCI) | |
413 | are a very active area of research with high hopes for comparable achievements in the near future." | |
414 | ||
415 | \append p "Applying this same step of removing the physical layer of reality from AR, we end up with something similar | |
416 | to the nano-particle drug in ", (i "Nexus"), " ", (ref 'Naam'), ". However this does not grant the user a similar | |
417 | amount of control over his experience as the holy grail of VR does, since the user and the physical part of the | |
418 | environment remain bound by the physical layer of reality's laws.", br!, | |
419 | "Instead the holy grail of AR is reached with the creation of a god machine that can manipulate the state of the | |
420 | physical world according to the user's wishes. In this way the digital and physical realities become unified and | |
421 | fully 'augmented'." | |
422 | ||
423 | \append with figures! | |
424 | \append addlabel "'Nexus'", Diagram => | |
425 | @mind! | |
426 | @inout nil, 0.75 | |
427 | @inout nil, 1.25 | |
428 | @phys! | |
429 | ||
430 | @next! | |
431 | @digi nil, .5 | |
432 | @phys '', 1.5 | |
433 | @finish! | |
434 | ||
435 | \append addlabel "holy grail of AR: 'Deus Machina'", Diagram => | |
436 | col = '#92807c' | |
437 | ||
438 | @mind! | |
439 | @inout! | |
440 | @block col, '' | |
441 | ||
442 | @next! | |
443 | @block col, '', 2 | |
444 | @svg\plain('phys + digi')\attr(o fill: 'white', 'font-size': '14px')\move 6, -2 * GRID_H | |
445 | @finish! | |
446 | ||
447 | \append p "Despite the similarities of VR and AR, the two can be considered polar opposites, as becomes evident when | |
448 | we compare their respective utopian implementations: they share the goal of allowing us to experience realities | |
449 | different from the one we naturally inhabit, but while VR seeks to accomplish this by creating a new, nested reality | |
450 | inside ours, thus giving us full control over it. | |
451 | AR, on the other hand, is instead an attempt to retrofit our specific needs directly into the very reality we exist | |
452 | in.", br!, | |
453 | "This is in direct contrast with the popular notion of the 'reality-virtuality continuum' ", (ref 'Milgram'), ": | |
454 | the reality-virtuality continuum places common reality and VR (virtuality) as the two extreme poles, while AR | |
455 | is represented as an intermediate state between the two. Here however we propose to view instead AR and VR as the | |
456 | respective poles and find instead reality at the centerpoint, where the two opposing influences 'cancel out'." | |
457 | ||
458 | \append with sect "Conclusion and Further Work" | |
459 | \append p "In this paper we have proposed a taxonomy and visualization style for multi-reality experiences, as well | |
460 | as demonstrated it's flexibility by applying them as examples. Through the application of the proposed theory, | |
461 | we have also gained a new and contrasting view on preceding work such as the reality-virtuality-continuum. | |
462 | We have also found that the taxonomy can be used outside the research field of media studies and its use may extend | |
463 | as far as philosophy of consciousness (see Appendix below)." | |
464 | ||
465 | \append p "Further research could enhance the proposed theory with better and more concrete definitions. | |
466 | In the future, the proposed taxonomy might be used to create a more extensive and complete classification | |
467 | of reality stacks and to analyse the relationships between them." | |
468 | ||
469 | \append with sect 'References' | |
470 | \append references! | |
471 | ||
472 | \append with sect "Appendix: Relation to Theories of Mind" | |
473 | \append p "This paper starts from a deeply materialistic point of view that borders on microphysicalism. | |
474 | However it should be noted that the diagram style introduced above lends itself also to display other | |
475 | philosophical theories of mind. As an example, the following graphics show a typical VR stack as interpreted by | |
476 | Materialism, Cartesian Dualism and Solipsism respectively:" | |
477 | ||
478 | \append with figures! | |
479 | \append addlabel "VR in Materialism", Diagram => | |
480 | @mind! | |
481 | @inout nil, 1 | |
482 | @phys! | |
483 | ||
484 | @next! | |
485 | @phys '', 2 | |
486 | @inout nil, 1 | |
487 | ||
488 | @next! | |
489 | @digi! | |
490 | @phys '' | |
491 | ||
492 | @finish! | |
493 | ||
494 | \append addlabel "VR in Solipsism", Diagram => | |
495 | @mind nil, 2 | |
496 | @inout nil, 1 | |
497 | ||
498 | @next! | |
499 | @digi! | |
500 | @mind '' | |
501 | @finish! | |
502 | ||
503 | \append addlabel "VR in Cartesian Dualism", Diagram => | |
504 | @mind nil, 2 | |
505 | @inout nil, 1 | |
506 | @next! | |
507 | ||
508 | @phys nil, 2 | |
509 | @inout nil, 1 | |
510 | @next! | |
511 | ||
512 | @digi! | |
513 | @phys '' | |
514 | @finish! | |
515 | ||
516 | \append p "However these philosophical theories of minds also constitute reality stacks by themselves and as such can | |
517 | be compared directly:" | |
518 | ||
519 | \append with figures! | |
520 | \append addlabel "Materialism", Diagram => | |
521 | @mind! | |
522 | @inout! | |
523 | @phys! | |
524 | ||
525 | @next! | |
526 | @phys '', 2 | |
527 | @finish! | |
528 | ||
529 | \append addlabel "Solipsism", Diagram => | |
530 | @mind! | |
531 | @finish! | |
532 | ||
533 | \append addlabel "Cartesian Dualism", Diagram => | |
534 | @mind! | |
535 | @inout! | |
536 | @next! | |
537 | ||
538 | @phys! | |
539 | @finish! | |
540 | ||
541 | _content |
14 | 14 | demo = tohtml example! |
15 | 15 | |
16 | 16 | div the_code, div demo, class: 'example' |
17 | ||
18 | raw_find = (fileder, key_pat) -> | |
19 | for key, val in pairs fileder.facets | |
20 | return val if key\tostring!\match key_pat | |
21 | 17 | |
22 | 18 | => |
23 | 19 | example = (name) -> |
0 | local p = package.preload | |
1 | if not p["mmm.canvasapp"] then p["mmm.canvasapp"] = load("local window = js.global\ | |
2 | local js = require('js')\ | |
3 | local a, canvas, div, button, script\ | |
4 | do\ | |
5 | local _obj_0 = require('mmm.dom')\ | |
6 | a, canvas, div, button, script = _obj_0.a, _obj_0.canvas, _obj_0.div, _obj_0.button, _obj_0.script\ | |
7 | end\ | |
8 | local CanvasApp\ | |
9 | do\ | |
10 | local _class_0\ | |
11 | local _base_0 = {\ | |
12 | width = 500,\ | |
13 | height = 400,\ | |
14 | update = function(self, dt)\ | |
15 | self.time = self.time + dt\ | |
16 | if self.length and self.time > self.length then\ | |
17 | self.time = self.time - self.length\ | |
18 | return true\ | |
19 | end\ | |
20 | end,\ | |
21 | render = function(self, fps)\ | |
22 | if fps == nil then\ | |
23 | fps = 60\ | |
24 | end\ | |
25 | assert(self.length, 'cannot render CanvasApp without length set')\ | |
26 | self.paused = true\ | |
27 | local actual_render\ | |
28 | actual_render = function()\ | |
29 | local writer = js.new(window.Whammy.Video, fps)\ | |
30 | local doFrame\ | |
31 | doFrame = function()\ | |
32 | local done = self:update(1 / fps)\ | |
33 | self.ctx:resetTransform()\ | |
34 | self:draw()\ | |
35 | writer:add(self.canvas)\ | |
36 | if done or self.time >= self.length then\ | |
37 | local blob = writer:compile()\ | |
38 | local name = tostring(self.__class.__name) .. \"_\" .. tostring(fps) .. \"fps.webm\"\ | |
39 | return self.node.lastChild:appendChild(a(name, {\ | |
40 | download = name,\ | |
41 | href = window.URL:createObjectURL(blob)\ | |
42 | }))\ | |
43 | else\ | |
44 | return window:setTimeout(doFrame)\ | |
45 | end\ | |
46 | end\ | |
47 | self.time = 0\ | |
48 | return doFrame()\ | |
49 | end\ | |
50 | if window.Whammy then\ | |
51 | return actual_render()\ | |
52 | else\ | |
53 | window.global = window.global or window\ | |
54 | return document.body:appendChild(script({\ | |
55 | onload = actual_render,\ | |
56 | src = 'https://cdn.jsdelivr.net/npm/whammy@0.0.1/whammy.min.js'\ | |
57 | }))\ | |
58 | end\ | |
59 | end\ | |
60 | }\ | |
61 | _base_0.__index = _base_0\ | |
62 | _class_0 = setmetatable({\ | |
63 | __init = function(self, show_menu, paused)\ | |
64 | if show_menu == nil then\ | |
65 | show_menu = false\ | |
66 | end\ | |
67 | self.paused = paused\ | |
68 | self.canvas = canvas({\ | |
69 | width = self.width,\ | |
70 | height = self.height\ | |
71 | })\ | |
72 | self.ctx = self.canvas:getContext('2d')\ | |
73 | self.time = 0\ | |
74 | self.canvas.tabIndex = 0\ | |
75 | self.canvas:addEventListener('click', function(_, e)\ | |
76 | return self.click and self:click(e.offsetX, e.offsetY, e.button)\ | |
77 | end)\ | |
78 | self.canvas:addEventListener('keydown', function(_, e)\ | |
79 | return self.keydown and self:keydown(e.key, e.code)\ | |
80 | end)\ | |
81 | local lastMillis = window.performance:now()\ | |
82 | local animationFrame\ | |
83 | animationFrame = function(_, millis)\ | |
84 | self:update((millis - lastMillis) / 1000)\ | |
85 | self.ctx:resetTransform()\ | |
86 | self:draw()\ | |
87 | lastMillis = millis\ | |
88 | if not self.paused then\ | |
89 | return window:setTimeout((function()\ | |
90 | return window:requestAnimationFrame(animationFrame)\ | |
91 | end), 0)\ | |
92 | end\ | |
93 | end\ | |
94 | window:requestAnimationFrame(animationFrame)\ | |
95 | if show_menu then\ | |
96 | self.node = div({\ | |
97 | className = 'canvas_app',\ | |
98 | self.canvas,\ | |
99 | div({\ | |
100 | className = 'overlay',\ | |
101 | button('render 30fps', {\ | |
102 | onclick = function()\ | |
103 | return self:render(30)\ | |
104 | end\ | |
105 | }),\ | |
106 | button('render 60fps', {\ | |
107 | onclick = function()\ | |
108 | return self:render(60)\ | |
109 | end\ | |
110 | })\ | |
111 | })\ | |
112 | })\ | |
113 | else\ | |
114 | self.node = self.canvas\ | |
115 | end\ | |
116 | end,\ | |
117 | __base = _base_0,\ | |
118 | __name = \"CanvasApp\"\ | |
119 | }, {\ | |
120 | __index = _base_0,\ | |
121 | __call = function(cls, ...)\ | |
122 | local _self_0 = setmetatable({}, _base_0)\ | |
123 | cls.__init(_self_0, ...)\ | |
124 | return _self_0\ | |
125 | end\ | |
126 | })\ | |
127 | _base_0.__class = _class_0\ | |
128 | CanvasApp = _class_0\ | |
129 | end\ | |
130 | return {\ | |
131 | CanvasApp = CanvasApp\ | |
132 | }\ | |
133 | ", "mmm/canvasapp.client.lua") end | |
134 | if not p["mmm.color"] then p["mmm.color"] = load("local rgb\ | |
135 | rgb = function(r, g, b)\ | |
136 | if 'table' == type(r) then\ | |
137 | r, g, b = table.unpack(r)\ | |
138 | end\ | |
139 | return \"rgb(\" .. tostring(r * 255) .. \", \" .. tostring(g * 255) .. \", \" .. tostring(b * 255) .. \")\"\ | |
140 | end\ | |
141 | local rgba\ | |
142 | rgba = function(r, g, b, a)\ | |
143 | if 'table' == type(r) then\ | |
144 | r, g, b, a = table.unpack(r)\ | |
145 | end\ | |
146 | return \"rgba(\" .. tostring(r * 255) .. \", \" .. tostring(g * 255) .. \", \" .. tostring(b * 255) .. \", \" .. tostring(a or 1) .. \")\"\ | |
147 | end\ | |
148 | local hsl\ | |
149 | hsl = function(h, s, l)\ | |
150 | if 'table' == type(h) then\ | |
151 | h, s, l = table.unpack(h)\ | |
152 | end\ | |
153 | return \"hsl(\" .. tostring(h * 360) .. \", \" .. tostring(s * 100) .. \"%, \" .. tostring(l * 100) .. \"%)\"\ | |
154 | end\ | |
155 | local hsla\ | |
156 | hsla = function(h, s, l, a)\ | |
157 | if 'table' == type(h) then\ | |
158 | h, s, l, a = table.unpack(h)\ | |
159 | end\ | |
160 | return \"hsla(\" .. tostring(h * 360) .. \", \" .. tostring(s * 100) .. \"%, \" .. tostring(l * 100) .. \"%, \" .. tostring(a or 1) .. \")\"\ | |
161 | end\ | |
162 | return {\ | |
163 | rgb = rgb,\ | |
164 | rgba = rgba,\ | |
165 | hsl = hsl,\ | |
166 | hsla = hsla\ | |
167 | }\ | |
168 | ", "mmm/color.lua") end | |
169 | if not p["mmm.highlighting"] then p["mmm.highlighting"] = load("local code\ | |
170 | code = require('mmm.dom').code\ | |
171 | local highlight\ | |
172 | local trim\ | |
173 | trim = function(str)\ | |
174 | return str:match('^ *(..-) *$')\ | |
175 | end\ | |
176 | if MODE == 'SERVER' then\ | |
177 | highlight = function(lang, str)\ | |
178 | assert(str, 'no string to highlight')\ | |
179 | return code((trim(str)), {\ | |
180 | class = \"hljs lang-\" .. tostring(lang)\ | |
181 | })\ | |
182 | end\ | |
183 | else\ | |
184 | highlight = function(lang, str)\ | |
185 | assert(str, 'no string to highlight')\ | |
186 | local result = window.hljs:highlight(lang, (trim(str)), true)\ | |
187 | do\ | |
188 | local _with_0 = code({\ | |
189 | class = \"hljs lang-\" .. tostring(lang)\ | |
190 | })\ | |
191 | _with_0.innerHTML = result.value\ | |
192 | return _with_0\ | |
193 | end\ | |
194 | end\ | |
195 | end\ | |
196 | local languages = setmetatable({ }, {\ | |
197 | __index = function(self, name)\ | |
198 | do\ | |
199 | local val\ | |
200 | val = function(str)\ | |
201 | return highlight(name, str)\ | |
202 | end\ | |
203 | self[name] = val\ | |
204 | return val\ | |
205 | end\ | |
206 | end\ | |
207 | })\ | |
208 | return {\ | |
209 | highlight = highlight,\ | |
210 | languages = languages\ | |
211 | }\ | |
212 | ", "mmm/highlighting.lua") end | |
213 | if not p["mmm"] then p["mmm"] = load("window = js.global\ | |
214 | local console\ | |
215 | document, console = window.document, window.console\ | |
216 | MODE = 'CLIENT'\ | |
217 | local deep_tostring\ | |
218 | deep_tostring = function(tbl, space)\ | |
219 | if space == nil then\ | |
220 | space = ''\ | |
221 | end\ | |
222 | if 'userdata' == type(tbl) then\ | |
223 | return tbl\ | |
224 | end\ | |
225 | local buf = space .. tostring(tbl)\ | |
226 | if not ('table' == type(tbl)) then\ | |
227 | return buf\ | |
228 | end\ | |
229 | buf = buf .. ' {\\n'\ | |
230 | for k, v in pairs(tbl) do\ | |
231 | buf = buf .. tostring(space) .. \" [\" .. tostring(k) .. \"]: \" .. tostring(deep_tostring(v, space .. ' ')) .. \"\\n\"\ | |
232 | end\ | |
233 | buf = buf .. tostring(space) .. \"}\"\ | |
234 | return buf\ | |
235 | end\ | |
236 | print = function(...)\ | |
237 | local contents\ | |
238 | do\ | |
239 | local _accum_0 = { }\ | |
240 | local _len_0 = 1\ | |
241 | local _list_0 = {\ | |
242 | ...\ | |
243 | }\ | |
244 | for _index_0 = 1, #_list_0 do\ | |
245 | local v = _list_0[_index_0]\ | |
246 | _accum_0[_len_0] = deep_tostring(v)\ | |
247 | _len_0 = _len_0 + 1\ | |
248 | end\ | |
249 | contents = _accum_0\ | |
250 | end\ | |
251 | return console:log(table.unpack(contents))\ | |
252 | end\ | |
253 | warn = function(...)\ | |
254 | local contents\ | |
255 | do\ | |
256 | local _accum_0 = { }\ | |
257 | local _len_0 = 1\ | |
258 | local _list_0 = {\ | |
259 | ...\ | |
260 | }\ | |
261 | for _index_0 = 1, #_list_0 do\ | |
262 | local v = _list_0[_index_0]\ | |
263 | _accum_0[_len_0] = deep_tostring(v)\ | |
264 | _len_0 = _len_0 + 1\ | |
265 | end\ | |
266 | contents = _accum_0\ | |
267 | end\ | |
268 | return console:warn(table.unpack(contents))\ | |
269 | end\ | |
270 | package.path = '/?.lua;/?/init.lua'\ | |
271 | do\ | |
272 | local _require = require\ | |
273 | relative = function(base, sub)\ | |
274 | if not ('number' == type(sub)) then\ | |
275 | sub = 0\ | |
276 | end\ | |
277 | for i = 1, sub do\ | |
278 | base = base:match('^(.*)%.%w+$')\ | |
279 | end\ | |
280 | return function(name, x)\ | |
281 | if '.' == name:sub(1, 1) then\ | |
282 | name = base .. name\ | |
283 | end\ | |
284 | return _require(name)\ | |
285 | end\ | |
286 | end\ | |
287 | end\ | |
288 | if on_load then\ | |
289 | for _index_0 = 1, #on_load do\ | |
290 | local f = on_load[_index_0]\ | |
291 | f()\ | |
292 | end\ | |
293 | end\ | |
294 | on_load = setmetatable({ }, {\ | |
295 | __newindex = function(t, k, v)\ | |
296 | rawset(t, k, v)\ | |
297 | return v()\ | |
298 | end\ | |
299 | })\ | |
300 | ", "mmm/init.client.lua") end | |
301 | if not p["mmm.ordered"] then p["mmm.ordered"] = load("local sort\ | |
302 | sort = function(t, order_fn, only_strings)\ | |
303 | do\ | |
304 | local index\ | |
305 | do\ | |
306 | local _accum_0 = { }\ | |
307 | local _len_0 = 1\ | |
308 | for k, v in pairs(t) do\ | |
309 | if (not only_strings) or 'string' == type(k) then\ | |
310 | _accum_0[_len_0] = k\ | |
311 | _len_0 = _len_0 + 1\ | |
312 | end\ | |
313 | end\ | |
314 | index = _accum_0\ | |
315 | end\ | |
316 | table.sort(index, order_fn)\ | |
317 | return index\ | |
318 | end\ | |
319 | end\ | |
320 | local onext\ | |
321 | onext = function(state, key)\ | |
322 | state.i = state.i + state.step\ | |
323 | local t, index, i\ | |
324 | t, index, i = state.t, state.index, state.i\ | |
325 | do\ | |
326 | key = index[i]\ | |
327 | if key then\ | |
328 | return key, t[key]\ | |
329 | end\ | |
330 | end\ | |
331 | end\ | |
332 | local opairs\ | |
333 | opairs = function(t, order_fn, only_strings)\ | |
334 | if only_strings == nil then\ | |
335 | only_strings = false\ | |
336 | end\ | |
337 | local state = {\ | |
338 | t = t,\ | |
339 | i = 0,\ | |
340 | step = 1,\ | |
341 | index = sort(t, order_fn, only_strings)\ | |
342 | }\ | |
343 | return onext, state, nil\ | |
344 | end\ | |
345 | local ropairs\ | |
346 | ropairs = function(t, order_fn, only_strings)\ | |
347 | if only_strings == nil then\ | |
348 | only_strings = false\ | |
349 | end\ | |
350 | local index = sort(t, order_fn, only_strings)\ | |
351 | local state = {\ | |
352 | t = t,\ | |
353 | index = index,\ | |
354 | i = #index + 1,\ | |
355 | step = -1\ | |
356 | }\ | |
357 | return onext, state, nil\ | |
358 | end\ | |
359 | return {\ | |
360 | onext = onext,\ | |
361 | opairs = opairs,\ | |
362 | ropairs = ropairs\ | |
363 | }\ | |
364 | ", "mmm/ordered.lua") end | |
365 | if not p["mmm.mmmfs.browser"] then p["mmm.mmmfs.browser"] = load("local require = relative(..., 1)\ | |
366 | local Key\ | |
367 | Key = require('.fileder').Key\ | |
368 | local converts, get_conversions, apply_conversions\ | |
369 | do\ | |
370 | local _obj_0 = require('.conversion')\ | |
371 | converts, get_conversions, apply_conversions = _obj_0.converts, _obj_0.get_conversions, _obj_0.apply_conversions\ | |
372 | end\ | |
373 | local ReactiveVar, get_or_create, text, elements\ | |
374 | do\ | |
375 | local _obj_0 = require('mmm.component')\ | |
376 | ReactiveVar, get_or_create, text, elements = _obj_0.ReactiveVar, _obj_0.get_or_create, _obj_0.text, _obj_0.elements\ | |
377 | end\ | |
378 | local pre, div, nav, span, button, a, code, select, option\ | |
379 | pre, div, nav, span, button, a, code, select, option = elements.pre, elements.div, elements.nav, elements.span, elements.button, elements.a, elements.code, elements.select, elements.option\ | |
380 | local languages\ | |
381 | languages = require('mmm.highlighting').languages\ | |
382 | local keep\ | |
383 | keep = function(var)\ | |
384 | local last = var:get()\ | |
385 | return var:map(function(val)\ | |
386 | last = val or last\ | |
387 | return last\ | |
388 | end)\ | |
389 | end\ | |
390 | local code_cast\ | |
391 | code_cast = function(lang)\ | |
392 | return {\ | |
393 | inp = \"text/\" .. tostring(lang) .. \".*\",\ | |
394 | out = 'mmm/dom',\ | |
395 | transform = function(val)\ | |
396 | return languages[lang](val)\ | |
397 | end\ | |
398 | }\ | |
399 | end\ | |
400 | local casts = {\ | |
401 | code_cast('javascript'),\ | |
402 | code_cast('moonscript'),\ | |
403 | code_cast('lua'),\ | |
404 | code_cast('markdown'),\ | |
405 | code_cast('html'),\ | |
406 | {\ | |
407 | inp = 'text/plain',\ | |
408 | out = 'mmm/dom',\ | |
409 | transform = function(val)\ | |
410 | return text(val)\ | |
411 | end\ | |
412 | },\ | |
413 | {\ | |
414 | inp = 'URL.*',\ | |
415 | out = 'mmm/dom',\ | |
416 | transform = function(href)\ | |
417 | return span(a((code(href)), {\ | |
418 | href = href\ | |
419 | }))\ | |
420 | end\ | |
421 | }\ | |
422 | }\ | |
423 | for _index_0 = 1, #converts do\ | |
424 | local convert = converts[_index_0]\ | |
425 | table.insert(casts, convert)\ | |
426 | end\ | |
427 | local Browser\ | |
428 | do\ | |
429 | local _class_0\ | |
430 | local err_and_trace, default_convert\ | |
431 | local _base_0 = {\ | |
432 | get_content = function(self, prop, err, convert)\ | |
433 | if err == nil then\ | |
434 | err = self.error\ | |
435 | end\ | |
436 | if convert == nil then\ | |
437 | convert = default_convert\ | |
438 | end\ | |
439 | local clear_error\ | |
440 | clear_error = function()\ | |
441 | if MODE == 'CLIENT' then\ | |
442 | return err:set()\ | |
443 | end\ | |
444 | end\ | |
445 | local disp_error\ | |
446 | disp_error = function(msg)\ | |
447 | if MODE == 'CLIENT' then\ | |
448 | err:set(pre(msg))\ | |
449 | end\ | |
450 | warn(\"ERROR rendering content: \" .. tostring(msg))\ | |
451 | return nil\ | |
452 | end\ | |
453 | local active = self.active:get()\ | |
454 | if not (active) then\ | |
455 | return disp_error(\"fileder not found!\")\ | |
456 | end\ | |
457 | if not (prop) then\ | |
458 | return disp_error(\"facet not found!\")\ | |
459 | end\ | |
460 | local ok, res = xpcall(convert, err_and_trace, active, prop)\ | |
461 | if MODE == 'CLIENT' then\ | |
462 | document.body.classList:remove('loading')\ | |
463 | end\ | |
464 | if ok and res then\ | |
465 | clear_error()\ | |
466 | return res\ | |
467 | elseif ok then\ | |
468 | return div(\"[no conversion path to \" .. tostring(prop.type) .. \"]\")\ | |
469 | elseif res and res.msg.match and res.msg:match('%[nossr%]$') then\ | |
470 | return div(\"[this page could not be pre-rendered on the server]\")\ | |
471 | else\ | |
472 | res = tostring(res.msg) .. \"\\n\" .. tostring(res.trace)\ | |
473 | return disp_error(res)\ | |
474 | end\ | |
475 | end,\ | |
476 | get_inspector = function(self)\ | |
477 | self.inspect_prop = self.facet:map(function(prop)\ | |
478 | local active = self.active:get()\ | |
479 | local key = active and active:find(prop)\ | |
480 | if key and key.original then\ | |
481 | key = key.original\ | |
482 | end\ | |
483 | return key\ | |
484 | end)\ | |
485 | self.inspect_err = ReactiveVar()\ | |
486 | do\ | |
487 | local _with_0 = div({\ | |
488 | class = 'view inspector'\ | |
489 | })\ | |
490 | _with_0:append(nav({\ | |
491 | span('inspector'),\ | |
492 | self.inspect_prop:map(function(current)\ | |
493 | current = current and current:tostring()\ | |
494 | local fileder = self.active:get()\ | |
495 | local onchange\ | |
496 | onchange = function(_, e)\ | |
497 | if e.target.value == '' then\ | |
498 | return \ | |
499 | end\ | |
500 | local name\ | |
501 | name = self.facet:get().name\ | |
502 | return self.inspect_prop:set(Key(e.target.value))\ | |
503 | end\ | |
504 | do\ | |
505 | local _with_1 = select({\ | |
506 | onchange = onchange\ | |
507 | })\ | |
508 | _with_1:append(option('(none)', {\ | |
509 | value = '',\ | |
510 | disabled = true,\ | |
511 | selected = not value\ | |
512 | }))\ | |
513 | if fileder then\ | |
514 | for key, _ in pairs(fileder.facets) do\ | |
515 | local value = key:tostring()\ | |
516 | _with_1:append(option(value, {\ | |
517 | value = value,\ | |
518 | selected = value == current\ | |
519 | }))\ | |
520 | end\ | |
521 | end\ | |
522 | return _with_1\ | |
523 | end\ | |
524 | end),\ | |
525 | self.inspect:map(function(enabled)\ | |
526 | if enabled then\ | |
527 | return button('close', {\ | |
528 | onclick = function(_, e)\ | |
529 | return self.inspect:set(false)\ | |
530 | end\ | |
531 | })\ | |
532 | end\ | |
533 | end)\ | |
534 | }))\ | |
535 | _with_0:append((function()\ | |
536 | do\ | |
537 | local _with_1 = div({\ | |
538 | class = self.inspect_err:map(function(e)\ | |
539 | if e then\ | |
540 | return 'error-wrap'\ | |
541 | else\ | |
542 | return 'error-wrap empty'\ | |
543 | end\ | |
544 | end)\ | |
545 | })\ | |
546 | _with_1:append(span(\"an error occured while rendering this view:\"))\ | |
547 | _with_1:append(self.inspect_err)\ | |
548 | return _with_1\ | |
549 | end\ | |
550 | end)())\ | |
551 | _with_0:append((function()\ | |
552 | do\ | |
553 | local _with_1 = pre({\ | |
554 | class = 'content'\ | |
555 | })\ | |
556 | _with_1:append(keep(self.inspect_prop:map(function(prop, old)\ | |
557 | return self:get_content(prop, self.inspect_err, function(self, prop)\ | |
558 | local value, key = self:get(prop)\ | |
559 | assert(key, \"couldn't @get \" .. tostring(prop))\ | |
560 | local conversions = get_conversions('mmm/dom', key.type, casts)\ | |
561 | assert(conversions, \"cannot cast '\" .. tostring(key.type) .. \"'\")\ | |
562 | return apply_conversions(conversions, value, self, prop)\ | |
563 | end)\ | |
564 | end)))\ | |
565 | return _with_1\ | |
566 | end\ | |
567 | end)())\ | |
568 | return _with_0\ | |
569 | end\ | |
570 | end,\ | |
571 | navigate = function(self, new)\ | |
572 | return self.path:set(new)\ | |
573 | end\ | |
574 | }\ | |
575 | _base_0.__index = _base_0\ | |
576 | _class_0 = setmetatable({\ | |
577 | __init = function(self, root, path, rehydrate)\ | |
578 | if rehydrate == nil then\ | |
579 | rehydrate = false\ | |
580 | end\ | |
581 | self.root = root\ | |
582 | assert(self.root, 'root fileder is nil')\ | |
583 | self.path = ReactiveVar(path or '')\ | |
584 | if MODE == 'CLIENT' then\ | |
585 | local logo = document:querySelector('header h1 > svg')\ | |
586 | local spin\ | |
587 | spin = function()\ | |
588 | logo.classList:add('spin')\ | |
589 | local _ = logo.parentElement.offsetWidth\ | |
590 | return logo.classList:remove('spin')\ | |
591 | end\ | |
592 | self.path:subscribe(function(path)\ | |
593 | document.body.classList:add('loading')\ | |
594 | spin()\ | |
595 | if self.skip then\ | |
596 | return \ | |
597 | end\ | |
598 | local vis_path = path .. ((function()\ | |
599 | if '/' == path:sub(-1) then\ | |
600 | return ''\ | |
601 | else\ | |
602 | return '/'\ | |
603 | end\ | |
604 | end)())\ | |
605 | return window.history:pushState(path, '', vis_path)\ | |
606 | end)\ | |
607 | window.onpopstate = function(_, event)\ | |
608 | if event.state and not event.state == js.null then\ | |
609 | self.skip = true\ | |
610 | self.path:set(event.state)\ | |
611 | self.skip = nil\ | |
612 | end\ | |
613 | end\ | |
614 | end\ | |
615 | self.active = self.path:map((function()\ | |
616 | local _base_1 = self.root\ | |
617 | local _fn_0 = _base_1.walk\ | |
618 | return function(...)\ | |
619 | return _fn_0(_base_1, ...)\ | |
620 | end\ | |
621 | end)())\ | |
622 | self.facet = self.active:map(function(fileder)\ | |
623 | if not (fileder) then\ | |
624 | return \ | |
625 | end\ | |
626 | local last = self.facet and self.facet:get()\ | |
627 | return Key((function()\ | |
628 | if last then\ | |
629 | return last.type\ | |
630 | else\ | |
631 | return 'mmm/dom'\ | |
632 | end\ | |
633 | end)())\ | |
634 | end)\ | |
635 | self.inspect = ReactiveVar((MODE == 'CLIENT' and window.location.search:match('[?&]inspect')))\ | |
636 | local main = get_or_create('div', 'browser-root', {\ | |
637 | class = 'main view'\ | |
638 | })\ | |
639 | if MODE == 'SERVER' then\ | |
640 | main:append(nav({\ | |
641 | id = 'browser-navbar',\ | |
642 | span('please stand by... interactivity loading :)')\ | |
643 | }))\ | |
644 | else\ | |
645 | main:prepend((function()\ | |
646 | do\ | |
647 | local _with_0 = get_or_create('nav', 'browser-navbar')\ | |
648 | _with_0.node.innerHTML = ''\ | |
649 | _with_0:append(span('path: ', self.path:map(function(path)\ | |
650 | do\ | |
651 | local _with_1 = div({\ | |
652 | class = 'path',\ | |
653 | style = {\ | |
654 | display = 'inline-block'\ | |
655 | }\ | |
656 | })\ | |
657 | local path_segment\ | |
658 | path_segment = function(name, href)\ | |
659 | return a(name, {\ | |
660 | href = href,\ | |
661 | onclick = function(_, e)\ | |
662 | e:preventDefault()\ | |
663 | return self:navigate(href)\ | |
664 | end\ | |
665 | })\ | |
666 | end\ | |
667 | local href = ''\ | |
668 | path = path:match('^/(.*)')\ | |
669 | _with_1:append(path_segment('root', ''))\ | |
670 | while path do\ | |
671 | local name, rest = path:match('^([%w%-_%.]+)/(.*)')\ | |
672 | if not name then\ | |
673 | name = path\ | |
674 | end\ | |
675 | path = rest\ | |
676 | href = tostring(href) .. \"/\" .. tostring(name)\ | |
677 | _with_1:append('/')\ | |
678 | _with_1:append(path_segment(name, href))\ | |
679 | end\ | |
680 | return _with_1\ | |
681 | end\ | |
682 | end)))\ | |
683 | _with_0:append(span('view facet:', {\ | |
684 | style = {\ | |
685 | ['margin-right'] = '0'\ | |
686 | }\ | |
687 | }))\ | |
688 | _with_0:append(self.active:map(function(fileder)\ | |
689 | local onchange\ | |
690 | onchange = function(_, e)\ | |
691 | local type\ | |
692 | type = self.facet:get().type\ | |
693 | return self.facet:set(Key({\ | |
694 | name = e.target.value,\ | |
695 | type = type\ | |
696 | }))\ | |
697 | end\ | |
698 | local current = self.facet:get()\ | |
699 | current = current and current.name\ | |
700 | do\ | |
701 | local _with_1 = select({\ | |
702 | onchange = onchange,\ | |
703 | disabled = not fileder\ | |
704 | })\ | |
705 | local has_main = fileder and fileder:find(current.name, '.*')\ | |
706 | _with_1:append(option('(main)', {\ | |
707 | value = '',\ | |
708 | disabled = not has_main,\ | |
709 | selected = current == ''\ | |
710 | }))\ | |
711 | if fileder then\ | |
712 | for i, value in ipairs(fileder:get_facets()) do\ | |
713 | local _continue_0 = false\ | |
714 | repeat\ | |
715 | if value == '' then\ | |
716 | _continue_0 = true\ | |
717 | break\ | |
718 | end\ | |
719 | _with_1:append(option(value, {\ | |
720 | value = value,\ | |
721 | selected = value == current\ | |
722 | }))\ | |
723 | _continue_0 = true\ | |
724 | until true\ | |
725 | if not _continue_0 then\ | |
726 | break\ | |
727 | end\ | |
728 | end\ | |
729 | end\ | |
730 | return _with_1\ | |
731 | end\ | |
732 | end))\ | |
733 | _with_0:append(self.inspect:map(function(enabled)\ | |
734 | if not enabled then\ | |
735 | return button('inspect', {\ | |
736 | onclick = function(_, e)\ | |
737 | return self.inspect:set(true)\ | |
738 | end\ | |
739 | })\ | |
740 | end\ | |
741 | end))\ | |
742 | return _with_0\ | |
743 | end\ | |
744 | end)())\ | |
745 | end\ | |
746 | self.error = ReactiveVar()\ | |
747 | main:append((function()\ | |
748 | do\ | |
749 | local _with_0 = get_or_create('div', 'browser-error', {\ | |
750 | class = self.error:map(function(e)\ | |
751 | if e then\ | |
752 | return 'error-wrap'\ | |
753 | else\ | |
754 | return 'error-wrap empty'\ | |
755 | end\ | |
756 | end)\ | |
757 | })\ | |
758 | _with_0:append((span(\"an error occured while rendering this view:\")), (rehydrate and _with_0.node.firstChild))\ | |
759 | _with_0:append(self.error)\ | |
760 | return _with_0\ | |
761 | end\ | |
762 | end)())\ | |
763 | main:append((function()\ | |
764 | do\ | |
765 | local _with_0 = get_or_create('div', 'browser-content', {\ | |
766 | class = 'content'\ | |
767 | })\ | |
768 | local content = ReactiveVar((function()\ | |
769 | if rehydrate then\ | |
770 | return _with_0.node.lastChild\ | |
771 | else\ | |
772 | return self:get_content(self.facet:get())\ | |
773 | end\ | |
774 | end)())\ | |
775 | _with_0:append(keep(content))\ | |
776 | if MODE == 'CLIENT' then\ | |
777 | self.facet:subscribe(function(p)\ | |
778 | return window:setTimeout((function()\ | |
779 | return content:set(self:get_content(p))\ | |
780 | end), 150)\ | |
781 | end)\ | |
782 | end\ | |
783 | return _with_0\ | |
784 | end\ | |
785 | end)())\ | |
786 | if rehydrate then\ | |
787 | self.facet:set(self.facet:get())\ | |
788 | end\ | |
789 | local inspector = self.inspect:map(function(enabled)\ | |
790 | if enabled then\ | |
791 | return self:get_inspector()\ | |
792 | end\ | |
793 | end)\ | |
794 | local wrapper = get_or_create('div', 'browser-wrapper', main, inspector, {\ | |
795 | class = 'browser'\ | |
796 | })\ | |
797 | self.node = wrapper.node\ | |
798 | do\ | |
799 | local _base_1 = wrapper\ | |
800 | local _fn_0 = _base_1.render\ | |
801 | self.render = function(...)\ | |
802 | return _fn_0(_base_1, ...)\ | |
803 | end\ | |
804 | end\ | |
805 | end,\ | |
806 | __base = _base_0,\ | |
807 | __name = \"Browser\"\ | |
808 | }, {\ | |
809 | __index = _base_0,\ | |
810 | __call = function(cls, ...)\ | |
811 | local _self_0 = setmetatable({}, _base_0)\ | |
812 | cls.__init(_self_0, ...)\ | |
813 | return _self_0\ | |
814 | end\ | |
815 | })\ | |
816 | _base_0.__class = _class_0\ | |
817 | local self = _class_0\ | |
818 | err_and_trace = function(msg)\ | |
819 | return {\ | |
820 | msg = msg,\ | |
821 | trace = debug.traceback()\ | |
822 | }\ | |
823 | end\ | |
824 | default_convert = function(self, key)\ | |
825 | return self:get(key.name, 'mmm/dom')\ | |
826 | end\ | |
827 | default_convert = function(self, key)\ | |
828 | return self:get(key.name, 'mmm/dom')\ | |
829 | end\ | |
830 | Browser = _class_0\ | |
831 | end\ | |
832 | return {\ | |
833 | Browser = Browser\ | |
834 | }\ | |
835 | ", "mmm/mmmfs/browser.lua") end | |
836 | if not p["mmm.mmmfs.conversion"] then p["mmm.mmmfs.conversion"] = load("local require = relative(..., 1)\ | |
837 | local converts = require('.converts')\ | |
838 | local count\ | |
839 | count = function(base, pattern)\ | |
840 | if pattern == nil then\ | |
841 | pattern = '->'\ | |
842 | end\ | |
843 | return select(2, base:gsub(pattern, ''))\ | |
844 | end\ | |
845 | local escape_pattern\ | |
846 | escape_pattern = function(inp)\ | |
847 | return \"^\" .. tostring(inp:gsub('([^%w])', '%%%1')) .. \"$\"\ | |
848 | end\ | |
849 | local escape_inp\ | |
850 | escape_inp = function(inp)\ | |
851 | return \"^\" .. tostring(inp:gsub('([-/])', '%%%1')) .. \"$\"\ | |
852 | end\ | |
853 | local get_conversions\ | |
854 | get_conversions = function(want, have, _converts, limit)\ | |
855 | if _converts == nil then\ | |
856 | _converts = converts\ | |
857 | end\ | |
858 | if limit == nil then\ | |
859 | limit = 5\ | |
860 | end\ | |
861 | assert(have, 'need starting type(s)')\ | |
862 | if 'string' == type(have) then\ | |
863 | have = {\ | |
864 | have\ | |
865 | }\ | |
866 | end\ | |
867 | assert(#have > 0, 'need starting type(s) (list was empty)')\ | |
868 | want = escape_pattern(want)\ | |
869 | local iterations = limit + math.max(table.unpack((function()\ | |
870 | local _accum_0 = { }\ | |
871 | local _len_0 = 1\ | |
872 | for _index_0 = 1, #have do\ | |
873 | local type = have[_index_0]\ | |
874 | _accum_0[_len_0] = count(type)\ | |
875 | _len_0 = _len_0 + 1\ | |
876 | end\ | |
877 | return _accum_0\ | |
878 | end)()))\ | |
879 | do\ | |
880 | local _accum_0 = { }\ | |
881 | local _len_0 = 1\ | |
882 | for _index_0 = 1, #have do\ | |
883 | local start = have[_index_0]\ | |
884 | _accum_0[_len_0] = {\ | |
885 | start = start,\ | |
886 | rest = start,\ | |
887 | conversions = { }\ | |
888 | }\ | |
889 | _len_0 = _len_0 + 1\ | |
890 | end\ | |
891 | have = _accum_0\ | |
892 | end\ | |
893 | for i = 1, iterations do\ | |
894 | local next_have, c = { }, 1\ | |
895 | for _index_0 = 1, #have do\ | |
896 | local _des_0 = have[_index_0]\ | |
897 | local start, rest, conversions\ | |
898 | start, rest, conversions = _des_0.start, _des_0.rest, _des_0.conversions\ | |
899 | if rest:match(want) then\ | |
900 | return conversions, start\ | |
901 | else\ | |
902 | for _index_1 = 1, #_converts do\ | |
903 | local _continue_0 = false\ | |
904 | repeat\ | |
905 | local convert = _converts[_index_1]\ | |
906 | local inp = escape_inp(convert.inp)\ | |
907 | if not (rest:match(inp)) then\ | |
908 | _continue_0 = true\ | |
909 | break\ | |
910 | end\ | |
911 | local result = rest:gsub(inp, convert.out)\ | |
912 | if result then\ | |
913 | next_have[c] = {\ | |
914 | start = start,\ | |
915 | rest = result,\ | |
916 | conversions = {\ | |
917 | {\ | |
918 | convert = convert,\ | |
919 | from = rest,\ | |
920 | to = result\ | |
921 | },\ | |
922 | table.unpack(conversions)\ | |
923 | }\ | |
924 | }\ | |
925 | c = c + 1\ | |
926 | end\ | |
927 | _continue_0 = true\ | |
928 | until true\ | |
929 | if not _continue_0 then\ | |
930 | break\ | |
931 | end\ | |
932 | end\ | |
933 | end\ | |
934 | end\ | |
935 | have = next_have\ | |
936 | if not (#have > 0) then\ | |
937 | return \ | |
938 | end\ | |
939 | end\ | |
940 | end\ | |
941 | local apply_conversions\ | |
942 | apply_conversions = function(conversions, value, ...)\ | |
943 | for i = #conversions, 1, -1 do\ | |
944 | local step = conversions[i]\ | |
945 | value = step.convert.transform(step, value, ...)\ | |
946 | end\ | |
947 | return value\ | |
948 | end\ | |
949 | return {\ | |
950 | converts = converts,\ | |
951 | get_conversions = get_conversions,\ | |
952 | apply_conversions = apply_conversions\ | |
953 | }\ | |
954 | ", "mmm/mmmfs/conversion.lua") end | |
955 | if not p["mmm.mmmfs.fileder"] then p["mmm.mmmfs.fileder"] = load("local require = relative(..., 1)\ | |
956 | local get_conversions, apply_conversions\ | |
957 | do\ | |
958 | local _obj_0 = require('.conversion')\ | |
959 | get_conversions, apply_conversions = _obj_0.get_conversions, _obj_0.apply_conversions\ | |
960 | end\ | |
961 | local Key\ | |
962 | do\ | |
963 | local _class_0\ | |
964 | local _base_0 = {\ | |
965 | tostring = function(self)\ | |
966 | if self.name == '' then\ | |
967 | return self.type\ | |
968 | else\ | |
969 | return tostring(self.name) .. \": \" .. tostring(self.type)\ | |
970 | end\ | |
971 | end,\ | |
972 | __tostring = function(self)\ | |
973 | return self:tostring()\ | |
974 | end\ | |
975 | }\ | |
976 | _base_0.__index = _base_0\ | |
977 | _class_0 = setmetatable({\ | |
978 | __init = function(self, opts, second)\ | |
979 | if 'string' == type(second) then\ | |
980 | self.name, self.type = (opts or ''), second\ | |
981 | elseif 'string' == type(opts) then\ | |
982 | self.name, self.type = opts:match('^([%w-_]+): *(.+)$')\ | |
983 | if not self.name then\ | |
984 | self.name = ''\ | |
985 | self.type = opts\ | |
986 | end\ | |
987 | elseif 'table' == type(opts) then\ | |
988 | self.name = opts.name\ | |
989 | self.type = opts.type\ | |
990 | self.original = opts.original\ | |
991 | self.filename = opts.filename\ | |
992 | else\ | |
993 | return error(\"wrong argument type: \" .. tostring(type(opts)) .. \", \" .. tostring(type(second)))\ | |
994 | end\ | |
995 | end,\ | |
996 | __base = _base_0,\ | |
997 | __name = \"Key\"\ | |
998 | }, {\ | |
999 | __index = _base_0,\ | |
1000 | __call = function(cls, ...)\ | |
1001 | local _self_0 = setmetatable({}, _base_0)\ | |
1002 | cls.__init(_self_0, ...)\ | |
1003 | return _self_0\ | |
1004 | end\ | |
1005 | })\ | |
1006 | _base_0.__class = _class_0\ | |
1007 | Key = _class_0\ | |
1008 | end\ | |
1009 | local Fileder\ | |
1010 | do\ | |
1011 | local _class_0\ | |
1012 | local _base_0 = {\ | |
1013 | walk = function(self, path)\ | |
1014 | if path == '' then\ | |
1015 | return self\ | |
1016 | end\ | |
1017 | if '/' ~= path:sub(1, 1) then\ | |
1018 | path = tostring(self.path) .. \"/\" .. tostring(path)\ | |
1019 | end\ | |
1020 | if not (self.path == path:sub(1, #self.path)) then\ | |
1021 | return \ | |
1022 | end\ | |
1023 | if #path == #self.path then\ | |
1024 | return self\ | |
1025 | end\ | |
1026 | local _list_0 = self.children\ | |
1027 | for _index_0 = 1, #_list_0 do\ | |
1028 | local child = _list_0[_index_0]\ | |
1029 | do\ | |
1030 | local match = child:walk(path)\ | |
1031 | if match then\ | |
1032 | return match\ | |
1033 | end\ | |
1034 | end\ | |
1035 | end\ | |
1036 | end,\ | |
1037 | mount = function(self, path, mount_as)\ | |
1038 | if not mount_as then\ | |
1039 | path = path .. self:gett('name: alpha')\ | |
1040 | end\ | |
1041 | assert(not self.path or self.path == path, \"mounted twice: \" .. tostring(self.path) .. \" and now \" .. tostring(path))\ | |
1042 | self.path = path\ | |
1043 | local _list_0 = self.children\ | |
1044 | for _index_0 = 1, #_list_0 do\ | |
1045 | local child = _list_0[_index_0]\ | |
1046 | child:mount(self.path .. '/')\ | |
1047 | end\ | |
1048 | end,\ | |
1049 | iterate = function(self, depth)\ | |
1050 | if depth == nil then\ | |
1051 | depth = 0\ | |
1052 | end\ | |
1053 | coroutine.yield(self)\ | |
1054 | if depth == 1 then\ | |
1055 | return \ | |
1056 | end\ | |
1057 | local _list_0 = self.children\ | |
1058 | for _index_0 = 1, #_list_0 do\ | |
1059 | local child = _list_0[_index_0]\ | |
1060 | child:iterate(depth - 1)\ | |
1061 | end\ | |
1062 | end,\ | |
1063 | get_facets = function(self)\ | |
1064 | local names = { }\ | |
1065 | for key in pairs(self.facets) do\ | |
1066 | names[key.name] = true\ | |
1067 | end\ | |
1068 | local _accum_0 = { }\ | |
1069 | local _len_0 = 1\ | |
1070 | for name in pairs(names) do\ | |
1071 | _accum_0[_len_0] = name\ | |
1072 | _len_0 = _len_0 + 1\ | |
1073 | end\ | |
1074 | return _accum_0\ | |
1075 | end,\ | |
1076 | has = function(self, ...)\ | |
1077 | local want = Key(...)\ | |
1078 | for key in pairs(self.facets) do\ | |
1079 | local _continue_0 = false\ | |
1080 | repeat\ | |
1081 | if key.original then\ | |
1082 | _continue_0 = true\ | |
1083 | break\ | |
1084 | end\ | |
1085 | if key.name == want.name and key.type == want.type then\ | |
1086 | return key\ | |
1087 | end\ | |
1088 | _continue_0 = true\ | |
1089 | until true\ | |
1090 | if not _continue_0 then\ | |
1091 | break\ | |
1092 | end\ | |
1093 | end\ | |
1094 | end,\ | |
1095 | has_facet = function(self, want)\ | |
1096 | for key in pairs(self.facets) do\ | |
1097 | local _continue_0 = false\ | |
1098 | repeat\ | |
1099 | if key.original then\ | |
1100 | _continue_0 = true\ | |
1101 | break\ | |
1102 | end\ | |
1103 | if key.name == want then\ | |
1104 | return key\ | |
1105 | end\ | |
1106 | _continue_0 = true\ | |
1107 | until true\ | |
1108 | if not _continue_0 then\ | |
1109 | break\ | |
1110 | end\ | |
1111 | end\ | |
1112 | end,\ | |
1113 | find = function(self, ...)\ | |
1114 | local want = Key(...)\ | |
1115 | local matching\ | |
1116 | do\ | |
1117 | local _accum_0 = { }\ | |
1118 | local _len_0 = 1\ | |
1119 | for key in pairs(self.facets) do\ | |
1120 | if key.name == want.name then\ | |
1121 | _accum_0[_len_0] = key\ | |
1122 | _len_0 = _len_0 + 1\ | |
1123 | end\ | |
1124 | end\ | |
1125 | matching = _accum_0\ | |
1126 | end\ | |
1127 | if not (#matching > 0) then\ | |
1128 | return \ | |
1129 | end\ | |
1130 | local shortest_path, start = get_conversions(want.type, (function()\ | |
1131 | local _accum_0 = { }\ | |
1132 | local _len_0 = 1\ | |
1133 | for _index_0 = 1, #matching do\ | |
1134 | local key = matching[_index_0]\ | |
1135 | _accum_0[_len_0] = key.type\ | |
1136 | _len_0 = _len_0 + 1\ | |
1137 | end\ | |
1138 | return _accum_0\ | |
1139 | end)())\ | |
1140 | if start then\ | |
1141 | for _index_0 = 1, #matching do\ | |
1142 | local key = matching[_index_0]\ | |
1143 | if key.type == start then\ | |
1144 | return key, shortest_path\ | |
1145 | end\ | |
1146 | end\ | |
1147 | return error(\"couldn't find key after resolution?\")\ | |
1148 | end\ | |
1149 | end,\ | |
1150 | get = function(self, ...)\ | |
1151 | local want = Key(...)\ | |
1152 | local key, conversions = self:find(want)\ | |
1153 | if key then\ | |
1154 | local value = apply_conversions(conversions, self.facets[key], self, key)\ | |
1155 | return value, key\ | |
1156 | end\ | |
1157 | end,\ | |
1158 | gett = function(self, ...)\ | |
1159 | local want = Key(...)\ | |
1160 | local value, key = self:get(want)\ | |
1161 | assert(value, tostring(self) .. \" doesn't have value for '\" .. tostring(want) .. \"'\")\ | |
1162 | return value, key\ | |
1163 | end,\ | |
1164 | __tostring = function(self)\ | |
1165 | return \"Fileder:\" .. tostring(self.path)\ | |
1166 | end\ | |
1167 | }\ | |
1168 | _base_0.__index = _base_0\ | |
1169 | _class_0 = setmetatable({\ | |
1170 | __init = function(self, facets, children)\ | |
1171 | if not children then\ | |
1172 | do\ | |
1173 | local _accum_0 = { }\ | |
1174 | local _len_0 = 1\ | |
1175 | for i, child in ipairs(facets) do\ | |
1176 | facets[i] = nil\ | |
1177 | local _value_0 = child\ | |
1178 | _accum_0[_len_0] = _value_0\ | |
1179 | _len_0 = _len_0 + 1\ | |
1180 | end\ | |
1181 | children = _accum_0\ | |
1182 | end\ | |
1183 | end\ | |
1184 | self.children = setmetatable({ }, {\ | |
1185 | __index = function(t, k)\ | |
1186 | if not ('string' == type(k)) then\ | |
1187 | return rawget(t, k)\ | |
1188 | end\ | |
1189 | return self:walk(tostring(self.path) .. \"/\" .. tostring(k))\ | |
1190 | end,\ | |
1191 | __newindex = function(t, k, child)\ | |
1192 | rawset(t, k, child)\ | |
1193 | if self.path == '/' then\ | |
1194 | return child:mount('/')\ | |
1195 | elseif self.path then\ | |
1196 | return child:mount(self.path .. '/')\ | |
1197 | end\ | |
1198 | end\ | |
1199 | })\ | |
1200 | for i, child in ipairs(children) do\ | |
1201 | self.children[i] = child\ | |
1202 | end\ | |
1203 | self.facets = setmetatable({ }, {\ | |
1204 | __newindex = function(t, key, v)\ | |
1205 | return rawset(t, (Key(key)), v)\ | |
1206 | end\ | |
1207 | })\ | |
1208 | for k, v in pairs(facets) do\ | |
1209 | self.facets[k] = v\ | |
1210 | end\ | |
1211 | end,\ | |
1212 | __base = _base_0,\ | |
1213 | __name = \"Fileder\"\ | |
1214 | }, {\ | |
1215 | __index = _base_0,\ | |
1216 | __call = function(cls, ...)\ | |
1217 | local _self_0 = setmetatable({}, _base_0)\ | |
1218 | cls.__init(_self_0, ...)\ | |
1219 | return _self_0\ | |
1220 | end\ | |
1221 | })\ | |
1222 | _base_0.__class = _class_0\ | |
1223 | Fileder = _class_0\ | |
1224 | end\ | |
1225 | local dir_base\ | |
1226 | dir_base = function(path)\ | |
1227 | local dir, base = path:match('(.-)([^/]-)$')\ | |
1228 | if dir and #dir > 0 then\ | |
1229 | dir = dir:sub(1, #dir - 1)\ | |
1230 | end\ | |
1231 | return dir, base\ | |
1232 | end\ | |
1233 | local load_tree\ | |
1234 | load_tree = function(store, root)\ | |
1235 | if root == nil then\ | |
1236 | root = ''\ | |
1237 | end\ | |
1238 | local fileders = setmetatable({ }, {\ | |
1239 | __index = function(self, path)\ | |
1240 | do\ | |
1241 | local val = Fileder({ })\ | |
1242 | val.path = path\ | |
1243 | rawset(self, path, val)\ | |
1244 | return val\ | |
1245 | end\ | |
1246 | end\ | |
1247 | })\ | |
1248 | root = fileders[root]\ | |
1249 | root.facets['name: alpha'] = ''\ | |
1250 | for fn, ft in store:list_facets(root.path) do\ | |
1251 | local val = store:load_facet(root.path, fn, ft)\ | |
1252 | root.facets[Key(fn, ft)] = val\ | |
1253 | end\ | |
1254 | for path in store:list_all_fileders(root.path) do\ | |
1255 | local fileder = fileders[path]\ | |
1256 | local parent, name = dir_base(path)\ | |
1257 | fileder.facets['name: alpha'] = name\ | |
1258 | table.insert(fileders[parent].children, fileder)\ | |
1259 | for fn, ft in store:list_facets(path) do\ | |
1260 | local val = store:load_facet(path, fn, ft)\ | |
1261 | fileder.facets[Key(fn, ft)] = val\ | |
1262 | end\ | |
1263 | end\ | |
1264 | return root\ | |
1265 | end\ | |
1266 | return {\ | |
1267 | Key = Key,\ | |
1268 | Fileder = Fileder,\ | |
1269 | dir_base = dir_base,\ | |
1270 | load_tree = load_tree\ | |
1271 | }\ | |
1272 | ", "mmm/mmmfs/fileder.lua") end | |
1273 | if not p["mmm.mmmfs"] then p["mmm.mmmfs"] = load("local require = relative(...)\ | |
1274 | local Key, Fileder\ | |
1275 | do\ | |
1276 | local _obj_0 = require('.fileder')\ | |
1277 | Key, Fileder = _obj_0.Key, _obj_0.Fileder\ | |
1278 | end\ | |
1279 | local Browser\ | |
1280 | Browser = require('.browser').Browser\ | |
1281 | return {\ | |
1282 | Key = Key,\ | |
1283 | Fileder = Fileder,\ | |
1284 | Browser = Browser\ | |
1285 | }\ | |
1286 | ", "mmm/mmmfs/init.lua") end | |
1287 | if not p["mmm.mmmfs.util"] then p["mmm.mmmfs.util"] = load("local merge\ | |
1288 | merge = function(orig, extra)\ | |
1289 | if orig == nil then\ | |
1290 | orig = { }\ | |
1291 | end\ | |
1292 | do\ | |
1293 | local attr\ | |
1294 | do\ | |
1295 | local _tbl_0 = { }\ | |
1296 | for k, v in pairs(orig) do\ | |
1297 | _tbl_0[k] = v\ | |
1298 | end\ | |
1299 | attr = _tbl_0\ | |
1300 | end\ | |
1301 | for k, v in pairs(extra) do\ | |
1302 | attr[k] = v\ | |
1303 | end\ | |
1304 | return attr\ | |
1305 | end\ | |
1306 | end\ | |
1307 | local tourl\ | |
1308 | tourl = function(path)\ | |
1309 | if STATIC then\ | |
1310 | return path .. '/'\ | |
1311 | else\ | |
1312 | return path .. '/'\ | |
1313 | end\ | |
1314 | end\ | |
1315 | return function(elements)\ | |
1316 | local a, div, pre\ | |
1317 | a, div, pre = elements.a, elements.div, elements.pre\ | |
1318 | local find_fileder\ | |
1319 | find_fileder = function(fileder, origin)\ | |
1320 | if 'string' == type(fileder) then\ | |
1321 | assert(origin, \"cannot resolve path '\" .. tostring(fileder) .. \"' without origin!\")\ | |
1322 | return assert((origin:walk(fileder)), \"couldn't resolve path '\" .. tostring(fileder) .. \"' from \" .. tostring(origin))\ | |
1323 | else\ | |
1324 | return assert(fileder, \"no fileder passed.\")\ | |
1325 | end\ | |
1326 | end\ | |
1327 | local navigate_to\ | |
1328 | navigate_to = function(path, name, opts)\ | |
1329 | if opts == nil then\ | |
1330 | opts = { }\ | |
1331 | end\ | |
1332 | opts.href = tourl(path)\ | |
1333 | if MODE == 'CLIENT' then\ | |
1334 | opts.onclick = function(self, e)\ | |
1335 | e:preventDefault()\ | |
1336 | return BROWSER:navigate(path)\ | |
1337 | end\ | |
1338 | end\ | |
1339 | return a(name, opts)\ | |
1340 | end\ | |
1341 | local link_to\ | |
1342 | link_to = function(fileder, name, origin, attr)\ | |
1343 | fileder = find_fileder(fileder, origin)\ | |
1344 | name = name or fileder:get('title: mmm/dom')\ | |
1345 | name = name or fileder:gett('name: alpha')\ | |
1346 | do\ | |
1347 | local href = fileder:get('link: URL.*')\ | |
1348 | if href then\ | |
1349 | return a(name, merge(attr, {\ | |
1350 | href = href,\ | |
1351 | target = '_blank'\ | |
1352 | }))\ | |
1353 | else\ | |
1354 | return a(name, merge(attr, {\ | |
1355 | href = tourl(fileder.path),\ | |
1356 | onclick = (function()\ | |
1357 | if MODE == 'CLIENT' then\ | |
1358 | return function(self, e)\ | |
1359 | e:preventDefault()\ | |
1360 | return BROWSER:navigate(fileder.path)\ | |
1361 | end\ | |
1362 | end\ | |
1363 | end)()\ | |
1364 | }))\ | |
1365 | end\ | |
1366 | end\ | |
1367 | end\ | |
1368 | local embed\ | |
1369 | embed = function(fileder, name, origin, opts)\ | |
1370 | if name == nil then\ | |
1371 | name = ''\ | |
1372 | end\ | |
1373 | if opts == nil then\ | |
1374 | opts = { }\ | |
1375 | end\ | |
1376 | fileder = find_fileder(fileder, origin)\ | |
1377 | local ok, node = pcall(fileder.gett, fileder, name, 'mmm/dom')\ | |
1378 | if not ok then\ | |
1379 | return div(\"couldn't embed \" .. tostring(fileder) .. \" \" .. tostring(name), (pre(node)), {\ | |
1380 | style = {\ | |
1381 | background = 'var(--gray-fail)',\ | |
1382 | padding = '1em'\ | |
1383 | }\ | |
1384 | })\ | |
1385 | end\ | |
1386 | local klass = 'embed'\ | |
1387 | if opts.desc then\ | |
1388 | klass = klass .. ' desc'\ | |
1389 | end\ | |
1390 | if opts.inline then\ | |
1391 | klass = klass .. ' inline'\ | |
1392 | end\ | |
1393 | node = div({\ | |
1394 | class = klass,\ | |
1395 | node,\ | |
1396 | (function()\ | |
1397 | if opts.desc then\ | |
1398 | return div(opts.desc, {\ | |
1399 | class = 'description'\ | |
1400 | })\ | |
1401 | end\ | |
1402 | end)()\ | |
1403 | })\ | |
1404 | if opts.nolink then\ | |
1405 | return node\ | |
1406 | end\ | |
1407 | return link_to(fileder, node, nil, opts.attr)\ | |
1408 | end\ | |
1409 | return {\ | |
1410 | find_fileder = find_fileder,\ | |
1411 | link_to = link_to,\ | |
1412 | navigate_to = navigate_to,\ | |
1413 | embed = embed\ | |
1414 | }\ | |
1415 | end\ | |
1416 | ", "mmm/mmmfs/util.lua") end | |
1417 | if not p["mmm.mmmfs.converts"] then p["mmm.mmmfs.converts"] = load("local require = relative(..., 1)\ | |
1418 | local div, code, img, video, blockquote, a, span, source, iframe\ | |
1419 | do\ | |
1420 | local _obj_0 = require('mmm.dom')\ | |
1421 | div, code, img, video, blockquote, a, span, source, iframe = _obj_0.div, _obj_0.code, _obj_0.img, _obj_0.video, _obj_0.blockquote, _obj_0.a, _obj_0.span, _obj_0.source, _obj_0.iframe\ | |
1422 | end\ | |
1423 | local find_fileder, link_to, embed\ | |
1424 | do\ | |
1425 | local _obj_0 = (require('mmm.mmmfs.util'))(require('mmm.dom'))\ | |
1426 | find_fileder, link_to, embed = _obj_0.find_fileder, _obj_0.link_to, _obj_0.embed\ | |
1427 | end\ | |
1428 | local render\ | |
1429 | render = require('.layout').render\ | |
1430 | local tohtml\ | |
1431 | tohtml = require('mmm.component').tohtml\ | |
1432 | local js_fix\ | |
1433 | if MODE == 'CLIENT' then\ | |
1434 | js_fix = function(arg)\ | |
1435 | if arg == js.null then\ | |
1436 | return \ | |
1437 | end\ | |
1438 | return arg\ | |
1439 | end\ | |
1440 | end\ | |
1441 | local single\ | |
1442 | single = function(func)\ | |
1443 | return function(self, val)\ | |
1444 | return func(val)\ | |
1445 | end\ | |
1446 | end\ | |
1447 | local loadwith\ | |
1448 | loadwith = function(_load)\ | |
1449 | return function(self, val, fileder, key)\ | |
1450 | local func = assert(_load(val, tostring(fileder) .. \"#\" .. tostring(key)))\ | |
1451 | return func()\ | |
1452 | end\ | |
1453 | end\ | |
1454 | local converts = {\ | |
1455 | {\ | |
1456 | inp = 'fn -> (.+)',\ | |
1457 | out = '%1',\ | |
1458 | transform = function(self, val, fileder)\ | |
1459 | return val(fileder)\ | |
1460 | end\ | |
1461 | },\ | |
1462 | {\ | |
1463 | inp = 'mmm/component',\ | |
1464 | out = 'mmm/dom',\ | |
1465 | transform = single(tohtml)\ | |
1466 | },\ | |
1467 | {\ | |
1468 | inp = 'mmm/dom',\ | |
1469 | out = 'text/html+frag',\ | |
1470 | transform = function(self, node)\ | |
1471 | if MODE == 'SERVER' then\ | |
1472 | return node\ | |
1473 | else\ | |
1474 | return node.outerHTML\ | |
1475 | end\ | |
1476 | end\ | |
1477 | },\ | |
1478 | {\ | |
1479 | inp = 'mmm/dom',\ | |
1480 | out = 'text/html',\ | |
1481 | transform = function(self, html, fileder)\ | |
1482 | return render(html, fileder)\ | |
1483 | end\ | |
1484 | },\ | |
1485 | {\ | |
1486 | inp = 'text/html%+frag',\ | |
1487 | out = 'mmm/dom',\ | |
1488 | transform = (function()\ | |
1489 | if MODE == 'SERVER' then\ | |
1490 | return function(self, html, fileder)\ | |
1491 | html = html:gsub('<mmm%-link%s+(.-)>(.-)</mmm%-link>', function(attrs, text)\ | |
1492 | if #text == 0 then\ | |
1493 | text = nil\ | |
1494 | end\ | |
1495 | local path = ''\ | |
1496 | while attrs and attrs ~= '' do\ | |
1497 | local key, val, _attrs = attrs:match('^(%w+)=\"([^\"]-)\"%s*(.*)')\ | |
1498 | if not key then\ | |
1499 | key, _attrs = attrs:match('^(%w+)%s*(.*)$')\ | |
1500 | val = true\ | |
1501 | end\ | |
1502 | attrs = _attrs\ | |
1503 | local _exp_0 = key\ | |
1504 | if 'path' == _exp_0 then\ | |
1505 | path = val\ | |
1506 | else\ | |
1507 | warn(\"unkown attribute '\" .. tostring(key) .. \"=\\\"\" .. tostring(val) .. \"\\\"' in <mmm-link>\")\ | |
1508 | end\ | |
1509 | end\ | |
1510 | return link_to(path, text, fileder)\ | |
1511 | end)\ | |
1512 | html = html:gsub('<mmm%-embed%s+(.-)>(.-)</mmm%-embed>', function(attrs, desc)\ | |
1513 | local path, facet = '', ''\ | |
1514 | local opts = { }\ | |
1515 | if #desc ~= 0 then\ | |
1516 | opts.desc = desc\ | |
1517 | end\ | |
1518 | while attrs and attrs ~= '' do\ | |
1519 | local key, val, _attrs = attrs:match('^(%w+)=\"([^\"]-)\"%s*(.*)')\ | |
1520 | if not key then\ | |
1521 | key, _attrs = attrs:match('^(%w+)%s*(.*)$')\ | |
1522 | val = true\ | |
1523 | end\ | |
1524 | attrs = _attrs\ | |
1525 | local _exp_0 = key\ | |
1526 | if 'path' == _exp_0 then\ | |
1527 | path = val\ | |
1528 | elseif 'facet' == _exp_0 then\ | |
1529 | facet = val\ | |
1530 | elseif 'nolink' == _exp_0 then\ | |
1531 | opts.nolink = true\ | |
1532 | elseif 'inline' == _exp_0 then\ | |
1533 | opts.inline = true\ | |
1534 | else\ | |
1535 | warn(\"unkown attribute '\" .. tostring(key) .. \"=\\\"\" .. tostring(val) .. \"\\\"' in <mmm-embed>\")\ | |
1536 | end\ | |
1537 | end\ | |
1538 | return embed(path, facet, fileder, opts)\ | |
1539 | end)\ | |
1540 | return html\ | |
1541 | end\ | |
1542 | else\ | |
1543 | return function(self, html, fileder)\ | |
1544 | local parent\ | |
1545 | do\ | |
1546 | local _with_0 = document:createElement('div')\ | |
1547 | _with_0.innerHTML = html\ | |
1548 | local embeds = _with_0:getElementsByTagName('mmm-embed')\ | |
1549 | do\ | |
1550 | local _accum_0 = { }\ | |
1551 | local _len_0 = 1\ | |
1552 | for i = 0, embeds.length - 1 do\ | |
1553 | _accum_0[_len_0] = embeds[i]\ | |
1554 | _len_0 = _len_0 + 1\ | |
1555 | end\ | |
1556 | embeds = _accum_0\ | |
1557 | end\ | |
1558 | for _index_0 = 1, #embeds do\ | |
1559 | local element = embeds[_index_0]\ | |
1560 | local path = js_fix(element:getAttribute('path'))\ | |
1561 | local facet = js_fix(element:getAttribute('facet'))\ | |
1562 | local nolink = js_fix(element:getAttribute('nolink'))\ | |
1563 | local inline = js_fix(element:getAttribute('inline'))\ | |
1564 | local desc = js_fix(element.innerText)\ | |
1565 | element:replaceWith(embed(path or '', facet or '', fileder, {\ | |
1566 | nolink = nolink,\ | |
1567 | inline = inline,\ | |
1568 | desc = desc\ | |
1569 | }))\ | |
1570 | end\ | |
1571 | embeds = _with_0:getElementsByTagName('mmm-link')\ | |
1572 | do\ | |
1573 | local _accum_0 = { }\ | |
1574 | local _len_0 = 1\ | |
1575 | for i = 0, embeds.length - 1 do\ | |
1576 | _accum_0[_len_0] = embeds[i]\ | |
1577 | _len_0 = _len_0 + 1\ | |
1578 | end\ | |
1579 | embeds = _accum_0\ | |
1580 | end\ | |
1581 | for _index_0 = 1, #embeds do\ | |
1582 | local element = embeds[_index_0]\ | |
1583 | local text = js_fix(element.innerText)\ | |
1584 | local path = js_fix(element:getAttribute('path'))\ | |
1585 | element:replaceWith(link_to(path or '', text, fileder))\ | |
1586 | end\ | |
1587 | parent = _with_0\ | |
1588 | end\ | |
1589 | assert(1 == parent.childElementCount, \"text/html with more than one child!\")\ | |
1590 | return parent.firstElementChild\ | |
1591 | end\ | |
1592 | end\ | |
1593 | end)()\ | |
1594 | },\ | |
1595 | {\ | |
1596 | inp = 'text/lua -> (.+)',\ | |
1597 | out = '%1',\ | |
1598 | transform = loadwith(load or loadstring)\ | |
1599 | },\ | |
1600 | {\ | |
1601 | inp = 'mmm/tpl -> (.+)',\ | |
1602 | out = '%1',\ | |
1603 | transform = function(self, source, fileder)\ | |
1604 | return source:gsub('{{(.-)}}', function(expr)\ | |
1605 | local path, facet = expr:match('^([%w%-_%./]*)%+(.*)')\ | |
1606 | assert(path, \"couldn't match TPL expression '\" .. tostring(expr) .. \"'\")\ | |
1607 | return (find_fileder(path, fileder)):gett(facet)\ | |
1608 | end)\ | |
1609 | end\ | |
1610 | },\ | |
1611 | {\ | |
1612 | inp = 'time/iso8601-date',\ | |
1613 | out = 'time/unix',\ | |
1614 | transform = function(self, val)\ | |
1615 | local year, _, month, day = val:match('^%s*(%d%d%d%d)(%-?)([01]%d)%2([0-3]%d)%s*$')\ | |
1616 | assert(year, \"failed to parse ISO 8601 date: '\" .. tostring(val) .. \"'\")\ | |
1617 | return os.time({\ | |
1618 | year = year,\ | |
1619 | month = month,\ | |
1620 | day = day\ | |
1621 | })\ | |
1622 | end\ | |
1623 | },\ | |
1624 | {\ | |
1625 | inp = 'URL -> twitter/tweet',\ | |
1626 | out = 'mmm/dom',\ | |
1627 | transform = function(self, href)\ | |
1628 | local id = assert((href:match('twitter.com/[^/]-/status/(%d*)')), \"couldn't parse twitter/tweet URL: '\" .. tostring(href) .. \"'\")\ | |
1629 | if MODE == 'CLIENT' then\ | |
1630 | do\ | |
1631 | local parent = div()\ | |
1632 | window.twttr.widgets:createTweet(id, parent)\ | |
1633 | return parent\ | |
1634 | end\ | |
1635 | else\ | |
1636 | return div(blockquote({\ | |
1637 | class = 'twitter-tweet',\ | |
1638 | ['data-lang'] = 'en',\ | |
1639 | a('(linked tweet)', {\ | |
1640 | href = href\ | |
1641 | })\ | |
1642 | }))\ | |
1643 | end\ | |
1644 | end\ | |
1645 | },\ | |
1646 | {\ | |
1647 | inp = 'URL -> youtube/video',\ | |
1648 | out = 'mmm/dom',\ | |
1649 | transform = function(self, link)\ | |
1650 | local id = link:match('youtu%.be/([^/]+)')\ | |
1651 | id = id or link:match('youtube.com/watch.*[?&]v=([^&]+)')\ | |
1652 | id = id or link:match('youtube.com/[ev]/([^/]+)')\ | |
1653 | id = id or link:match('youtube.com/embed/([^/]+)')\ | |
1654 | assert(id, \"couldn't parse youtube URL: '\" .. tostring(link) .. \"'\")\ | |
1655 | return iframe({\ | |
1656 | width = 560,\ | |
1657 | height = 315,\ | |
1658 | frameborder = 0,\ | |
1659 | allowfullscreen = true,\ | |
1660 | frameBorder = 0,\ | |
1661 | src = \"//www.youtube.com/embed/\" .. tostring(id)\ | |
1662 | })\ | |
1663 | end\ | |
1664 | },\ | |
1665 | {\ | |
1666 | inp = 'URL -> image/.+',\ | |
1667 | out = 'mmm/dom',\ | |
1668 | transform = function(self, src, fileder)\ | |
1669 | return img({\ | |
1670 | src = src\ | |
1671 | })\ | |
1672 | end\ | |
1673 | },\ | |
1674 | {\ | |
1675 | inp = 'URL -> video/.+',\ | |
1676 | out = 'mmm/dom',\ | |
1677 | transform = function(self, src)\ | |
1678 | return video((source({\ | |
1679 | src = src\ | |
1680 | })), {\ | |
1681 | controls = true,\ | |
1682 | loop = true\ | |
1683 | })\ | |
1684 | end\ | |
1685 | },\ | |
1686 | {\ | |
1687 | inp = 'text/plain',\ | |
1688 | out = 'mmm/dom',\ | |
1689 | transform = function(self, val)\ | |
1690 | return span(val)\ | |
1691 | end\ | |
1692 | },\ | |
1693 | {\ | |
1694 | inp = 'alpha',\ | |
1695 | out = 'mmm/dom',\ | |
1696 | transform = single(code)\ | |
1697 | },\ | |
1698 | {\ | |
1699 | inp = '(.+)',\ | |
1700 | out = 'URL -> %1',\ | |
1701 | transform = function(self, _, fileder, key)\ | |
1702 | return tostring(fileder.path) .. \"/\" .. tostring(key.name) .. \":\" .. tostring(self.from)\ | |
1703 | end\ | |
1704 | }\ | |
1705 | }\ | |
1706 | if MODE == 'SERVER' then\ | |
1707 | local ok, moon = pcall(require, 'moonscript.base')\ | |
1708 | if ok then\ | |
1709 | local _load = moon.load or moon.loadstring\ | |
1710 | table.insert(converts, {\ | |
1711 | inp = 'text/moonscript -> (.+)',\ | |
1712 | out = '%1',\ | |
1713 | transform = loadwith(moon.load or moon.loadstring)\ | |
1714 | })\ | |
1715 | table.insert(converts, {\ | |
1716 | inp = 'text/moonscript -> (.+)',\ | |
1717 | out = 'text/lua -> %1',\ | |
1718 | transform = single(moon.to_lua)\ | |
1719 | })\ | |
1720 | end\ | |
1721 | else\ | |
1722 | table.insert(converts, {\ | |
1723 | inp = 'text/javascript -> (.+)',\ | |
1724 | out = '%1',\ | |
1725 | transform = function(self, source)\ | |
1726 | local f = js.new(window.Function, source)\ | |
1727 | return f()\ | |
1728 | end\ | |
1729 | })\ | |
1730 | end\ | |
1731 | do\ | |
1732 | local markdown\ | |
1733 | if MODE == 'SERVER' then\ | |
1734 | local success, discount = pcall(require, 'discount')\ | |
1735 | if not success then\ | |
1736 | warn(\"NO MARKDOWN SUPPORT!\", discount)\ | |
1737 | end\ | |
1738 | markdown = success and function(md)\ | |
1739 | local res = assert(discount.compile(md, 'githubtags'))\ | |
1740 | return res.body\ | |
1741 | end\ | |
1742 | else\ | |
1743 | markdown = window and window.marked and (function()\ | |
1744 | local _base_0 = window\ | |
1745 | local _fn_0 = _base_0.marked\ | |
1746 | return function(...)\ | |
1747 | return _fn_0(_base_0, ...)\ | |
1748 | end\ | |
1749 | end)()\ | |
1750 | end\ | |
1751 | if markdown then\ | |
1752 | table.insert(converts, {\ | |
1753 | inp = 'text/markdown',\ | |
1754 | out = 'text/html+frag',\ | |
1755 | transform = function(self, md)\ | |
1756 | return \"<div class=\\\"markdown\\\">\" .. tostring(markdown(md)) .. \"</div>\"\ | |
1757 | end\ | |
1758 | })\ | |
1759 | table.insert(converts, {\ | |
1760 | inp = 'text/markdown%+span',\ | |
1761 | out = 'mmm/dom',\ | |
1762 | transform = (function()\ | |
1763 | if MODE == 'SERVER' then\ | |
1764 | return function(self, source)\ | |
1765 | local html = markdown(source)\ | |
1766 | html = html:gsub('^<p', '<span')\ | |
1767 | return html:gsub('/p>$', '/span>')\ | |
1768 | end\ | |
1769 | else\ | |
1770 | return function(self, source)\ | |
1771 | local html = markdown(source)\ | |
1772 | html = html:gsub('^%s*<p>%s*', '')\ | |
1773 | html = html:gsub('%s*</p>%s*$', '')\ | |
1774 | do\ | |
1775 | local _with_0 = document:createElement('span')\ | |
1776 | _with_0.innerHTML = html\ | |
1777 | return _with_0\ | |
1778 | end\ | |
1779 | end\ | |
1780 | end\ | |
1781 | end)()\ | |
1782 | })\ | |
1783 | end\ | |