diff options
| author | s-ol <s-ol@users.noreply.github.com> | 2019-09-21 14:47:10 +0000 |
|---|---|---|
| committer | s-ol <s-ol@users.noreply.github.com> | 2019-09-21 14:47:10 +0000 |
| commit | 379c43f442c84fd46ffd0c97fead1895386387b2 (patch) | |
| tree | 98b308e8317ef62a18d8fc5c1e7a794031025ef3 | |
| parent | add .tup to dockerignore (diff) | |
| download | mmm-379c43f442c84fd46ffd0c97fead1895386387b2.tar.gz mmm-379c43f442c84fd46ffd0c97fead1895386387b2.zip | |
put brack browser. oops?
| -rw-r--r-- | mmm/mmmfs/browser.moon | 241 |
1 files changed, 241 insertions, 0 deletions
diff --git a/mmm/mmmfs/browser.moon b/mmm/mmmfs/browser.moon new file mode 100644 index 0000000..c25062d --- /dev/null +++ b/mmm/mmmfs/browser.moon @@ -0,0 +1,241 @@ +require = relative ..., 1 +import Key from require '.fileder' +import converts, get_conversions, apply_conversions from require '.conversion' +import ReactiveVar, get_or_create, text, elements from require 'mmm.component' +import pre, div, nav, span, button, a, select, option from elements +import languages from require 'mmm.highlighting' + +keep = (var) -> + last = var\get! + var\map (val) -> + last = val or last + last + +code_cast = (lang) -> + { + inp: "text/#{lang}.*", + out: 'mmm/dom', + transform: (val) -> languages[lang] val + } + +casts = { + code_cast 'javascript', + code_cast 'moonscript', + code_cast 'lua', + code_cast 'markdown', + code_cast 'html', + { + inp: 'text/plain' + out: 'mmm/dom' + transform: (val) -> text val + } +} + +for convert in *converts + table.insert casts, convert + +class Browser + new: (@root, path='', rehydrate=false) => + -- root fileder + assert @root, 'root fileder is nil' + + -- active path + @path = ReactiveVar path + + -- update URL bar + if MODE == 'CLIENT' + logo = document\querySelector 'header h1 > svg' + spin = -> + logo.classList\add 'spin' + logo.parentElement.offsetWidth + logo.classList\remove 'spin' + @path\subscribe (path) -> + document.body.classList\add 'loading' + spin! + + return if @skip + vis_path = path .. (if '/' == path\sub -1 then '' else '/') + window.history\pushState path, '', vis_path + + window.onpopstate = (_, event) -> + if event.state + @skip = true + @path\set event.state + @skip = nil + + -- active fileder + -- (re)set every time @path changes + @active = @path\map @root\walk + + -- currently active facet + -- (re)set to default when @active changes + @facet = @active\map (fileder) -> + return unless fileder + last = @facet and @facet\get! + Key if last then last.type else 'mmm/dom' + + -- whether inspect tab is active + @inspect = ReactiveVar (MODE == 'CLIENT' and window.location.search\match '[?&]inspect') + + -- retrieve or create the wrapper and main elements + main = get_or_create 'div', 'browser-root', class: 'main view' + + -- prepend the navbar + if MODE == 'SERVER' + main\append nav { id: 'browser-navbar', span 'please stand by... interactivity loading :)' } + else + main\prepend with get_or_create 'nav', 'browser-navbar' + .node.innerHTML = '' + \append span 'path: ', @path\map (path) -> with div class: 'path', style: { display: 'inline-block' } + path_segment = (name, href) -> + a name, :href, onclick: (_, e) -> + e\preventDefault! + @navigate href + + href = '' + path = path\match '^/(.*)' + + \append path_segment 'root', '' + + while path + name, rest = path\match '^([%w%-_%.]+)/(.*)' + if not name + name = path + + path = rest + href = "#{href}/#{name}" + + \append '/' + \append path_segment name, href + + \append span 'view facet:', style: { 'margin-right': '0' } + \append @active\map (fileder) -> + onchange = (_, e) -> + { :type } = @facet\get! + @facet\set Key name: e.target.value, :type + + current = @facet\get! + current = current and current.name + with select :onchange, disabled: not fileder + has_main = fileder and fileder\find current.name, '.*' + \append option '(main)', value: '', disabled: not has_main, selected: current == '' + if fileder + for i, value in ipairs fileder\get_facets! + continue if value == '' + \append option value, :value, selected: value == current + \append @inspect\map (enabled) -> + if not enabled + button 'inspect', onclick: (_, e) -> @inspect\set true + + @error = ReactiveVar! + main\append with get_or_create 'div', 'browser-error', class: @error\map (e) -> if e then 'error-wrap' else 'error-wrap empty' + \append (span "an error occured while rendering this view:"), (rehydrate and .node.firstChild) + \append @error + + -- append or patch #browser-content + main\append with get_or_create 'div', 'browser-content', class: 'content' + content = ReactiveVar if rehydrate then .node.lastChild else @get_content @facet\get! + \append keep content + if MODE == 'CLIENT' + @facet\subscribe (p) -> + window\setTimeout (-> content\set @get_content p), 150 + + if rehydrate + -- force one rerender to set onclick handlers etc + @facet\set @facet\get! + + inspector = @inspect\map (enabled) -> if enabled then @get_inspector! + + -- export mmm/component interface + wrapper = get_or_create 'div', 'browser-wrapper', main, inspector, class: 'browser' + @node = wrapper.node + @render = wrapper\render + + err_and_trace = (err) -> "#{err} #{debug.traceback!}" + default_convert = (key) => @get key.name, 'mmm/dom' + + -- render #browser-content + get_content: (prop, err=@error, convert=default_convert) => + clear_error = -> + err\set! if MODE == 'CLIENT' + disp_error = (msg) -> + if MODE == 'CLIENT' + err\set pre msg + warn "ERROR rendering content: #{msg}" + nil + + active = @active\get! + + return disp_error "fileder not found!" unless active + return disp_error "facet not found!" unless prop + + ok, res, trace = xpcall convert, err_and_trace, active, prop + + document.body.classList\remove 'loading' if MODE == 'CLIENT' + + if ok and res + clear_error! + res + elseif ok + div "[no conversion path to #{prop.type}]" + elseif res and res.match and res\match '%[nossr%]$' + div "[this page could not be pre-rendered on the server]" + else + if trace + res = "#{res}\n#{trace}" + disp_error res + + get_inspector: => + -- active facet in inspect tab + -- (re)set to match when @facet changes + @inspect_prop = @facet\map (prop) -> + active = @active\get! + key = active and active\find prop + key = key.original if key and key.original + key + + @inspect_err = ReactiveVar! + + with div class: 'view inspector' + \append nav { + span 'inspector' + @inspect_prop\map (current) -> + current = current and current\tostring! + fileder = @active\get! + + onchange = (_, e) -> + return if e.target.value == '' + { :name } = @facet\get! + @inspect_prop\set Key e.target.value + + with select :onchange + \append option '(none)', value: '', disabled: true, selected: not value + if fileder + for key, _ in pairs fileder.facets + value = key\tostring! + \append option value, :value, selected: value == current + @inspect\map (enabled) -> + if enabled + button 'close', onclick: (_, e) -> @inspect\set false + } + \append with div class: @inspect_err\map (e) -> if e then 'error-wrap' else 'error-wrap empty' + \append span "an error occured while rendering this view:" + \append @inspect_err + \append with pre class: 'content' + \append keep @inspect_prop\map (prop, old) -> + @get_content prop, @inspect_err, (prop) => + value, key = @get prop + assert key, "couldn't @get #{prop}" + + conversions = get_conversions 'mmm/dom', key.type, casts + assert conversions, "cannot cast '#{key.type}'" + apply_conversions conversions, value, @, prop + + default_convert = (key) => @get key.name, 'mmm/dom' + + navigate: (new) => + @path\set new + +{ + :Browser +} |
