change ?interactive pseudofacet to text/html+interactive pseudo-type
fix browser push/popstate
s-ol
3 years ago
46 | 46 | switch method |
47 | 47 | when 'GET', 'HEAD' |
48 | 48 | val = switch facet.name |
49 | when '?interactive' | |
50 | export BROWSER | |
51 | ||
52 | root = Fileder @store | |
53 | BROWSER = Browser root, path | |
54 | render BROWSER\todom!, fileder, noview: true, scripts: " | |
55 | <script type=\"application/lua\"> | |
56 | on_load = on_load or {} | |
57 | table.insert(on_load, function() | |
58 | local path = #{string.format '%q', path} | |
59 | local browser = require 'mmm.mmmfs.browser' | |
60 | local fileder = require 'mmm.mmmfs.fileder' | |
61 | local web = require 'mmm.mmmfs.stores.web' | |
62 | ||
63 | local store = web.WebStore({ verbose = true }) | |
64 | local index = store:get_index(path, -1) | |
65 | local root = fileder.Fileder(store, index) | |
66 | ||
67 | BROWSER = browser.Browser(root, path, true) | |
68 | end) | |
69 | </script>" | |
70 | ||
71 | 49 | when '?index', '?tree' |
72 | 50 | -- serve fileder index |
73 | 51 | -- '?index': one level deep |
80 | 58 | if not fileder\has_facet facet.name |
81 | 59 | return 404, "facet '#{facet.name}' not found in fileder '#{path}'" |
82 | 60 | |
83 | fileder\get facet | |
61 | if facet.type == 'text/html+interactive' | |
62 | export BROWSER | |
63 | ||
64 | root = Fileder @store | |
65 | BROWSER = Browser root, path, facet.name | |
66 | render BROWSER\todom!, fileder, noview: true, scripts: " | |
67 | <script type=\"application/lua\"> | |
68 | on_load = on_load or {} | |
69 | table.insert(on_load, function() | |
70 | local path = #{string.format '%q', path} | |
71 | local facet = #{string.format '%q', facet.name} | |
72 | local browser = require 'mmm.mmmfs.browser' | |
73 | local fileder = require 'mmm.mmmfs.fileder' | |
74 | local web = require 'mmm.mmmfs.stores.web' | |
75 | ||
76 | local store = web.WebStore({ verbose = true }) | |
77 | local index = store:get_index(path, -1) | |
78 | local root = fileder.Fileder(store, index) | |
79 | ||
80 | BROWSER = browser.Browser(root, path, facet, true) | |
81 | end) | |
82 | </script>" | |
83 | else | |
84 | fileder\get facet | |
84 | 85 | |
85 | 86 | if val |
86 | 87 | 200, val |
99 | 100 | path_facet or= path |
100 | 101 | path, facet = path_facet\match '(.*)/([^/]*)' |
101 | 102 | |
102 | type or= 'text/html' | |
103 | type or= 'text/html+interactive' | |
103 | 104 | type = type\match '%s*(.*)' |
104 | 105 | facet = Key facet, type |
105 | 106 | |
111 | 112 | |
112 | 113 | res = headers.new! |
113 | 114 | response_type = if status > 299 then 'text/plain' |
114 | else if facet then facet.type | |
115 | else 'text/json' | |
115 | else if facet.type == 'text/html+interactive' then 'text/html' | |
116 | else facet.type | |
116 | 117 | res\append ':status', tostring status |
117 | 118 | res\append 'content-type', response_type |
118 | 119 |
39 | 39 | table.insert casts, convert |
40 | 40 | |
41 | 41 | class Browser |
42 | new: (@root, path, rehydrate=false) => | |
42 | new: (@root, path, facet, rehydrate=false) => | |
43 | 43 | -- root fileder |
44 | 44 | assert @root, 'root fileder is nil' |
45 | 45 | |
46 | 46 | -- active path |
47 | 47 | @path = ReactiveVar path or '' |
48 | ||
49 | -- active fileder | |
50 | -- (re)set every time @path changes | |
51 | @active = @path\map @root\walk | |
52 | ||
53 | -- currently active facet | |
54 | -- (re)set to default when @active changes | |
55 | @facet = ReactiveVar Key facet, 'mmm/dom' | |
56 | if MODE == 'CLIENT' | |
57 | @active\subscribe (fileder) -> | |
58 | return unless fileder | |
59 | last = @facet and @facet\get! | |
60 | @facet\set Key if last then last.type else 'mmm/dom' | |
48 | 61 | |
49 | 62 | -- update URL bar |
50 | 63 | if MODE == 'CLIENT' |
53 | 66 | logo.classList\add 'spin' |
54 | 67 | logo.parentElement.offsetWidth |
55 | 68 | logo.classList\remove 'spin' |
56 | @path\subscribe (path) -> | |
69 | ||
70 | @facet\subscribe (facet) -> | |
57 | 71 | document.body.classList\add 'loading' |
58 | 72 | spin! |
59 | 73 | |
60 | 74 | return if @skip |
61 | vis_path = path .. (if '/' == path\sub -1 then '' else '/') | |
62 | window.history\pushState path, '', vis_path | |
75 | ||
76 | path = @path\get! | |
77 | state = js.global\eval 'new Object()' | |
78 | state.path = path | |
79 | state.name = facet.name | |
80 | state.type = facet.type | |
81 | ||
82 | window.history\pushState state, '', "#{path}/#{(Key facet.name, 'text/html+interactive')\tostring true}" | |
63 | 83 | |
64 | 84 | window.onpopstate = (_, event) -> |
65 | if event.state and not event.state == js.null | |
85 | state = event.state | |
86 | if state != js.null | |
66 | 87 | @skip = true |
67 | @path\set event.state | |
88 | @path\set state.path | |
89 | @facet\set Key state.name, state.type | |
68 | 90 | @skip = nil |
69 | ||
70 | -- active fileder | |
71 | -- (re)set every time @path changes | |
72 | @active = @path\map @root\walk | |
73 | ||
74 | -- currently active facet | |
75 | -- (re)set to default when @active changes | |
76 | @facet = @active\map (fileder) -> | |
77 | return unless fileder | |
78 | last = @facet and @facet\get! | |
79 | Key if last then last.type else 'mmm/dom' | |
80 | 91 | |
81 | 92 | -- whether inspect tab is active |
82 | 93 | @inspect = ReactiveVar (MODE == 'CLIENT' and window.location.search\match '[?&]inspect') |
120 | 131 | |
121 | 132 | current = @facet\get! |
122 | 133 | current = current and current.name |
123 | with select :onchange, disabled: not fileder | |
134 | with select :onchange, disabled: not fileder, value: @facet\map (f) -> f and f.name | |
124 | 135 | has_main = fileder and fileder\has_facet '' |
125 | 136 | \append option '(main)', value: '', disabled: not has_main, selected: current == '' |
126 | 137 | if fileder |
33 | 33 | assert ('string' == type @type), "type is not a string: '#{@type}'" |
34 | 34 | |
35 | 35 | -- format as a string (see constructor) |
36 | tostring: => | |
37 | if @name == '' | |
36 | -- in strict mode never omit name | |
37 | tostring: (strict=false) => | |
38 | if not strict and @name == '' | |
38 | 39 | @type |
39 | 40 | else |
40 | 41 | "#{@name}: #{@type}" |
28 | 28 | else |
29 | 29 | val |
30 | 30 | |
31 | (string) -> | |
32 | print fix JSON\parse string | |
33 | fix JSON\parse string | |
31 | (string) -> fix JSON\parse string | |
34 | 32 | |
35 | 33 | class WebStore extends Store |
36 | 34 | new: (opts = {}) => |
55 | 55 | assert.is.equal 'facet: and -> long -> type', tostring Key 'facet: and -> long -> type' |
56 | 56 | assert.is.equal 'facet: and -> long -> type', tostring Key 'facet', 'and -> long -> type' |
57 | 57 | assert.is.equal 'facet: and -> long -> type', tostring Key 'facet: and -> long -> type' |
58 | ||
59 | it ":tostring formats the key", -> | |
60 | assert.is.equal 'type/only', (Key 'type/only')\tostring! | |
61 | assert.is.equal 'type/only', (Key '', 'type/only')\tostring! | |
62 | assert.is.equal 'type/only', (Key ": type/only")\tostring! | |
63 | ||
64 | it ":tostring supports strict mode", -> | |
65 | assert.is.equal ': type/only', (Key 'type/only')\tostring true | |
66 | assert.is.equal ': type/only', (Key '', 'type/only')\tostring true | |
67 | assert.is.equal ': type/only', (Key ": type/only")\tostring true |