diff --git a/.ignore b/.ignore index 4356adf..2df43f6 100644 --- a/.ignore +++ b/.ignore @@ -3,3 +3,4 @@ $bundle.lua *.js *.js.map +*.lua diff --git a/build/import.moon b/build/import.moon index ba99b03..92d4397 100644 --- a/build/import.moon +++ b/build/import.moon @@ -9,7 +9,7 @@ require 'mmm' require 'lfs' -import Fileder, Key from require 'mmm.mmmfs.fileder' +import Key from require 'mmm.mmmfs.fileder' import SQLStore from require 'mmm.mmmfs.stores.sql' -- usage: diff --git a/build/render_all.moon b/build/render_all.moon index 02da12c..b09957e 100644 --- a/build/render_all.moon +++ b/build/render_all.moon @@ -8,7 +8,7 @@ add '?/init.server' require 'mmm' -import load_tree from require 'mmm.mmmfs.fileder' +import Fileder from require 'mmm.mmmfs.fileder' import get_store from require 'mmm.mmmfs.stores' import render from require 'mmm.mmmfs.layout' @@ -20,7 +20,7 @@ STATIC = true store = get_store store -tree = load_tree store +tree = Fileder store tree = tree\walk startpath if startpath for fileder in coroutine.wrap tree\iterate diff --git a/build/server.moon b/build/server.moon index 693f00c..4e7ad3a 100644 --- a/build/server.moon +++ b/build/server.moon @@ -9,7 +9,7 @@ require 'mmm' -import Key, dir_base, load_tree from require 'mmm.mmmfs.fileder' +import dir_base, Key, Fileder from require 'mmm.mmmfs.fileder' import convert from require 'mmm.mmmfs.conversion' import get_store from require 'mmm.mmmfs.stores' import render from require 'mmm.mmmfs.layout' @@ -38,7 +38,7 @@ assert @server\loop! handle: (method, path, facet) => - fileder = load_tree @store, path -- @tree\walk path + fileder = Fileder @store, path if not fileder -- fileder not found @@ -50,7 +50,7 @@ when '?interactive' export BROWSER - root = load_tree @store + root = Fileder @store BROWSER = Browser root, path render BROWSER\todom!, fileder, noview: true, scripts: " " + rplc\render! + +if MODE == 'CLIENT' + export ^ + export o + eval = js.global\eval + GRID_W = 50 + GRID_H = 40 + + SVG = + doc: eval "(function() { return SVG(document.createElement('svg')); })", + G: eval "(function() { return new SVG.G(); })", + setmetatable SVG, __call: => @doc! + + o = do + mkobj = eval "(function () { return {}; })" + (tbl) -> + with obj = mkobj! + for k,v in pairs(tbl) + obj[k] = v + + class Diagram + new: (f) => + @svg = SVG! + @arrows = SVG.G! + @width, @height = 0, 0 + @y = 0 + + f @ + + txtattr = o { + fill: 'white', + 'font-size': '14px', + 'text-anchor': 'middle', + } + block: (color, label, h=1) => + @svg\add with SVG.G! + with \rect GRID_W, h * GRID_H + \attr o fill: color + if label + with \plain label + \move GRID_W/2, 0 + \attr txtattr + + \move @width * GRID_W, (@y + h) * -GRID_H + @y += h + if @y > @height + @height = @y + + arrattr = o { + fill: 'white', + 'font-size': '18px', + 'text-anchor': 'middle', + } + arrow: (char, x, y) => + with @arrows\plain char + \attr arrattr + \move (x + 1) * GRID_W, (y - 0.5) * -GRID_H - 11 + + -- inout: (x=@width, y=@y) => @arrow '⇋', x, y -- U+21CB + -- inn: (x=@width, y=@y) => @arrow '↼', x, y+0.25 -- U+21BC + -- out: (x=@width, y=@y) => @arrow '⇁', x, y-0.25 -- U+21C1 + inout: (x=@width, y=@y) => @arrow '⇆', x, y -- U+21C6 + inn: (x=@width, y=@y) => @arrow '←', x, y+0.25 -- U+2190 + out: (x=@width, y=@y) => @arrow '→', x, y-0.25 -- U+2192 + + mind: (label='mind', ...) => @block '#fac710', label, ... + phys: (label='phys', ...) => @block '#8fd13f', label, ... + digi: (label='digi', ...) => @block '#9510ac', label, ... + + next: => + @y = 0 + @width += 1 + + finish: => + return if @node + @svg\add @arrows + + @width += 1 + w, h = @width * GRID_W, @height * GRID_H + + l = GRID_W / 6.5 + @svg\add with @svg\line 0, -GRID_H, w, -GRID_H + \stroke o width: 2, color: '#ffffff', dasharray: "#{l}, #{l}" + + @svg\size w, h + @svg\viewbox 0, -h, w, h + @node = @svg.node + +addlabel = (label, diagram) -> + with div style: { display: 'inline-block', margin: '20px', 'text-align': 'center' } + \append diagram + \append div label + +figures = do + style = + display: 'flex' + 'align-items': 'flex-end' + 'justify-content': 'space-evenly' + (...) -> div { :style, ... } + +sources = do + short = => "#{@id} #{@year}" + long = => @names, " (#{@year}): ", (i @title), ", #{@published}" + { + { + id: 'Milgram', + title: 'Augmented Reality: A class of displays on the reality-virtuality continuum', + published: 'in SPIE Vol. 2351', + names: 'P. Milgram, H. Takemura, A. Utsumi, F. Kishino', + year: 1994 + :long, :short, + }, + { + id: 'Marsh', + title: 'Nested Immersion: Describing and Classifying Augmented Virtual Reality', + published: 'IEEE Virtual Reality Conference 2015', + names: 'W. Marsh, F. Mérienne', + year: 2015 + :long, :short, + }, + { + id: 'Billinghurst', + title: 'The MagicBook: a transitional AR interface', + published: 'in Computer & Graphics 25', + names: 'M. Billinghurst, H. Kato, I. Poupyrev', + year: 2001, + :long, :short, + }, + { + id: 'Matrix', + title: 'The Matrix', + year: 1999, + names: 'L. Wachowski, A. Wachowski', + long: => @names, " (#{@year}): ", (i @title), " (movie)" + short: => tostring @year + }, + { + id: 'Naam', + title: 'Nexus', + published: 'Angry Robot (novel)', + names: 'R. Naam', + year: 2012, + :long, :short, + } + } + +ref = do + fmt = (id) -> + + local src + for _src in *sources + if _src.id == id + src = _src + break + + if src + a { src\short!, href: "##{src.id}" } + else + span id + + ref = (...) -> + refs = { ... } + with span "(", fmt refs[1] + for i=2, #refs + \append ", " + \append fmt refs[i] + \append ")" + +references = -> + with ol! + for src in *sources + \append li { id: src.id, src\long! } + +sect = (label) -> + with section style: 'page-break-inside': 'avoid' + \append h2 label + +append with article style: { margin: 'auto', 'max-width': '750px' } + \append div 'Sol Bekic', style: 'text-align': 'right' + + \append h1 { + style: { 'text-align': 'center', 'font-size': '2em' }, + "Reality Stacks", + div "a Taxonomy for Multi-Reality Experiences", style: 'font-size': '0.6em' + } + + \append with sect "Abstract" + \append p "With the development of mixed-reality experiences and the corresponding interface devices + multiple frameworks for classification of these experiences have been proposed. However these past + attempts have mostly been developed alongside and with the intent of capturing specific projects ", + (ref 'Marsh', 'Billinghurst'), " or are nevertheless very focused on existing methods and technologies ", + (ref 'Milgram'), ". The existing taxonomies also all assume physical reality as a fixpoint and constant and are + thereby not suited to describe many fictional mixed-reality environments and altered states of consciousness. + In this paper we describe a new model for describing such experiences and examplify it's use with currently + existing as well as idealized technologies from popular culture." + + \append with sect "Terminology" + \append p "We propose the following terms and definitions that will be used extensively for the remainder of the paper:" + for definition in *{ + { "layer of reality": "a closed system consisting of a world model and a set of rules or dynamics operating on and + constraining said model." }, + { "world model": "describes a world state containing objects, agents and/or concepts on an arbitrary abstraction level." }, + '------', + { "reality stack": "structure consisting of all layers of reality encoding an agent's interaction with his environment + in their world model at a given moment, as well as all layers supporting these respectively." }, + '------', + { "physical reality": "layer of reality defined by physical matter and the physical laws acting upon it. + While the emergent phenomena of micro- and macro physics as well as layers of social existence etc. may be seen + as separate layers, for the purpose of this paper we will group these together under the term of physical reality." }, + { "mental reality": "layer of reality perceived and processed by the brain of a human agent." }, + { "digital reality": "layer of reality created and simulated by a digital system, e.g. a virtual reality game." }, + { "phys, mind, digi": "abbreviations for physical, mental and digital reality respectively." }, + } + if 'string' == type definition + \append hr! + continue + \append with div style: { 'margin-left': '2rem' } + term = next definition + \append span term, style: { + display: 'inline-block', + 'margin-left': '-2rem', + 'font-weight': 'bold', + 'min-width': '140px' + } + \append span definition[term] + + \append with sect "Introduction" + \append p "We identify two different types of relationships between layers in multi-reality environments. + The first is layer nesting. Layer nesting describes how some layers are contained in other layers; i.e. they exist + within and can be represented fully by the parent layer's world model and the child layer's rules emerge natively from + the parent layer's dynamics. Layer nesting is visualized on the vertical axis in the following diagrams. + For each layer of reality on the bottom of the diagram the nested parent layers can be found by tracing a line upwards + to the top of the diagram. Following a materialistic point of view, physical reality therefore must completely encompass + the top of each diagram." + + \append p "The second type of relationship describes the information flow between a subject and the layers of reality + the subject is immersed in. In a multi-reality experience the subject has access to multiple layers of reality and + their corresponding world models simultaneously.", br!, + "Depending on the specific experience, different types of and directions for information exchange + can exist between these layers and the subject's internal representation of the experience. + For the sake of this paper we distinguish only between ", (i "input"), " and ", (i "output"), " data flow (from the + perspective of the subject); categorized loosely as information the subject receives from the environment + (", (i "input"), ", e.g. visual stimuli) and actions the subject can take to influence the state of the world model + (", (i "output"), ", e.g. motor actions) respectively." + + \append p "In the following diagrams, information flow is visualized horizontally, in the region below the dashed line + at the bottom of the diagram. The subject's internal mental model and layer of reality are placed on the bottom left + side of the diagram. + The layers of reality that the subject experiences directly and that mirror it's internal representations are placed + on the far right. There may be multiple layers of reality sharing this space, visualized as a vertical stack of + layers. Since the subject must necessarily have a complete internal model of the multi-reality experience around + him to feel immersed, the subject's mental layer of reality must span the full height of all the layers visible + on the right side of the diagram.", br!, + "Information flow itself is now visualized concretely using arrows that cross layer boundaries in the lower part of + the diagram as described above. Arrows pointing leftwards denote ", (i "input"), " flow, whilst arrows pointing + rightwards denote ", (i "output"), "-directed information flow. In some cases information doesn't flow directly + between the layers the subject is directly aware of and the subject's internal representation and instead + traverses ", (i "intermediate layers"), " first." + + \append p "Before we take a look at some reality stacks corresponding to current VR and AR technology, + we can take a look at waking life as a baseline stack. To illustrate the format of the diagram we will compare it + to the stack corresponding to a dreaming state:" + + \append with figures! + \append addlabel "Waking Life", Diagram => + @mind! + @inout! + @phys! + + @next! + @phys '', 2 + @finish! + + \append addlabel "Dreaming", Diagram => + @mind! + @phys! + @finish! + + \append p "In both cases, the top of the diagram is fully occupied by the physical layer of reality, colored in green. + This is due to the fact that, according to the materialistic theory of mind, human consciousness owes its existance + to the physical and chemical dynamics of neurons in our brains. Therefore our mental reality must be considered + fully embedded in the physical reality, and consequently it may only appear underneath it in the diagram." + + \append p "During waking life, we concern ourselves mostly with the physical reality surrounding us. + For this reason the physical reality is placed in the lower right corner of the diagram as the layer holding the + external world model relevant to the subject. Information flows in both directions between the physical world model + and the subject's mental model, as denoted by the two white arrows: Information about the state of the world model + enter the subjects mind via the senses (top arrow, pointing leftwards), and choices the subject makes inside of and + based on his mental model can feed back into the physical layer through movements (lower arrow, pointing rightwards)." + + \append p "In the dreaming state on the other hand, the subject is unaware of the physical layer of reality, though + the mind remains embedded inside it. When dreaming, subjects' mental models don't depend on external models, hence + the mental layer of reality must be the only layer along the bottom of the diagram." + + \append with sect "Current Technologies" + \append p "Since recent technological advancements have enabled the development of VR and AR consumer devices, + AR and VR have been established as the potential next frontier of digital entertainment.", br!, + "As the names imply, the notion of reality is at the core of both technologies. + In the following section we will take a look at the respective stacks of both experience types:" + + \append with figures! + \append addlabel "VR", Diagram => + @mind! + @phys! + @inout nil, 1 + + @next! + @phys '', 2 + @inout nil, 1 + + @next! + @digi! + @phys '' + @finish! + + + \append addlabel "AR", Diagram => + @mind! + @inout nil, 1.25 + @inn nil, 0.5 + @phys! + + @next! + @phys '', 2 + @inn nil, .5 + + @next! + @digi nil, .5 + @phys '', 1.5 + @finish! + + \append p "In both cases we find the physical layer of reality as an ", (i "intermediate layer"), " between the mental + and digital layers. Actions taken by the subject have to be acted out physically (corresponding to the + information traversing the barrier between mental and physical reality) before they can be again digitized using + the various tracking and input technologies (which in turn carry the information across the boundary of the physical + and digital spaces)." + + \append p "The difference between AR and VR lies in the fact that in AR the subject experiences a mixture of the + digital and physical world models. This can be seen in the diagram, where we find that right of the diagram origin + and the mental model, the diagram splits and terminates in both layers: while information reaches the subject both + from the digital reality through the physical one, as well as directly from the physical reality, the subject only + directly manipulates state in the physical reality." + + \append p "The data conversions necessary at layer boundaries incur at the least losses in quality and accuracy of + information for purely technical reasons. However ", (i "intermediate layers"), " come at a cost larger than just + an additional step of conversion: + For information to flow through a layer, it must be encodable within that layer’s world model. + This means that the 'weakest link' in a given reality stack determines the upper bound of information possible to + encode within said stack and thereby limits the overall expressivity of the stack.", br!, + "As a practical example we can consider creating an hypothetical VR application that allows users to traverse a + large virtual space by flying. While the human mind is perfectly capable of imagining to fly and control the motion + appropriately, it is extremely hard to devise and implement a satisfying setup and control scheme because the + physical body of the user needs to be taken into account and it, unlike the corresponding representations in the + mental and digital world models, cannot float around freely." + + \append with sect "Future Developments" + \append p "In the previous section we found that the presence of the physical layer in the information path of + VR and AR stacks limits the experience as a whole. It follows that the removal of that indirection should be + an obvious goal for future developments:" + + \append figures addlabel "holy grail of VR: 'The Matrix'", Diagram => + @mind! + @inout! + @phys! + + @next! + @digi! + @phys '' + @finish! + + \append p "In the action movie 'The Matrix' ", (ref 'Matrix'), ", users of the titular VR environment interface with it + by plugging cables into implanted sockets that connect the simulation directly to their central nervous system.", br!, + "While these cables and implanted devices are physical devices, they don't constitute the presence of the + physical layer of reality in the information path because while they do transmit information, the information + remains in either the encoding of the mental model (neural firing patterns) or the encoding of the digital model + (e.g. a numeric encoding of a player character's movement in digital space) and the conversion is made directly + between those two - the data never assumes the native encoding of the physical layer (e.g. as a physical motion)." + + \append p "While we are currently far from being able to read arbitrary high-level information from the brain + or to synthesize sensual input in human perception by bypassing the sensory organs, brain-computer interfaces (BCI) + are a very active area of research with high hopes for comparable achievements in the near future." + + \append p "Applying this same step of removing the physical layer of reality from AR, we end up with something similar + to the nano-particle drug in ", (i "Nexus"), " ", (ref 'Naam'), ". However this does not grant the user a similar + amount of control over his experience as the holy grail of VR does, since the user and the physical part of the + environment remain bound by the physical layer of reality's laws.", br!, + "Instead the holy grail of AR is reached with the creation of a god machine that can manipulate the state of the + physical world according to the user's wishes. In this way the digital and physical realities become unified and + fully 'augmented'." + + \append with figures! + \append addlabel "'Nexus'", Diagram => + @mind! + @inout nil, 0.75 + @inout nil, 1.25 + @phys! + + @next! + @digi nil, .5 + @phys '', 1.5 + @finish! + + \append addlabel "holy grail of AR: 'Deus Machina'", Diagram => + col = '#92807c' + + @mind! + @inout! + @block col, '' + + @next! + @block col, '', 2 + @svg\plain('phys + digi')\attr(o fill: 'white', 'font-size': '14px')\move 6, -2 * GRID_H + @finish! + + \append p "Despite the similarities of VR and AR, the two can be considered polar opposites, as becomes evident when + we compare their respective utopian implementations: they share the goal of allowing us to experience realities + different from the one we naturally inhabit, but while VR seeks to accomplish this by creating a new, nested reality + inside ours, thus giving us full control over it. + AR, on the other hand, is instead an attempt to retrofit our specific needs directly into the very reality we exist + in.", br!, + "This is in direct contrast with the popular notion of the 'reality-virtuality continuum' ", (ref 'Milgram'), ": + the reality-virtuality continuum places common reality and VR (virtuality) as the two extreme poles, while AR + is represented as an intermediate state between the two. Here however we propose to view instead AR and VR as the + respective poles and find instead reality at the centerpoint, where the two opposing influences 'cancel out'." + + \append with sect "Conclusion and Further Work" + \append p "In this paper we have proposed a taxonomy and visualization style for multi-reality experiences, as well + as demonstrated it's flexibility by applying them as examples. Through the application of the proposed theory, + we have also gained a new and contrasting view on preceding work such as the reality-virtuality-continuum. + We have also found that the taxonomy can be used outside the research field of media studies and its use may extend + as far as philosophy of consciousness (see Appendix below)." + + \append p "Further research could enhance the proposed theory with better and more concrete definitions. + In the future, the proposed taxonomy might be used to create a more extensive and complete classification + of reality stacks and to analyse the relationships between them." + + \append with sect 'References' + \append references! + + \append with sect "Appendix: Relation to Theories of Mind" + \append p "This paper starts from a deeply materialistic point of view that borders on microphysicalism. + However it should be noted that the diagram style introduced above lends itself also to display other + philosophical theories of mind. As an example, the following graphics show a typical VR stack as interpreted by + Materialism, Cartesian Dualism and Solipsism respectively:" + + \append with figures! + \append addlabel "VR in Materialism", Diagram => + @mind! + @inout nil, 1 + @phys! + + @next! + @phys '', 2 + @inout nil, 1 + + @next! + @digi! + @phys '' + + @finish! + + \append addlabel "VR in Solipsism", Diagram => + @mind nil, 2 + @inout nil, 1 + + @next! + @digi! + @mind '' + @finish! + + \append addlabel "VR in Cartesian Dualism", Diagram => + @mind nil, 2 + @inout nil, 1 + @next! + + @phys nil, 2 + @inout nil, 1 + @next! + + @digi! + @phys '' + @finish! + + \append p "However these philosophical theories of minds also constitute reality stacks by themselves and as such can + be compared directly:" + + \append with figures! + \append addlabel "Materialism", Diagram => + @mind! + @inout! + @phys! + + @next! + @phys '', 2 + @finish! + + \append addlabel "Solipsism", Diagram => + @mind! + @finish! + + \append addlabel "Cartesian Dualism", Diagram => + @mind! + @inout! + @next! + + @phys! + @finish! + +_content diff --git a/root/articles/realities/text$moonscript -> mmm$dom.moon b/root/articles/realities/text$moonscript -> mmm$dom.moon deleted file mode 100644 index 7689dbb..0000000 --- a/root/articles/realities/text$moonscript -> mmm$dom.moon +++ /dev/null @@ -1,542 +0,0 @@ -import elements from require 'mmm.component' -import h1, h2, p, a, i, div, ol, li, br, hr, span, button, section, article from elements - -_content = div! -append = _content\append - -if MODE == 'SERVER' - export ^ - class Diagram - style = { - display: 'inline-block', - width: '150px', - height: '80px', - 'line-height': '80px', - color: '#fff', - background: '#666', - } - - @id = 1 - new: (@func) => - @id = "diagram-#{@@id}" - @@id += 1 - - render: => - rplc = with div id: @id, :style - \append '(diagram goes here)' - -- \append "" - rplc\render! - -if MODE == 'CLIENT' - export ^ - export o - eval = js.global\eval - GRID_W = 50 - GRID_H = 40 - - SVG = - doc: eval "(function() { return SVG(document.createElement('svg')); })", - G: eval "(function() { return new SVG.G(); })", - setmetatable SVG, __call: => @doc! - - o = do - mkobj = eval "(function () { return {}; })" - (tbl) -> - with obj = mkobj! - for k,v in pairs(tbl) - obj[k] = v - - class Diagram - new: (f) => - @svg = SVG! - @arrows = SVG.G! - @width, @height = 0, 0 - @y = 0 - - f @ - - txtattr = o { - fill: 'white', - 'font-size': '14px', - 'text-anchor': 'middle', - } - block: (color, label, h=1) => - @svg\add with SVG.G! - with \rect GRID_W, h * GRID_H - \attr o fill: color - if label - with \plain label - \move GRID_W/2, 0 - \attr txtattr - - \move @width * GRID_W, (@y + h) * -GRID_H - @y += h - if @y > @height - @height = @y - - arrattr = o { - fill: 'white', - 'font-size': '18px', - 'text-anchor': 'middle', - } - arrow: (char, x, y) => - with @arrows\plain char - \attr arrattr - \move (x + 1) * GRID_W, (y - 0.5) * -GRID_H - 11 - - -- inout: (x=@width, y=@y) => @arrow '⇋', x, y -- U+21CB - -- inn: (x=@width, y=@y) => @arrow '↼', x, y+0.25 -- U+21BC - -- out: (x=@width, y=@y) => @arrow '⇁', x, y-0.25 -- U+21C1 - inout: (x=@width, y=@y) => @arrow '⇆', x, y -- U+21C6 - inn: (x=@width, y=@y) => @arrow '←', x, y+0.25 -- U+2190 - out: (x=@width, y=@y) => @arrow '→', x, y-0.25 -- U+2192 - - mind: (label='mind', ...) => @block '#fac710', label, ... - phys: (label='phys', ...) => @block '#8fd13f', label, ... - digi: (label='digi', ...) => @block '#9510ac', label, ... - - next: => - @y = 0 - @width += 1 - - finish: => - return if @node - @svg\add @arrows - - @width += 1 - w, h = @width * GRID_W, @height * GRID_H - - l = GRID_W / 6.5 - @svg\add with @svg\line 0, -GRID_H, w, -GRID_H - \stroke o width: 2, color: '#ffffff', dasharray: "#{l}, #{l}" - - @svg\size w, h - @svg\viewbox 0, -h, w, h - @node = @svg.node - -addlabel = (label, diagram) -> - with div style: { display: 'inline-block', margin: '20px', 'text-align': 'center' } - \append diagram - \append div label - -figures = do - style = - display: 'flex' - 'align-items': 'flex-end' - 'justify-content': 'space-evenly' - (...) -> div { :style, ... } - -sources = do - short = => "#{@id} #{@year}" - long = => @names, " (#{@year}): ", (i @title), ", #{@published}" - { - { - id: 'Milgram', - title: 'Augmented Reality: A class of displays on the reality-virtuality continuum', - published: 'in SPIE Vol. 2351', - names: 'P. Milgram, H. Takemura, A. Utsumi, F. Kishino', - year: 1994 - :long, :short, - }, - { - id: 'Marsh', - title: 'Nested Immersion: Describing and Classifying Augmented Virtual Reality', - published: 'IEEE Virtual Reality Conference 2015', - names: 'W. Marsh, F. Mérienne', - year: 2015 - :long, :short, - }, - { - id: 'Billinghurst', - title: 'The MagicBook: a transitional AR interface', - published: 'in Computer & Graphics 25', - names: 'M. Billinghurst, H. Kato, I. Poupyrev', - year: 2001, - :long, :short, - }, - { - id: 'Matrix', - title: 'The Matrix', - year: 1999, - names: 'L. Wachowski, A. Wachowski', - long: => @names, " (#{@year}): ", (i @title), " (movie)" - short: => tostring @year - }, - { - id: 'Naam', - title: 'Nexus', - published: 'Angry Robot (novel)', - names: 'R. Naam', - year: 2012, - :long, :short, - } - } - -ref = do - fmt = (id) -> - - local src - for _src in *sources - if _src.id == id - src = _src - break - - if src - a { src\short!, href: "##{src.id}" } - else - span id - - ref = (...) -> - refs = { ... } - with span "(", fmt refs[1] - for i=2, #refs - \append ", " - \append fmt refs[i] - \append ")" - -references = -> - with ol! - for src in *sources - \append li { id: src.id, src\long! } - -sect = (label) -> - with section style: 'page-break-inside': 'avoid' - \append h2 label - -append with article style: { margin: 'auto', 'max-width': '750px' } - \append div 'Sol Bekic', style: 'text-align': 'right' - - \append h1 { - style: { 'text-align': 'center', 'font-size': '2em' }, - "Reality Stacks", - div "a Taxonomy for Multi-Reality Experiences", style: 'font-size': '0.6em' - } - - \append with sect "Abstract" - \append p "With the development of mixed-reality experiences and the corresponding interface devices - multiple frameworks for classification of these experiences have been proposed. However these past - attempts have mostly been developed alongside and with the intent of capturing specific projects ", - (ref 'Marsh', 'Billinghurst'), " or are nevertheless very focused on existing methods and technologies ", - (ref 'Milgram'), ". The existing taxonomies also all assume physical reality as a fixpoint and constant and are - thereby not suited to describe many fictional mixed-reality environments and altered states of consciousness. - In this paper we describe a new model for describing such experiences and examplify it's use with currently - existing as well as idealized technologies from popular culture." - - \append with sect "Terminology" - \append p "We propose the following terms and definitions that will be used extensively for the remainder of the paper:" - for definition in *{ - { "layer of reality": "a closed system consisting of a world model and a set of rules or dynamics operating on and - constraining said model." }, - { "world model": "describes a world state containing objects, agents and/or concepts on an arbitrary abstraction level." }, - '------', - { "reality stack": "structure consisting of all layers of reality encoding an agent's interaction with his environment - in their world model at a given moment, as well as all layers supporting these respectively." }, - '------', - { "physical reality": "layer of reality defined by physical matter and the physical laws acting upon it. - While the emergent phenomena of micro- and macro physics as well as layers of social existence etc. may be seen - as separate layers, for the purpose of this paper we will group these together under the term of physical reality." }, - { "mental reality": "layer of reality perceived and processed by the brain of a human agent." }, - { "digital reality": "layer of reality created and simulated by a digital system, e.g. a virtual reality game." }, - { "phys, mind, digi": "abbreviations for physical, mental and digital reality respectively." }, - } - if 'string' == type definition - \append hr! - continue - \append with div style: { 'margin-left': '2rem' } - term = next definition - \append span term, style: { - display: 'inline-block', - 'margin-left': '-2rem', - 'font-weight': 'bold', - 'min-width': '140px' - } - \append span definition[term] - - \append with sect "Introduction" - \append p "We identify two different types of relationships between layers in multi-reality environments. - The first is layer nesting. Layer nesting describes how some layers are contained in other layers; i.e. they exist - within and can be represented fully by the parent layer's world model and the child layer's rules emerge natively from - the parent layer's dynamics. Layer nesting is visualized on the vertical axis in the following diagrams. - For each layer of reality on the bottom of the diagram the nested parent layers can be found by tracing a line upwards - to the top of the diagram. Following a materialistic point of view, physical reality therefore must completely encompass - the top of each diagram." - - \append p "The second type of relationship describes the information flow between a subject and the layers of reality - the subject is immersed in. In a multi-reality experience the subject has access to multiple layers of reality and - their corresponding world models simultaneously.", br!, - "Depending on the specific experience, different types of and directions for information exchange - can exist between these layers and the subject's internal representation of the experience. - For the sake of this paper we distinguish only between ", (i "input"), " and ", (i "output"), " data flow (from the - perspective of the subject); categorized loosely as information the subject receives from the environment - (", (i "input"), ", e.g. visual stimuli) and actions the subject can take to influence the state of the world model - (", (i "output"), ", e.g. motor actions) respectively." - - \append p "In the following diagrams, information flow is visualized horizontally, in the region below the dashed line - at the bottom of the diagram. The subject's internal mental model and layer of reality are placed on the bottom left - side of the diagram. - The layers of reality that the subject experiences directly and that mirror it's internal representations are placed - on the far right. There may be multiple layers of reality sharing this space, visualized as a vertical stack of - layers. Since the subject must necessarily have a complete internal model of the multi-reality experience around - him to feel immersed, the subject's mental layer of reality must span the full height of all the layers visible - on the right side of the diagram.", br!, - "Information flow itself is now visualized concretely using arrows that cross layer boundaries in the lower part of - the diagram as described above. Arrows pointing leftwards denote ", (i "input"), " flow, whilst arrows pointing - rightwards denote ", (i "output"), "-directed information flow. In some cases information doesn't flow directly - between the layers the subject is directly aware of and the subject's internal representation and instead - traverses ", (i "intermediate layers"), " first." - - \append p "Before we take a look at some reality stacks corresponding to current VR and AR technology, - we can take a look at waking life as a baseline stack. To illustrate the format of the diagram we will compare it - to the stack corresponding to a dreaming state:" - - \append with figures! - \append addlabel "Waking Life", Diagram => - @mind! - @inout! - @phys! - - @next! - @phys '', 2 - @finish! - - \append addlabel "Dreaming", Diagram => - @mind! - @phys! - @finish! - - \append p "In both cases, the top of the diagram is fully occupied by the physical layer of reality, colored in green. - This is due to the fact that, according to the materialistic theory of mind, human consciousness owes its existance - to the physical and chemical dynamics of neurons in our brains. Therefore our mental reality must be considered - fully embedded in the physical reality, and consequently it may only appear underneath it in the diagram." - - \append p "During waking life, we concern ourselves mostly with the physical reality surrounding us. - For this reason the physical reality is placed in the lower right corner of the diagram as the layer holding the - external world model relevant to the subject. Information flows in both directions between the physical world model - and the subject's mental model, as denoted by the two white arrows: Information about the state of the world model - enter the subjects mind via the senses (top arrow, pointing leftwards), and choices the subject makes inside of and - based on his mental model can feed back into the physical layer through movements (lower arrow, pointing rightwards)." - - \append p "In the dreaming state on the other hand, the subject is unaware of the physical layer of reality, though - the mind remains embedded inside it. When dreaming, subjects' mental models don't depend on external models, hence - the mental layer of reality must be the only layer along the bottom of the diagram." - - \append with sect "Current Technologies" - \append p "Since recent technological advancements have enabled the development of VR and AR consumer devices, - AR and VR have been established as the potential next frontier of digital entertainment.", br!, - "As the names imply, the notion of reality is at the core of both technologies. - In the following section we will take a look at the respective stacks of both experience types:" - - \append with figures! - \append addlabel "VR", Diagram => - @mind! - @phys! - @inout nil, 1 - - @next! - @phys '', 2 - @inout nil, 1 - - @next! - @digi! - @phys '' - @finish! - - - \append addlabel "AR", Diagram => - @mind! - @inout nil, 1.25 - @inn nil, 0.5 - @phys! - - @next! - @phys '', 2 - @inn nil, .5 - - @next! - @digi nil, .5 - @phys '', 1.5 - @finish! - - \append p "In both cases we find the physical layer of reality as an ", (i "intermediate layer"), " between the mental - and digital layers. Actions taken by the subject have to be acted out physically (corresponding to the - information traversing the barrier between mental and physical reality) before they can be again digitized using - the various tracking and input technologies (which in turn carry the information across the boundary of the physical - and digital spaces)." - - \append p "The difference between AR and VR lies in the fact that in AR the subject experiences a mixture of the - digital and physical world models. This can be seen in the diagram, where we find that right of the diagram origin - and the mental model, the diagram splits and terminates in both layers: while information reaches the subject both - from the digital reality through the physical one, as well as directly from the physical reality, the subject only - directly manipulates state in the physical reality." - - \append p "The data conversions necessary at layer boundaries incur at the least losses in quality and accuracy of - information for purely technical reasons. However ", (i "intermediate layers"), " come at a cost larger than just - an additional step of conversion: - For information to flow through a layer, it must be encodable within that layer’s world model. - This means that the 'weakest link' in a given reality stack determines the upper bound of information possible to - encode within said stack and thereby limits the overall expressivity of the stack.", br!, - "As a practical example we can consider creating an hypothetical VR application that allows users to traverse a - large virtual space by flying. While the human mind is perfectly capable of imagining to fly and control the motion - appropriately, it is extremely hard to devise and implement a satisfying setup and control scheme because the - physical body of the user needs to be taken into account and it, unlike the corresponding representations in the - mental and digital world models, cannot float around freely." - - \append with sect "Future Developments" - \append p "In the previous section we found that the presence of the physical layer in the information path of - VR and AR stacks limits the experience as a whole. It follows that the removal of that indirection should be - an obvious goal for future developments:" - - \append figures addlabel "holy grail of VR: 'The Matrix'", Diagram => - @mind! - @inout! - @phys! - - @next! - @digi! - @phys '' - @finish! - - \append p "In the action movie 'The Matrix' ", (ref 'Matrix'), ", users of the titular VR environment interface with it - by plugging cables into implanted sockets that connect the simulation directly to their central nervous system.", br!, - "While these cables and implanted devices are physical devices, they don't constitute the presence of the - physical layer of reality in the information path because while they do transmit information, the information - remains in either the encoding of the mental model (neural firing patterns) or the encoding of the digital model - (e.g. a numeric encoding of a player character's movement in digital space) and the conversion is made directly - between those two - the data never assumes the native encoding of the physical layer (e.g. as a physical motion)." - - \append p "While we are currently far from being able to read arbitrary high-level information from the brain - or to synthesize sensual input in human perception by bypassing the sensory organs, brain-computer interfaces (BCI) - are a very active area of research with high hopes for comparable achievements in the near future." - - \append p "Applying this same step of removing the physical layer of reality from AR, we end up with something similar - to the nano-particle drug in ", (i "Nexus"), " ", (ref 'Naam'), ". However this does not grant the user a similar - amount of control over his experience as the holy grail of VR does, since the user and the physical part of the - environment remain bound by the physical layer of reality's laws.", br!, - "Instead the holy grail of AR is reached with the creation of a god machine that can manipulate the state of the - physical world according to the user's wishes. In this way the digital and physical realities become unified and - fully 'augmented'." - - \append with figures! - \append addlabel "'Nexus'", Diagram => - @mind! - @inout nil, 0.75 - @inout nil, 1.25 - @phys! - - @next! - @digi nil, .5 - @phys '', 1.5 - @finish! - - \append addlabel "holy grail of AR: 'Deus Machina'", Diagram => - col = '#92807c' - - @mind! - @inout! - @block col, '' - - @next! - @block col, '', 2 - @svg\plain('phys + digi')\attr(o fill: 'white', 'font-size': '14px')\move 6, -2 * GRID_H - @finish! - - \append p "Despite the similarities of VR and AR, the two can be considered polar opposites, as becomes evident when - we compare their respective utopian implementations: they share the goal of allowing us to experience realities - different from the one we naturally inhabit, but while VR seeks to accomplish this by creating a new, nested reality - inside ours, thus giving us full control over it. - AR, on the other hand, is instead an attempt to retrofit our specific needs directly into the very reality we exist - in.", br!, - "This is in direct contrast with the popular notion of the 'reality-virtuality continuum' ", (ref 'Milgram'), ": - the reality-virtuality continuum places common reality and VR (virtuality) as the two extreme poles, while AR - is represented as an intermediate state between the two. Here however we propose to view instead AR and VR as the - respective poles and find instead reality at the centerpoint, where the two opposing influences 'cancel out'." - - \append with sect "Conclusion and Further Work" - \append p "In this paper we have proposed a taxonomy and visualization style for multi-reality experiences, as well - as demonstrated it's flexibility by applying them as examples. Through the application of the proposed theory, - we have also gained a new and contrasting view on preceding work such as the reality-virtuality-continuum. - We have also found that the taxonomy can be used outside the research field of media studies and its use may extend - as far as philosophy of consciousness (see Appendix below)." - - \append p "Further research could enhance the proposed theory with better and more concrete definitions. - In the future, the proposed taxonomy might be used to create a more extensive and complete classification - of reality stacks and to analyse the relationships between them." - - \append with sect 'References' - \append references! - - \append with sect "Appendix: Relation to Theories of Mind" - \append p "This paper starts from a deeply materialistic point of view that borders on microphysicalism. - However it should be noted that the diagram style introduced above lends itself also to display other - philosophical theories of mind. As an example, the following graphics show a typical VR stack as interpreted by - Materialism, Cartesian Dualism and Solipsism respectively:" - - \append with figures! - \append addlabel "VR in Materialism", Diagram => - @mind! - @inout nil, 1 - @phys! - - @next! - @phys '', 2 - @inout nil, 1 - - @next! - @digi! - @phys '' - - @finish! - - \append addlabel "VR in Solipsism", Diagram => - @mind nil, 2 - @inout nil, 1 - - @next! - @digi! - @mind '' - @finish! - - \append addlabel "VR in Cartesian Dualism", Diagram => - @mind nil, 2 - @inout nil, 1 - @next! - - @phys nil, 2 - @inout nil, 1 - @next! - - @digi! - @phys '' - @finish! - - \append p "However these philosophical theories of minds also constitute reality stacks by themselves and as such can - be compared directly:" - - \append with figures! - \append addlabel "Materialism", Diagram => - @mind! - @inout! - @phys! - - @next! - @phys '', 2 - @finish! - - \append addlabel "Solipsism", Diagram => - @mind! - @finish! - - \append addlabel "Cartesian Dualism", Diagram => - @mind! - @inout! - @next! - - @phys! - @finish! - -_content diff --git a/root/meta/mmm.component/text$moonscript -> fn -> mmm$dom.moon b/root/meta/mmm.component/text$moonscript -> fn -> mmm$dom.moon index ca9a13f..65556c3 100644 --- a/root/meta/mmm.component/text$moonscript -> fn -> mmm$dom.moon +++ b/root/meta/mmm.component/text$moonscript -> fn -> mmm$dom.moon @@ -15,10 +15,6 @@ demo = tohtml example! div the_code, div demo, class: 'example' - -raw_find = (fileder, key_pat) -> - for key, val in pairs fileder.facets - return val if key\tostring!\match key_pat => example = (name) -> diff --git a/root/static/mmm/application$lua.lua b/root/static/mmm/application$lua.lua new file mode 100644 index 0000000..5a6dc53 --- /dev/null +++ b/root/static/mmm/application$lua.lua @@ -0,0 +1,2701 @@ +local p = package.preload +if not p["mmm.canvasapp"] then p["mmm.canvasapp"] = load("local window = js.global\ +local js = require('js')\ +local a, canvas, div, button, script\ +do\ + local _obj_0 = require('mmm.dom')\ + a, canvas, div, button, script = _obj_0.a, _obj_0.canvas, _obj_0.div, _obj_0.button, _obj_0.script\ +end\ +local CanvasApp\ +do\ + local _class_0\ + local _base_0 = {\ + width = 500,\ + height = 400,\ + update = function(self, dt)\ + self.time = self.time + dt\ + if self.length and self.time > self.length then\ + self.time = self.time - self.length\ + return true\ + end\ + end,\ + render = function(self, fps)\ + if fps == nil then\ + fps = 60\ + end\ + assert(self.length, 'cannot render CanvasApp without length set')\ + self.paused = true\ + local actual_render\ + actual_render = function()\ + local writer = js.new(window.Whammy.Video, fps)\ + local doFrame\ + doFrame = function()\ + local done = self:update(1 / fps)\ + self.ctx:resetTransform()\ + self:draw()\ + writer:add(self.canvas)\ + if done or self.time >= self.length then\ + local blob = writer:compile()\ + local name = tostring(self.__class.__name) .. \"_\" .. tostring(fps) .. \"fps.webm\"\ + return self.node.lastChild:appendChild(a(name, {\ + download = name,\ + href = window.URL:createObjectURL(blob)\ + }))\ + else\ + return window:setTimeout(doFrame)\ + end\ + end\ + self.time = 0\ + return doFrame()\ + end\ + if window.Whammy then\ + return actual_render()\ + else\ + window.global = window.global or window\ + return document.body:appendChild(script({\ + onload = actual_render,\ + src = 'https://cdn.jsdelivr.net/npm/whammy@0.0.1/whammy.min.js'\ + }))\ + end\ + end\ + }\ + _base_0.__index = _base_0\ + _class_0 = setmetatable({\ + __init = function(self, show_menu, paused)\ + if show_menu == nil then\ + show_menu = false\ + end\ + self.paused = paused\ + self.canvas = canvas({\ + width = self.width,\ + height = self.height\ + })\ + self.ctx = self.canvas:getContext('2d')\ + self.time = 0\ + self.canvas.tabIndex = 0\ + self.canvas:addEventListener('click', function(_, e)\ + return self.click and self:click(e.offsetX, e.offsetY, e.button)\ + end)\ + self.canvas:addEventListener('keydown', function(_, e)\ + return self.keydown and self:keydown(e.key, e.code)\ + end)\ + local lastMillis = window.performance:now()\ + local animationFrame\ + animationFrame = function(_, millis)\ + self:update((millis - lastMillis) / 1000)\ + self.ctx:resetTransform()\ + self:draw()\ + lastMillis = millis\ + if not self.paused then\ + return window:setTimeout((function()\ + return window:requestAnimationFrame(animationFrame)\ + end), 0)\ + end\ + end\ + window:requestAnimationFrame(animationFrame)\ + if show_menu then\ + self.node = div({\ + className = 'canvas_app',\ + self.canvas,\ + div({\ + className = 'overlay',\ + button('render 30fps', {\ + onclick = function()\ + return self:render(30)\ + end\ + }),\ + button('render 60fps', {\ + onclick = function()\ + return self:render(60)\ + end\ + })\ + })\ + })\ + else\ + self.node = self.canvas\ + end\ + end,\ + __base = _base_0,\ + __name = \"CanvasApp\"\ + }, {\ + __index = _base_0,\ + __call = function(cls, ...)\ + local _self_0 = setmetatable({}, _base_0)\ + cls.__init(_self_0, ...)\ + return _self_0\ + end\ + })\ + _base_0.__class = _class_0\ + CanvasApp = _class_0\ +end\ +return {\ + CanvasApp = CanvasApp\ +}\ +", "mmm/canvasapp.client.lua") end +if not p["mmm.color"] then p["mmm.color"] = load("local rgb\ +rgb = function(r, g, b)\ + if 'table' == type(r) then\ + r, g, b = table.unpack(r)\ + end\ + return \"rgb(\" .. tostring(r * 255) .. \", \" .. tostring(g * 255) .. \", \" .. tostring(b * 255) .. \")\"\ +end\ +local rgba\ +rgba = function(r, g, b, a)\ + if 'table' == type(r) then\ + r, g, b, a = table.unpack(r)\ + end\ + return \"rgba(\" .. tostring(r * 255) .. \", \" .. tostring(g * 255) .. \", \" .. tostring(b * 255) .. \", \" .. tostring(a or 1) .. \")\"\ +end\ +local hsl\ +hsl = function(h, s, l)\ + if 'table' == type(h) then\ + h, s, l = table.unpack(h)\ + end\ + return \"hsl(\" .. tostring(h * 360) .. \", \" .. tostring(s * 100) .. \"%, \" .. tostring(l * 100) .. \"%)\"\ +end\ +local hsla\ +hsla = function(h, s, l, a)\ + if 'table' == type(h) then\ + h, s, l, a = table.unpack(h)\ + end\ + return \"hsla(\" .. tostring(h * 360) .. \", \" .. tostring(s * 100) .. \"%, \" .. tostring(l * 100) .. \"%, \" .. tostring(a or 1) .. \")\"\ +end\ +return {\ + rgb = rgb,\ + rgba = rgba,\ + hsl = hsl,\ + hsla = hsla\ +}\ +", "mmm/color.lua") end +if not p["mmm.highlighting"] then p["mmm.highlighting"] = load("local code\ +code = require('mmm.dom').code\ +local highlight\ +local trim\ +trim = function(str)\ + return str:match('^ *(..-) *$')\ +end\ +if MODE == 'SERVER' then\ + highlight = function(lang, str)\ + assert(str, 'no string to highlight')\ + return code((trim(str)), {\ + class = \"hljs lang-\" .. tostring(lang)\ + })\ + end\ +else\ + highlight = function(lang, str)\ + assert(str, 'no string to highlight')\ + local result = window.hljs:highlight(lang, (trim(str)), true)\ + do\ + local _with_0 = code({\ + class = \"hljs lang-\" .. tostring(lang)\ + })\ + _with_0.innerHTML = result.value\ + return _with_0\ + end\ + end\ +end\ +local languages = setmetatable({ }, {\ + __index = function(self, name)\ + do\ + local val\ + val = function(str)\ + return highlight(name, str)\ + end\ + self[name] = val\ + return val\ + end\ + end\ +})\ +return {\ + highlight = highlight,\ + languages = languages\ +}\ +", "mmm/highlighting.lua") end +if not p["mmm"] then p["mmm"] = load("window = js.global\ +local console\ +document, console = window.document, window.console\ +MODE = 'CLIENT'\ +local deep_tostring\ +deep_tostring = function(tbl, space)\ + if space == nil then\ + space = ''\ + end\ + if 'userdata' == type(tbl) then\ + return tbl\ + end\ + local buf = space .. tostring(tbl)\ + if not ('table' == type(tbl)) then\ + return buf\ + end\ + buf = buf .. ' {\\n'\ + for k, v in pairs(tbl) do\ + buf = buf .. tostring(space) .. \" [\" .. tostring(k) .. \"]: \" .. tostring(deep_tostring(v, space .. ' ')) .. \"\\n\"\ + end\ + buf = buf .. tostring(space) .. \"}\"\ + return buf\ +end\ +print = function(...)\ + local contents\ + do\ + local _accum_0 = { }\ + local _len_0 = 1\ + local _list_0 = {\ + ...\ + }\ + for _index_0 = 1, #_list_0 do\ + local v = _list_0[_index_0]\ + _accum_0[_len_0] = deep_tostring(v)\ + _len_0 = _len_0 + 1\ + end\ + contents = _accum_0\ + end\ + return console:log(table.unpack(contents))\ +end\ +warn = function(...)\ + local contents\ + do\ + local _accum_0 = { }\ + local _len_0 = 1\ + local _list_0 = {\ + ...\ + }\ + for _index_0 = 1, #_list_0 do\ + local v = _list_0[_index_0]\ + _accum_0[_len_0] = deep_tostring(v)\ + _len_0 = _len_0 + 1\ + end\ + contents = _accum_0\ + end\ + return console:warn(table.unpack(contents))\ +end\ +package.path = '/?.lua;/?/init.lua'\ +do\ + local _require = require\ + relative = function(base, sub)\ + if not ('number' == type(sub)) then\ + sub = 0\ + end\ + for i = 1, sub do\ + base = base:match('^(.*)%.%w+$')\ + end\ + return function(name, x)\ + if '.' == name:sub(1, 1) then\ + name = base .. name\ + end\ + return _require(name)\ + end\ + end\ +end\ +if on_load then\ + for _index_0 = 1, #on_load do\ + local f = on_load[_index_0]\ + f()\ + end\ +end\ +on_load = setmetatable({ }, {\ + __newindex = function(t, k, v)\ + rawset(t, k, v)\ + return v()\ + end\ +})\ +", "mmm/init.client.lua") end +if not p["mmm.ordered"] then p["mmm.ordered"] = load("local sort\ +sort = function(t, order_fn, only_strings)\ + do\ + local index\ + do\ + local _accum_0 = { }\ + local _len_0 = 1\ + for k, v in pairs(t) do\ + if (not only_strings) or 'string' == type(k) then\ + _accum_0[_len_0] = k\ + _len_0 = _len_0 + 1\ + end\ + end\ + index = _accum_0\ + end\ + table.sort(index, order_fn)\ + return index\ + end\ +end\ +local onext\ +onext = function(state, key)\ + state.i = state.i + state.step\ + local t, index, i\ + t, index, i = state.t, state.index, state.i\ + do\ + key = index[i]\ + if key then\ + return key, t[key]\ + end\ + end\ +end\ +local opairs\ +opairs = function(t, order_fn, only_strings)\ + if only_strings == nil then\ + only_strings = false\ + end\ + local state = {\ + t = t,\ + i = 0,\ + step = 1,\ + index = sort(t, order_fn, only_strings)\ + }\ + return onext, state, nil\ +end\ +local ropairs\ +ropairs = function(t, order_fn, only_strings)\ + if only_strings == nil then\ + only_strings = false\ + end\ + local index = sort(t, order_fn, only_strings)\ + local state = {\ + t = t,\ + index = index,\ + i = #index + 1,\ + step = -1\ + }\ + return onext, state, nil\ +end\ +return {\ + onext = onext,\ + opairs = opairs,\ + ropairs = ropairs\ +}\ +", "mmm/ordered.lua") end +if not p["mmm.mmmfs.browser"] then p["mmm.mmmfs.browser"] = load("local require = relative(..., 1)\ +local Key\ +Key = require('.fileder').Key\ +local converts, get_conversions, apply_conversions\ +do\ + local _obj_0 = require('.conversion')\ + converts, get_conversions, apply_conversions = _obj_0.converts, _obj_0.get_conversions, _obj_0.apply_conversions\ +end\ +local ReactiveVar, get_or_create, text, elements\ +do\ + local _obj_0 = require('mmm.component')\ + ReactiveVar, get_or_create, text, elements = _obj_0.ReactiveVar, _obj_0.get_or_create, _obj_0.text, _obj_0.elements\ +end\ +local pre, div, nav, span, button, a, code, select, option\ +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\ +local languages\ +languages = require('mmm.highlighting').languages\ +local keep\ +keep = function(var)\ + local last = var:get()\ + return var:map(function(val)\ + last = val or last\ + return last\ + end)\ +end\ +local code_cast\ +code_cast = function(lang)\ + return {\ + inp = \"text/\" .. tostring(lang) .. \".*\",\ + out = 'mmm/dom',\ + transform = function(val)\ + return languages[lang](val)\ + end\ + }\ +end\ +local casts = {\ + code_cast('javascript'),\ + code_cast('moonscript'),\ + code_cast('lua'),\ + code_cast('markdown'),\ + code_cast('html'),\ + {\ + inp = 'text/plain',\ + out = 'mmm/dom',\ + transform = function(val)\ + return text(val)\ + end\ + },\ + {\ + inp = 'URL.*',\ + out = 'mmm/dom',\ + transform = function(href)\ + return span(a((code(href)), {\ + href = href\ + }))\ + end\ + }\ +}\ +for _index_0 = 1, #converts do\ + local convert = converts[_index_0]\ + table.insert(casts, convert)\ +end\ +local Browser\ +do\ + local _class_0\ + local err_and_trace, default_convert\ + local _base_0 = {\ + get_content = function(self, prop, err, convert)\ + if err == nil then\ + err = self.error\ + end\ + if convert == nil then\ + convert = default_convert\ + end\ + local clear_error\ + clear_error = function()\ + if MODE == 'CLIENT' then\ + return err:set()\ + end\ + end\ + local disp_error\ + disp_error = function(msg)\ + if MODE == 'CLIENT' then\ + err:set(pre(msg))\ + end\ + warn(\"ERROR rendering content: \" .. tostring(msg))\ + return nil\ + end\ + local active = self.active:get()\ + if not (active) then\ + return disp_error(\"fileder not found!\")\ + end\ + if not (prop) then\ + return disp_error(\"facet not found!\")\ + end\ + local ok, res = xpcall(convert, err_and_trace, active, prop)\ + if MODE == 'CLIENT' then\ + document.body.classList:remove('loading')\ + end\ + if ok and res then\ + clear_error()\ + return res\ + elseif ok then\ + return div(\"[no conversion path to \" .. tostring(prop.type) .. \"]\")\ + elseif res and res.msg.match and res.msg:match('%[nossr%]$') then\ + return div(\"[this page could not be pre-rendered on the server]\")\ + else\ + res = tostring(res.msg) .. \"\\n\" .. tostring(res.trace)\ + return disp_error(res)\ + end\ + end,\ + get_inspector = function(self)\ + self.inspect_prop = self.facet:map(function(prop)\ + local active = self.active:get()\ + local key = active and active:find(prop)\ + if key and key.original then\ + key = key.original\ + end\ + return key\ + end)\ + self.inspect_err = ReactiveVar()\ + do\ + local _with_0 = div({\ + class = 'view inspector'\ + })\ + _with_0:append(nav({\ + span('inspector'),\ + self.inspect_prop:map(function(current)\ + current = current and current:tostring()\ + local fileder = self.active:get()\ + local onchange\ + onchange = function(_, e)\ + if e.target.value == '' then\ + return \ + end\ + local name\ + name = self.facet:get().name\ + return self.inspect_prop:set(Key(e.target.value))\ + end\ + do\ + local _with_1 = select({\ + onchange = onchange\ + })\ + _with_1:append(option('(none)', {\ + value = '',\ + disabled = true,\ + selected = not value\ + }))\ + if fileder then\ + for key, _ in pairs(fileder.facets) do\ + local value = key:tostring()\ + _with_1:append(option(value, {\ + value = value,\ + selected = value == current\ + }))\ + end\ + end\ + return _with_1\ + end\ + end),\ + self.inspect:map(function(enabled)\ + if enabled then\ + return button('close', {\ + onclick = function(_, e)\ + return self.inspect:set(false)\ + end\ + })\ + end\ + end)\ + }))\ + _with_0:append((function()\ + do\ + local _with_1 = div({\ + class = self.inspect_err:map(function(e)\ + if e then\ + return 'error-wrap'\ + else\ + return 'error-wrap empty'\ + end\ + end)\ + })\ + _with_1:append(span(\"an error occured while rendering this view:\"))\ + _with_1:append(self.inspect_err)\ + return _with_1\ + end\ + end)())\ + _with_0:append((function()\ + do\ + local _with_1 = pre({\ + class = 'content'\ + })\ + _with_1:append(keep(self.inspect_prop:map(function(prop, old)\ + return self:get_content(prop, self.inspect_err, function(self, prop)\ + local value, key = self:get(prop)\ + assert(key, \"couldn't @get \" .. tostring(prop))\ + local conversions = get_conversions('mmm/dom', key.type, casts)\ + assert(conversions, \"cannot cast '\" .. tostring(key.type) .. \"'\")\ + return apply_conversions(conversions, value, self, prop)\ + end)\ + end)))\ + return _with_1\ + end\ + end)())\ + return _with_0\ + end\ + end,\ + navigate = function(self, new)\ + return self.path:set(new)\ + end\ + }\ + _base_0.__index = _base_0\ + _class_0 = setmetatable({\ + __init = function(self, root, path, rehydrate)\ + if rehydrate == nil then\ + rehydrate = false\ + end\ + self.root = root\ + assert(self.root, 'root fileder is nil')\ + self.path = ReactiveVar(path or '')\ + if MODE == 'CLIENT' then\ + local logo = document:querySelector('header h1 > svg')\ + local spin\ + spin = function()\ + logo.classList:add('spin')\ + local _ = logo.parentElement.offsetWidth\ + return logo.classList:remove('spin')\ + end\ + self.path:subscribe(function(path)\ + document.body.classList:add('loading')\ + spin()\ + if self.skip then\ + return \ + end\ + local vis_path = path .. ((function()\ + if '/' == path:sub(-1) then\ + return ''\ + else\ + return '/'\ + end\ + end)())\ + return window.history:pushState(path, '', vis_path)\ + end)\ + window.onpopstate = function(_, event)\ + if event.state and not event.state == js.null then\ + self.skip = true\ + self.path:set(event.state)\ + self.skip = nil\ + end\ + end\ + end\ + self.active = self.path:map((function()\ + local _base_1 = self.root\ + local _fn_0 = _base_1.walk\ + return function(...)\ + return _fn_0(_base_1, ...)\ + end\ + end)())\ + self.facet = self.active:map(function(fileder)\ + if not (fileder) then\ + return \ + end\ + local last = self.facet and self.facet:get()\ + return Key((function()\ + if last then\ + return last.type\ + else\ + return 'mmm/dom'\ + end\ + end)())\ + end)\ + self.inspect = ReactiveVar((MODE == 'CLIENT' and window.location.search:match('[?&]inspect')))\ + local main = get_or_create('div', 'browser-root', {\ + class = 'main view'\ + })\ + if MODE == 'SERVER' then\ + main:append(nav({\ + id = 'browser-navbar',\ + span('please stand by... interactivity loading :)')\ + }))\ + else\ + main:prepend((function()\ + do\ + local _with_0 = get_or_create('nav', 'browser-navbar')\ + _with_0.node.innerHTML = ''\ + _with_0:append(span('path: ', self.path:map(function(path)\ + do\ + local _with_1 = div({\ + class = 'path',\ + style = {\ + display = 'inline-block'\ + }\ + })\ + local path_segment\ + path_segment = function(name, href)\ + return a(name, {\ + href = href,\ + onclick = function(_, e)\ + e:preventDefault()\ + return self:navigate(href)\ + end\ + })\ + end\ + local href = ''\ + path = path:match('^/(.*)')\ + _with_1:append(path_segment('root', ''))\ + while path do\ + local name, rest = path:match('^([%w%-_%.]+)/(.*)')\ + if not name then\ + name = path\ + end\ + path = rest\ + href = tostring(href) .. \"/\" .. tostring(name)\ + _with_1:append('/')\ + _with_1:append(path_segment(name, href))\ + end\ + return _with_1\ + end\ + end)))\ + _with_0:append(span('view facet:', {\ + style = {\ + ['margin-right'] = '0'\ + }\ + }))\ + _with_0:append(self.active:map(function(fileder)\ + local onchange\ + onchange = function(_, e)\ + local type\ + type = self.facet:get().type\ + return self.facet:set(Key({\ + name = e.target.value,\ + type = type\ + }))\ + end\ + local current = self.facet:get()\ + current = current and current.name\ + do\ + local _with_1 = select({\ + onchange = onchange,\ + disabled = not fileder\ + })\ + local has_main = fileder and fileder:find(current.name, '.*')\ + _with_1:append(option('(main)', {\ + value = '',\ + disabled = not has_main,\ + selected = current == ''\ + }))\ + if fileder then\ + for i, value in ipairs(fileder:get_facets()) do\ + local _continue_0 = false\ + repeat\ + if value == '' then\ + _continue_0 = true\ + break\ + end\ + _with_1:append(option(value, {\ + value = value,\ + selected = value == current\ + }))\ + _continue_0 = true\ + until true\ + if not _continue_0 then\ + break\ + end\ + end\ + end\ + return _with_1\ + end\ + end))\ + _with_0:append(self.inspect:map(function(enabled)\ + if not enabled then\ + return button('inspect', {\ + onclick = function(_, e)\ + return self.inspect:set(true)\ + end\ + })\ + end\ + end))\ + return _with_0\ + end\ + end)())\ + end\ + self.error = ReactiveVar()\ + main:append((function()\ + do\ + local _with_0 = get_or_create('div', 'browser-error', {\ + class = self.error:map(function(e)\ + if e then\ + return 'error-wrap'\ + else\ + return 'error-wrap empty'\ + end\ + end)\ + })\ + _with_0:append((span(\"an error occured while rendering this view:\")), (rehydrate and _with_0.node.firstChild))\ + _with_0:append(self.error)\ + return _with_0\ + end\ + end)())\ + main:append((function()\ + do\ + local _with_0 = get_or_create('div', 'browser-content', {\ + class = 'content'\ + })\ + local content = ReactiveVar((function()\ + if rehydrate then\ + return _with_0.node.lastChild\ + else\ + return self:get_content(self.facet:get())\ + end\ + end)())\ + _with_0:append(keep(content))\ + if MODE == 'CLIENT' then\ + self.facet:subscribe(function(p)\ + return window:setTimeout((function()\ + return content:set(self:get_content(p))\ + end), 150)\ + end)\ + end\ + return _with_0\ + end\ + end)())\ + if rehydrate then\ + self.facet:set(self.facet:get())\ + end\ + local inspector = self.inspect:map(function(enabled)\ + if enabled then\ + return self:get_inspector()\ + end\ + end)\ + local wrapper = get_or_create('div', 'browser-wrapper', main, inspector, {\ + class = 'browser'\ + })\ + self.node = wrapper.node\ + do\ + local _base_1 = wrapper\ + local _fn_0 = _base_1.render\ + self.render = function(...)\ + return _fn_0(_base_1, ...)\ + end\ + end\ + end,\ + __base = _base_0,\ + __name = \"Browser\"\ + }, {\ + __index = _base_0,\ + __call = function(cls, ...)\ + local _self_0 = setmetatable({}, _base_0)\ + cls.__init(_self_0, ...)\ + return _self_0\ + end\ + })\ + _base_0.__class = _class_0\ + local self = _class_0\ + err_and_trace = function(msg)\ + return {\ + msg = msg,\ + trace = debug.traceback()\ + }\ + end\ + default_convert = function(self, key)\ + return self:get(key.name, 'mmm/dom')\ + end\ + default_convert = function(self, key)\ + return self:get(key.name, 'mmm/dom')\ + end\ + Browser = _class_0\ +end\ +return {\ + Browser = Browser\ +}\ +", "mmm/mmmfs/browser.lua") end +if not p["mmm.mmmfs.conversion"] then p["mmm.mmmfs.conversion"] = load("local require = relative(..., 1)\ +local converts = require('.converts')\ +local count\ +count = function(base, pattern)\ + if pattern == nil then\ + pattern = '->'\ + end\ + return select(2, base:gsub(pattern, ''))\ +end\ +local escape_pattern\ +escape_pattern = function(inp)\ + return \"^\" .. tostring(inp:gsub('([^%w])', '%%%1')) .. \"$\"\ +end\ +local escape_inp\ +escape_inp = function(inp)\ + return \"^\" .. tostring(inp:gsub('([-/])', '%%%1')) .. \"$\"\ +end\ +local get_conversions\ +get_conversions = function(want, have, _converts, limit)\ + if _converts == nil then\ + _converts = converts\ + end\ + if limit == nil then\ + limit = 5\ + end\ + assert(have, 'need starting type(s)')\ + if 'string' == type(have) then\ + have = {\ + have\ + }\ + end\ + assert(#have > 0, 'need starting type(s) (list was empty)')\ + want = escape_pattern(want)\ + local iterations = limit + math.max(table.unpack((function()\ + local _accum_0 = { }\ + local _len_0 = 1\ + for _index_0 = 1, #have do\ + local type = have[_index_0]\ + _accum_0[_len_0] = count(type)\ + _len_0 = _len_0 + 1\ + end\ + return _accum_0\ + end)()))\ + do\ + local _accum_0 = { }\ + local _len_0 = 1\ + for _index_0 = 1, #have do\ + local start = have[_index_0]\ + _accum_0[_len_0] = {\ + start = start,\ + rest = start,\ + conversions = { }\ + }\ + _len_0 = _len_0 + 1\ + end\ + have = _accum_0\ + end\ + for i = 1, iterations do\ + local next_have, c = { }, 1\ + for _index_0 = 1, #have do\ + local _des_0 = have[_index_0]\ + local start, rest, conversions\ + start, rest, conversions = _des_0.start, _des_0.rest, _des_0.conversions\ + if rest:match(want) then\ + return conversions, start\ + else\ + for _index_1 = 1, #_converts do\ + local _continue_0 = false\ + repeat\ + local convert = _converts[_index_1]\ + local inp = escape_inp(convert.inp)\ + if not (rest:match(inp)) then\ + _continue_0 = true\ + break\ + end\ + local result = rest:gsub(inp, convert.out)\ + if result then\ + next_have[c] = {\ + start = start,\ + rest = result,\ + conversions = {\ + {\ + convert = convert,\ + from = rest,\ + to = result\ + },\ + table.unpack(conversions)\ + }\ + }\ + c = c + 1\ + end\ + _continue_0 = true\ + until true\ + if not _continue_0 then\ + break\ + end\ + end\ + end\ + end\ + have = next_have\ + if not (#have > 0) then\ + return \ + end\ + end\ +end\ +local apply_conversions\ +apply_conversions = function(conversions, value, ...)\ + for i = #conversions, 1, -1 do\ + local step = conversions[i]\ + value = step.convert.transform(step, value, ...)\ + end\ + return value\ +end\ +return {\ + converts = converts,\ + get_conversions = get_conversions,\ + apply_conversions = apply_conversions\ +}\ +", "mmm/mmmfs/conversion.lua") end +if not p["mmm.mmmfs.fileder"] then p["mmm.mmmfs.fileder"] = load("local require = relative(..., 1)\ +local get_conversions, apply_conversions\ +do\ + local _obj_0 = require('.conversion')\ + get_conversions, apply_conversions = _obj_0.get_conversions, _obj_0.apply_conversions\ +end\ +local Key\ +do\ + local _class_0\ + local _base_0 = {\ + tostring = function(self)\ + if self.name == '' then\ + return self.type\ + else\ + return tostring(self.name) .. \": \" .. tostring(self.type)\ + end\ + end,\ + __tostring = function(self)\ + return self:tostring()\ + end\ + }\ + _base_0.__index = _base_0\ + _class_0 = setmetatable({\ + __init = function(self, opts, second)\ + if 'string' == type(second) then\ + self.name, self.type = (opts or ''), second\ + elseif 'string' == type(opts) then\ + self.name, self.type = opts:match('^([%w-_]+): *(.+)$')\ + if not self.name then\ + self.name = ''\ + self.type = opts\ + end\ + elseif 'table' == type(opts) then\ + self.name = opts.name\ + self.type = opts.type\ + self.original = opts.original\ + self.filename = opts.filename\ + else\ + return error(\"wrong argument type: \" .. tostring(type(opts)) .. \", \" .. tostring(type(second)))\ + end\ + end,\ + __base = _base_0,\ + __name = \"Key\"\ + }, {\ + __index = _base_0,\ + __call = function(cls, ...)\ + local _self_0 = setmetatable({}, _base_0)\ + cls.__init(_self_0, ...)\ + return _self_0\ + end\ + })\ + _base_0.__class = _class_0\ + Key = _class_0\ +end\ +local Fileder\ +do\ + local _class_0\ + local _base_0 = {\ + walk = function(self, path)\ + if path == '' then\ + return self\ + end\ + if '/' ~= path:sub(1, 1) then\ + path = tostring(self.path) .. \"/\" .. tostring(path)\ + end\ + if not (self.path == path:sub(1, #self.path)) then\ + return \ + end\ + if #path == #self.path then\ + return self\ + end\ + local _list_0 = self.children\ + for _index_0 = 1, #_list_0 do\ + local child = _list_0[_index_0]\ + do\ + local match = child:walk(path)\ + if match then\ + return match\ + end\ + end\ + end\ + end,\ + mount = function(self, path, mount_as)\ + if not mount_as then\ + path = path .. self:gett('name: alpha')\ + end\ + assert(not self.path or self.path == path, \"mounted twice: \" .. tostring(self.path) .. \" and now \" .. tostring(path))\ + self.path = path\ + local _list_0 = self.children\ + for _index_0 = 1, #_list_0 do\ + local child = _list_0[_index_0]\ + child:mount(self.path .. '/')\ + end\ + end,\ + iterate = function(self, depth)\ + if depth == nil then\ + depth = 0\ + end\ + coroutine.yield(self)\ + if depth == 1 then\ + return \ + end\ + local _list_0 = self.children\ + for _index_0 = 1, #_list_0 do\ + local child = _list_0[_index_0]\ + child:iterate(depth - 1)\ + end\ + end,\ + get_facets = function(self)\ + local names = { }\ + for key in pairs(self.facets) do\ + names[key.name] = true\ + end\ + local _accum_0 = { }\ + local _len_0 = 1\ + for name in pairs(names) do\ + _accum_0[_len_0] = name\ + _len_0 = _len_0 + 1\ + end\ + return _accum_0\ + end,\ + has = function(self, ...)\ + local want = Key(...)\ + for key in pairs(self.facets) do\ + local _continue_0 = false\ + repeat\ + if key.original then\ + _continue_0 = true\ + break\ + end\ + if key.name == want.name and key.type == want.type then\ + return key\ + end\ + _continue_0 = true\ + until true\ + if not _continue_0 then\ + break\ + end\ + end\ + end,\ + has_facet = function(self, want)\ + for key in pairs(self.facets) do\ + local _continue_0 = false\ + repeat\ + if key.original then\ + _continue_0 = true\ + break\ + end\ + if key.name == want then\ + return key\ + end\ + _continue_0 = true\ + until true\ + if not _continue_0 then\ + break\ + end\ + end\ + end,\ + find = function(self, ...)\ + local want = Key(...)\ + local matching\ + do\ + local _accum_0 = { }\ + local _len_0 = 1\ + for key in pairs(self.facets) do\ + if key.name == want.name then\ + _accum_0[_len_0] = key\ + _len_0 = _len_0 + 1\ + end\ + end\ + matching = _accum_0\ + end\ + if not (#matching > 0) then\ + return \ + end\ + local shortest_path, start = get_conversions(want.type, (function()\ + local _accum_0 = { }\ + local _len_0 = 1\ + for _index_0 = 1, #matching do\ + local key = matching[_index_0]\ + _accum_0[_len_0] = key.type\ + _len_0 = _len_0 + 1\ + end\ + return _accum_0\ + end)())\ + if start then\ + for _index_0 = 1, #matching do\ + local key = matching[_index_0]\ + if key.type == start then\ + return key, shortest_path\ + end\ + end\ + return error(\"couldn't find key after resolution?\")\ + end\ + end,\ + get = function(self, ...)\ + local want = Key(...)\ + local key, conversions = self:find(want)\ + if key then\ + local value = apply_conversions(conversions, self.facets[key], self, key)\ + return value, key\ + end\ + end,\ + gett = function(self, ...)\ + local want = Key(...)\ + local value, key = self:get(want)\ + assert(value, tostring(self) .. \" doesn't have value for '\" .. tostring(want) .. \"'\")\ + return value, key\ + end,\ + __tostring = function(self)\ + return \"Fileder:\" .. tostring(self.path)\ + end\ + }\ + _base_0.__index = _base_0\ + _class_0 = setmetatable({\ + __init = function(self, facets, children)\ + if not children then\ + do\ + local _accum_0 = { }\ + local _len_0 = 1\ + for i, child in ipairs(facets) do\ + facets[i] = nil\ + local _value_0 = child\ + _accum_0[_len_0] = _value_0\ + _len_0 = _len_0 + 1\ + end\ + children = _accum_0\ + end\ + end\ + self.children = setmetatable({ }, {\ + __index = function(t, k)\ + if not ('string' == type(k)) then\ + return rawget(t, k)\ + end\ + return self:walk(tostring(self.path) .. \"/\" .. tostring(k))\ + end,\ + __newindex = function(t, k, child)\ + rawset(t, k, child)\ + if self.path == '/' then\ + return child:mount('/')\ + elseif self.path then\ + return child:mount(self.path .. '/')\ + end\ + end\ + })\ + for i, child in ipairs(children) do\ + self.children[i] = child\ + end\ + self.facets = setmetatable({ }, {\ + __newindex = function(t, key, v)\ + return rawset(t, (Key(key)), v)\ + end\ + })\ + for k, v in pairs(facets) do\ + self.facets[k] = v\ + end\ + end,\ + __base = _base_0,\ + __name = \"Fileder\"\ + }, {\ + __index = _base_0,\ + __call = function(cls, ...)\ + local _self_0 = setmetatable({}, _base_0)\ + cls.__init(_self_0, ...)\ + return _self_0\ + end\ + })\ + _base_0.__class = _class_0\ + Fileder = _class_0\ +end\ +local dir_base\ +dir_base = function(path)\ + local dir, base = path:match('(.-)([^/]-)$')\ + if dir and #dir > 0 then\ + dir = dir:sub(1, #dir - 1)\ + end\ + return dir, base\ +end\ +local load_tree\ +load_tree = function(store, root)\ + if root == nil then\ + root = ''\ + end\ + local fileders = setmetatable({ }, {\ + __index = function(self, path)\ + do\ + local val = Fileder({ })\ + val.path = path\ + rawset(self, path, val)\ + return val\ + end\ + end\ + })\ + root = fileders[root]\ + root.facets['name: alpha'] = ''\ + for fn, ft in store:list_facets(root.path) do\ + local val = store:load_facet(root.path, fn, ft)\ + root.facets[Key(fn, ft)] = val\ + end\ + for path in store:list_all_fileders(root.path) do\ + local fileder = fileders[path]\ + local parent, name = dir_base(path)\ + fileder.facets['name: alpha'] = name\ + table.insert(fileders[parent].children, fileder)\ + for fn, ft in store:list_facets(path) do\ + local val = store:load_facet(path, fn, ft)\ + fileder.facets[Key(fn, ft)] = val\ + end\ + end\ + return root\ +end\ +return {\ + Key = Key,\ + Fileder = Fileder,\ + dir_base = dir_base,\ + load_tree = load_tree\ +}\ +", "mmm/mmmfs/fileder.lua") end +if not p["mmm.mmmfs"] then p["mmm.mmmfs"] = load("local require = relative(...)\ +local Key, Fileder\ +do\ + local _obj_0 = require('.fileder')\ + Key, Fileder = _obj_0.Key, _obj_0.Fileder\ +end\ +local Browser\ +Browser = require('.browser').Browser\ +return {\ + Key = Key,\ + Fileder = Fileder,\ + Browser = Browser\ +}\ +", "mmm/mmmfs/init.lua") end +if not p["mmm.mmmfs.util"] then p["mmm.mmmfs.util"] = load("local merge\ +merge = function(orig, extra)\ + if orig == nil then\ + orig = { }\ + end\ + do\ + local attr\ + do\ + local _tbl_0 = { }\ + for k, v in pairs(orig) do\ + _tbl_0[k] = v\ + end\ + attr = _tbl_0\ + end\ + for k, v in pairs(extra) do\ + attr[k] = v\ + end\ + return attr\ + end\ +end\ +local tourl\ +tourl = function(path)\ + if STATIC then\ + return path .. '/'\ + else\ + return path .. '/'\ + end\ +end\ +return function(elements)\ + local a, div, pre\ + a, div, pre = elements.a, elements.div, elements.pre\ + local find_fileder\ + find_fileder = function(fileder, origin)\ + if 'string' == type(fileder) then\ + assert(origin, \"cannot resolve path '\" .. tostring(fileder) .. \"' without origin!\")\ + return assert((origin:walk(fileder)), \"couldn't resolve path '\" .. tostring(fileder) .. \"' from \" .. tostring(origin))\ + else\ + return assert(fileder, \"no fileder passed.\")\ + end\ + end\ + local navigate_to\ + navigate_to = function(path, name, opts)\ + if opts == nil then\ + opts = { }\ + end\ + opts.href = tourl(path)\ + if MODE == 'CLIENT' then\ + opts.onclick = function(self, e)\ + e:preventDefault()\ + return BROWSER:navigate(path)\ + end\ + end\ + return a(name, opts)\ + end\ + local link_to\ + link_to = function(fileder, name, origin, attr)\ + fileder = find_fileder(fileder, origin)\ + name = name or fileder:get('title: mmm/dom')\ + name = name or fileder:gett('name: alpha')\ + do\ + local href = fileder:get('link: URL.*')\ + if href then\ + return a(name, merge(attr, {\ + href = href,\ + target = '_blank'\ + }))\ + else\ + return a(name, merge(attr, {\ + href = tourl(fileder.path),\ + onclick = (function()\ + if MODE == 'CLIENT' then\ + return function(self, e)\ + e:preventDefault()\ + return BROWSER:navigate(fileder.path)\ + end\ + end\ + end)()\ + }))\ + end\ + end\ + end\ + local embed\ + embed = function(fileder, name, origin, opts)\ + if name == nil then\ + name = ''\ + end\ + if opts == nil then\ + opts = { }\ + end\ + fileder = find_fileder(fileder, origin)\ + local ok, node = pcall(fileder.gett, fileder, name, 'mmm/dom')\ + if not ok then\ + return div(\"couldn't embed \" .. tostring(fileder) .. \" \" .. tostring(name), (pre(node)), {\ + style = {\ + background = 'var(--gray-fail)',\ + padding = '1em'\ + }\ + })\ + end\ + local klass = 'embed'\ + if opts.desc then\ + klass = klass .. ' desc'\ + end\ + if opts.inline then\ + klass = klass .. ' inline'\ + end\ + node = div({\ + class = klass,\ + node,\ + (function()\ + if opts.desc then\ + return div(opts.desc, {\ + class = 'description'\ + })\ + end\ + end)()\ + })\ + if opts.nolink then\ + return node\ + end\ + return link_to(fileder, node, nil, opts.attr)\ + end\ + return {\ + find_fileder = find_fileder,\ + link_to = link_to,\ + navigate_to = navigate_to,\ + embed = embed\ + }\ +end\ +", "mmm/mmmfs/util.lua") end +if not p["mmm.mmmfs.converts"] then p["mmm.mmmfs.converts"] = load("local require = relative(..., 1)\ +local div, code, img, video, blockquote, a, span, source, iframe\ +do\ + local _obj_0 = require('mmm.dom')\ + 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\ +end\ +local find_fileder, link_to, embed\ +do\ + local _obj_0 = (require('mmm.mmmfs.util'))(require('mmm.dom'))\ + find_fileder, link_to, embed = _obj_0.find_fileder, _obj_0.link_to, _obj_0.embed\ +end\ +local render\ +render = require('.layout').render\ +local tohtml\ +tohtml = require('mmm.component').tohtml\ +local js_fix\ +if MODE == 'CLIENT' then\ + js_fix = function(arg)\ + if arg == js.null then\ + return \ + end\ + return arg\ + end\ +end\ +local single\ +single = function(func)\ + return function(self, val)\ + return func(val)\ + end\ +end\ +local loadwith\ +loadwith = function(_load)\ + return function(self, val, fileder, key)\ + local func = assert(_load(val, tostring(fileder) .. \"#\" .. tostring(key)))\ + return func()\ + end\ +end\ +local converts = {\ + {\ + inp = 'fn -> (.+)',\ + out = '%1',\ + transform = function(self, val, fileder)\ + return val(fileder)\ + end\ + },\ + {\ + inp = 'mmm/component',\ + out = 'mmm/dom',\ + transform = single(tohtml)\ + },\ + {\ + inp = 'mmm/dom',\ + out = 'text/html+frag',\ + transform = function(self, node)\ + if MODE == 'SERVER' then\ + return node\ + else\ + return node.outerHTML\ + end\ + end\ + },\ + {\ + inp = 'mmm/dom',\ + out = 'text/html',\ + transform = function(self, html, fileder)\ + return render(html, fileder)\ + end\ + },\ + {\ + inp = 'text/html%+frag',\ + out = 'mmm/dom',\ + transform = (function()\ + if MODE == 'SERVER' then\ + return function(self, html, fileder)\ + html = html:gsub('(.-)', function(attrs, text)\ + if #text == 0 then\ + text = nil\ + end\ + local path = ''\ + while attrs and attrs ~= '' do\ + local key, val, _attrs = attrs:match('^(%w+)=\"([^\"]-)\"%s*(.*)')\ + if not key then\ + key, _attrs = attrs:match('^(%w+)%s*(.*)$')\ + val = true\ + end\ + attrs = _attrs\ + local _exp_0 = key\ + if 'path' == _exp_0 then\ + path = val\ + else\ + warn(\"unkown attribute '\" .. tostring(key) .. \"=\\\"\" .. tostring(val) .. \"\\\"' in \")\ + end\ + end\ + return link_to(path, text, fileder)\ + end)\ + html = html:gsub('(.-)', function(attrs, desc)\ + local path, facet = '', ''\ + local opts = { }\ + if #desc ~= 0 then\ + opts.desc = desc\ + end\ + while attrs and attrs ~= '' do\ + local key, val, _attrs = attrs:match('^(%w+)=\"([^\"]-)\"%s*(.*)')\ + if not key then\ + key, _attrs = attrs:match('^(%w+)%s*(.*)$')\ + val = true\ + end\ + attrs = _attrs\ + local _exp_0 = key\ + if 'path' == _exp_0 then\ + path = val\ + elseif 'facet' == _exp_0 then\ + facet = val\ + elseif 'nolink' == _exp_0 then\ + opts.nolink = true\ + elseif 'inline' == _exp_0 then\ + opts.inline = true\ + else\ + warn(\"unkown attribute '\" .. tostring(key) .. \"=\\\"\" .. tostring(val) .. \"\\\"' in \")\ + end\ + end\ + return embed(path, facet, fileder, opts)\ + end)\ + return html\ + end\ + else\ + return function(self, html, fileder)\ + local parent\ + do\ + local _with_0 = document:createElement('div')\ + _with_0.innerHTML = html\ + local embeds = _with_0:getElementsByTagName('mmm-embed')\ + do\ + local _accum_0 = { }\ + local _len_0 = 1\ + for i = 0, embeds.length - 1 do\ + _accum_0[_len_0] = embeds[i]\ + _len_0 = _len_0 + 1\ + end\ + embeds = _accum_0\ + end\ + for _index_0 = 1, #embeds do\ + local element = embeds[_index_0]\ + local path = js_fix(element:getAttribute('path'))\ + local facet = js_fix(element:getAttribute('facet'))\ + local nolink = js_fix(element:getAttribute('nolink'))\ + local inline = js_fix(element:getAttribute('inline'))\ + local desc = js_fix(element.innerText)\ + element:replaceWith(embed(path or '', facet or '', fileder, {\ + nolink = nolink,\ + inline = inline,\ + desc = desc\ + }))\ + end\ + embeds = _with_0:getElementsByTagName('mmm-link')\ + do\ + local _accum_0 = { }\ + local _len_0 = 1\ + for i = 0, embeds.length - 1 do\ + _accum_0[_len_0] = embeds[i]\ + _len_0 = _len_0 + 1\ + end\ + embeds = _accum_0\ + end\ + for _index_0 = 1, #embeds do\ + local element = embeds[_index_0]\ + local text = js_fix(element.innerText)\ + local path = js_fix(element:getAttribute('path'))\ + element:replaceWith(link_to(path or '', text, fileder))\ + end\ + parent = _with_0\ + end\ + assert(1 == parent.childElementCount, \"text/html with more than one child!\")\ + return parent.firstElementChild\ + end\ + end\ + end)()\ + },\ + {\ + inp = 'text/lua -> (.+)',\ + out = '%1',\ + transform = loadwith(load or loadstring)\ + },\ + {\ + inp = 'mmm/tpl -> (.+)',\ + out = '%1',\ + transform = function(self, source, fileder)\ + return source:gsub('{{(.-)}}', function(expr)\ + local path, facet = expr:match('^([%w%-_%./]*)%+(.*)')\ + assert(path, \"couldn't match TPL expression '\" .. tostring(expr) .. \"'\")\ + return (find_fileder(path, fileder)):gett(facet)\ + end)\ + end\ + },\ + {\ + inp = 'time/iso8601-date',\ + out = 'time/unix',\ + transform = function(self, val)\ + local year, _, month, day = val:match('^%s*(%d%d%d%d)(%-?)([01]%d)%2([0-3]%d)%s*$')\ + assert(year, \"failed to parse ISO 8601 date: '\" .. tostring(val) .. \"'\")\ + return os.time({\ + year = year,\ + month = month,\ + day = day\ + })\ + end\ + },\ + {\ + inp = 'URL -> twitter/tweet',\ + out = 'mmm/dom',\ + transform = function(self, href)\ + local id = assert((href:match('twitter.com/[^/]-/status/(%d*)')), \"couldn't parse twitter/tweet URL: '\" .. tostring(href) .. \"'\")\ + if MODE == 'CLIENT' then\ + do\ + local parent = div()\ + window.twttr.widgets:createTweet(id, parent)\ + return parent\ + end\ + else\ + return div(blockquote({\ + class = 'twitter-tweet',\ + ['data-lang'] = 'en',\ + a('(linked tweet)', {\ + href = href\ + })\ + }))\ + end\ + end\ + },\ + {\ + inp = 'URL -> youtube/video',\ + out = 'mmm/dom',\ + transform = function(self, link)\ + local id = link:match('youtu%.be/([^/]+)')\ + id = id or link:match('youtube.com/watch.*[?&]v=([^&]+)')\ + id = id or link:match('youtube.com/[ev]/([^/]+)')\ + id = id or link:match('youtube.com/embed/([^/]+)')\ + assert(id, \"couldn't parse youtube URL: '\" .. tostring(link) .. \"'\")\ + return iframe({\ + width = 560,\ + height = 315,\ + frameborder = 0,\ + allowfullscreen = true,\ + frameBorder = 0,\ + src = \"//www.youtube.com/embed/\" .. tostring(id)\ + })\ + end\ + },\ + {\ + inp = 'URL -> image/.+',\ + out = 'mmm/dom',\ + transform = function(self, src, fileder)\ + return img({\ + src = src\ + })\ + end\ + },\ + {\ + inp = 'URL -> video/.+',\ + out = 'mmm/dom',\ + transform = function(self, src)\ + return video((source({\ + src = src\ + })), {\ + controls = true,\ + loop = true\ + })\ + end\ + },\ + {\ + inp = 'text/plain',\ + out = 'mmm/dom',\ + transform = function(self, val)\ + return span(val)\ + end\ + },\ + {\ + inp = 'alpha',\ + out = 'mmm/dom',\ + transform = single(code)\ + },\ + {\ + inp = '(.+)',\ + out = 'URL -> %1',\ + transform = function(self, _, fileder, key)\ + return tostring(fileder.path) .. \"/\" .. tostring(key.name) .. \":\" .. tostring(self.from)\ + end\ + }\ +}\ +if MODE == 'SERVER' then\ + local ok, moon = pcall(require, 'moonscript.base')\ + if ok then\ + local _load = moon.load or moon.loadstring\ + table.insert(converts, {\ + inp = 'text/moonscript -> (.+)',\ + out = '%1',\ + transform = loadwith(moon.load or moon.loadstring)\ + })\ + table.insert(converts, {\ + inp = 'text/moonscript -> (.+)',\ + out = 'text/lua -> %1',\ + transform = single(moon.to_lua)\ + })\ + end\ +else\ + table.insert(converts, {\ + inp = 'text/javascript -> (.+)',\ + out = '%1',\ + transform = function(self, source)\ + local f = js.new(window.Function, source)\ + return f()\ + end\ + })\ +end\ +do\ + local markdown\ + if MODE == 'SERVER' then\ + local success, discount = pcall(require, 'discount')\ + if not success then\ + warn(\"NO MARKDOWN SUPPORT!\", discount)\ + end\ + markdown = success and function(md)\ + local res = assert(discount.compile(md, 'githubtags'))\ + return res.body\ + end\ + else\ + markdown = window and window.marked and (function()\ + local _base_0 = window\ + local _fn_0 = _base_0.marked\ + return function(...)\ + return _fn_0(_base_0, ...)\ + end\ + end)()\ + end\ + if markdown then\ + table.insert(converts, {\ + inp = 'text/markdown',\ + out = 'text/html+frag',\ + transform = function(self, md)\ + return \"
\" .. tostring(markdown(md)) .. \"
\"\ + end\ + })\ + table.insert(converts, {\ + inp = 'text/markdown%+span',\ + out = 'mmm/dom',\ + transform = (function()\ + if MODE == 'SERVER' then\ + return function(self, source)\ + local html = markdown(source)\ + html = html:gsub('^$', '/span>')\ + end\ + else\ + return function(self, source)\ + local html = markdown(source)\ + html = html:gsub('^%s*

%s*', '')\ + html = html:gsub('%s*

%s*$', '')\ + do\ + local _with_0 = document:createElement('span')\ + _with_0.innerHTML = html\ + return _with_0\ + end\ + end\ + end\ + end)()\ + })\ + end\ +end\ +return converts\ +", "mmm/mmmfs/converts.lua") end +if not p["mmm.mmmfs.layout"] then p["mmm.mmmfs.layout"] = load("local require = relative(..., 1)\ +local header, aside, footer, div, svg, script, g, circle, h1, span, b, a, img\ +do\ + local _obj_0 = require('mmm.dom')\ + header, aside, footer, div, svg, script, g, circle, h1, span, b, a, img = _obj_0.header, _obj_0.aside, _obj_0.footer, _obj_0.div, _obj_0.svg, _obj_0.script, _obj_0.g, _obj_0.circle, _obj_0.h1, _obj_0.span, _obj_0.b, _obj_0.a, _obj_0.img\ +end\ +local navigate_to\ +navigate_to = (require('mmm.mmmfs.util'))(require('mmm.dom')).navigate_to\ +local pick\ +pick = function(...)\ + local num = select('#', ...)\ + local i = math.ceil(math.random() * num)\ + return (select(i, ...))\ +end\ +local iconlink\ +iconlink = function(href, src, alt, style)\ + return a({\ + class = 'iconlink',\ + target = '_blank',\ + rel = 'me',\ + href = href,\ + img({\ + src = src,\ + alt = alt,\ + style = style\ + })\ + })\ +end\ +local logo = svg({\ + class = 'sun',\ + viewBox = '-0.75 -1 1.5 2',\ + xmlns = 'http://www.w3.org/2000/svg',\ + baseProfile = 'full',\ + version = '1.1',\ + g({\ + transform = 'translate(0 .18)',\ + g({\ + class = 'circle out',\ + circle({\ + r = '.6',\ + fill = 'none',\ + ['stroke-width'] = '.12'\ + })\ + }),\ + g({\ + class = 'circle in',\ + circle({\ + r = '.2',\ + stroke = 'none'\ + })\ + })\ + })\ +})\ +local gen_header\ +gen_header = function()\ + return header({\ + div({\ + h1({\ + navigate_to('', logo),\ + span({\ + span('mmm', {\ + class = 'bold'\ + }),\ + '​',\ + '.s‑ol.nu'\ + })\ + }),\ + table.concat({\ + pick('fun', 'cool', 'weird', 'interesting', 'new', 'pleasant'),\ + pick('stuff', 'things', 'projects', 'experiments', 'visuals', 'ideas'),\ + pick(\"with\", 'and'),\ + pick('mostly code', 'code and wires', 'silicon', 'electronics', 'shaders', 'oscilloscopes', 'interfaces', 'hardware', 'FPGAs')\ + }, ' ')\ + }),\ + aside({\ + navigate_to('/about', 'about me'),\ + navigate_to('/games', 'games'),\ + navigate_to('/projects', 'other'),\ + a({\ + href = 'mailto:s%20[removethis]%20[at]%20s-ol.nu',\ + 'contact',\ + script(\"\\n var l = document.currentScript.parentElement;\\n l.href = l.href.replace('%20[at]%20', '@');\\n l.href = l.href.replace('%20[removethis]', '') + '?subject=Hey there :)';\\n \")\ + })\ + })\ + })\ +end\ +footer = footer({\ + span({\ + 'made with \\xe2\\x98\\xbd by ',\ + a('s-ol', {\ + href = 'https://twitter.com/S0lll0s'\ + }),\ + \", \" .. tostring(os.date('%Y'))\ + }),\ + div({\ + class = 'icons',\ + iconlink('https://github.com/s-ol', 'https://cdn.jsdelivr.net/npm/simple-icons@latest/icons/github.svg', 'github'),\ + iconlink('https://merveilles.town/@s_ol', 'https://cdn.jsdelivr.net/npm/simple-icons@latest/icons/mastodon.svg', 'mastodon'),\ + iconlink('https://twitter.com/S0lll0s', 'https://cdn.jsdelivr.net/npm/simple-icons@latest/icons/twitter.svg', 'twitter'),\ + iconlink('https://webring.xxiivv.com/#random', 'https://webring.xxiivv.com/icon.black.svg', 'webring', {\ + height = '1.3em',\ + ['margin-left'] = '.3em',\ + ['margin-top'] = '-0.12em'\ + })\ + })\ +})\ +local get_meta\ +get_meta = function(self)\ + local title = (self:get('title: text/plain')) or self:gett('name: alpha')\ + local l\ + l = function(str)\ + str = str:gsub('[%s\\\\n]+$', '')\ + return str:gsub('\\\\n', ' ')\ + end\ + local e\ + e = function(str)\ + return string.format('%q', l(str))\ + end\ + local meta = \"\\n \\n \" .. tostring(l(title)) .. \"\\n \"\ + do\ + local page_meta = self:get('_meta: mmm/dom')\ + if page_meta then\ + meta = meta .. page_meta\ + else\ + meta = meta .. \"\\n \\n\\n \\n \\n \\n \"\ + do\ + local desc = self:get('description: text/plain')\ + if desc then\ + meta = meta .. \"\\n \"\ + end\ + end\ + end\ + end\ + return meta\ +end\ +local render\ +render = function(content, fileder, opts)\ + if opts == nil then\ + opts = { }\ + end\ + opts.meta = opts.meta or get_meta(fileder)\ + opts.scripts = opts.scripts or ''\ + if not (opts.noview) then\ + content = [[
\ +
\ + ]] .. content .. [[
\ +
\ + ]]\ + end\ + local buf = [[\ +\ + \ + \ + \ + ]]\ + buf = buf .. \"\\n \" .. tostring(get_meta(fileder)) .. \"\\n \\n \\n \" .. tostring(gen_header()) .. \"\\n\\n \" .. tostring(content) .. \"\\n\\n \" .. tostring(footer) .. \"\\n \"\ + buf = buf .. [[ \ + \ + \ + \ + \ + \ + \ + ]]\ + buf = buf .. opts.scripts\ + buf = buf .. \"\\n \\n\\n \"\ + return buf\ +end\ +return {\ + render = render\ +}\ +", "mmm/mmmfs/layout.lua") end +if not p["mmm.mmmfs.stores.fs"] then p["mmm.mmmfs.stores.fs"] = load("local lfs = require('lfs')\ +local dir_base\ +dir_base = function(path)\ + local dir, base = path:match('(.-)([^/]-)$')\ + if dir and #dir > 0 then\ + dir = dir:sub(1, #dir - 1)\ + end\ + return dir, base\ +end\ +local FSStore\ +do\ + local _class_0\ + local _base_0 = {\ + log = function(self, ...)\ + return print(\"[DB]\", ...)\ + end,\ + list_fileders_in = function(self, path)\ + if path == nil then\ + path = ''\ + end\ + return coroutine.wrap(function()\ + for entry_name in lfs.dir(self.root .. path) do\ + local _continue_0 = false\ + repeat\ + if '.' == entry_name:sub(1, 1) then\ + _continue_0 = true\ + break\ + end\ + local entry_path = self.root .. tostring(path) .. \"/\" .. tostring(entry_name)\ + if 'directory' == lfs.attributes(entry_path, 'mode') then\ + coroutine.yield(tostring(path) .. \"/\" .. tostring(entry_name))\ + end\ + _continue_0 = true\ + until true\ + if not _continue_0 then\ + break\ + end\ + end\ + end)\ + end,\ + list_all_fileders = function(self, path)\ + if path == nil then\ + path = ''\ + end\ + return coroutine.wrap(function()\ + for path in self:list_fileders_in(path) do\ + coroutine.yield(path)\ + for p in self:list_all_fileders(path) do\ + coroutine.yield(p)\ + end\ + end\ + end)\ + end,\ + create_fileder = function(self, parent, name)\ + self:log(\"creating fileder \" .. tostring(path))\ + local path = tostring(parent) .. \"/\" .. tostring(name)\ + assert(lfs.mkdir(self.root .. path))\ + return path\ + end,\ + remove_fileder = function(self, path)\ + self:log(\"removing fileder \" .. tostring(path))\ + local rmdir\ + rmdir = function(path)\ + for file in lfs.dir(path) do\ + local _continue_0 = false\ + repeat\ + if '.' == file:sub(1, 1) then\ + _continue_0 = true\ + break\ + end\ + local file_path = tostring(path) .. \"/\" .. tostring(file)\ + local _exp_0 = lfs.attributes(file_path, 'mode')\ + if 'file' == _exp_0 then\ + assert(os.remove(file_path))\ + elseif 'directory' == _exp_0 then\ + assert(rmdir(file_path))\ + end\ + _continue_0 = true\ + until true\ + if not _continue_0 then\ + break\ + end\ + end\ + return lfs.rmdir(path)\ + end\ + return rmdir(self.root .. path)\ + end,\ + rename_fileder = function(self, path, next_name)\ + self:log(\"renaming fileder \" .. tostring(path) .. \" -> '\" .. tostring(next_name) .. \"'\")\ + local parent, name = dir_base(path)\ + return assert(os.rename(path, self.root .. tostring(parent) .. \"/\" .. tostring(next_name)))\ + end,\ + move_fileder = function(self, path, next_parent)\ + self:log(\"moving fileder \" .. tostring(path) .. \" -> \" .. tostring(next_parent) .. \"/\")\ + local parent, name = dir_base(path)\ + return assert(os.rename(self.root .. path, self.root .. tostring(next_parent) .. \"/\" .. tostring(name)))\ + end,\ + list_facets = function(self, path)\ + return coroutine.wrap(function()\ + for entry_name in lfs.dir(self.root .. path) do\ + local entry_path = tostring(self.root .. path) .. \"/\" .. tostring(entry_name)\ + if 'file' == lfs.attributes(entry_path, 'mode') then\ + entry_name = (entry_name:match('(.*)%.%w+')) or entry_name\ + entry_name = entry_name:gsub('%$', '/')\ + local name, type = entry_name:match('([%w-_]+): *(.+)')\ + if not name then\ + name = ''\ + type = entry_name\ + end\ + coroutine.yield(name, type)\ + end\ + end\ + end)\ + end,\ + tofp = function(self, path, name, type)\ + if #name > 0 then\ + type = tostring(name) .. \": \" .. tostring(type)\ + end\ + type = type:gsub('%/', '$')\ + return self.root .. tostring(path) .. \"/\" .. tostring(type)\ + end,\ + locate = function(self, path, name, type)\ + if not (lfs.attributes(self.root .. path, 'mode')) then\ + return \ + end\ + type = type:gsub('%/', '$')\ + if #name > 0 then\ + name = tostring(name) .. \": \"\ + end\ + name = name .. type\ + name = name:gsub('([^%w])', '%%%1')\ + local file_name\ + for entry_name in lfs.dir(self.root .. path) do\ + if (entry_name:match(\"^\" .. tostring(name) .. \"$\")) or entry_name:match(\"^\" .. tostring(name) .. \"%.%w+$\") then\ + if file_name then\ + error(\"two files match \" .. tostring(name) .. \": \" .. tostring(file_name) .. \" and \" .. tostring(entry_name) .. \"!\")\ + end\ + file_name = entry_name\ + end\ + end\ + return file_name and self.root .. tostring(path) .. \"/\" .. tostring(file_name)\ + end,\ + load_facet = function(self, path, name, type)\ + local filepath = self:locate(path, name, type)\ + if not (filepath) then\ + return \ + end\ + local file = assert((io.open(filepath, 'rb')), \"couldn't open facet file '\" .. tostring(filepath) .. \"'\")\ + do\ + local _with_0 = file:read('*all')\ + file:close()\ + return _with_0\ + end\ + end,\ + create_facet = function(self, path, name, type, blob)\ + self:log(\"creating facet \" .. tostring(path) .. \" | \" .. tostring(name) .. \": \" .. tostring(type))\ + assert(blob, \"cant create facet without value!\")\ + local filepath = self:tofp(path, name, type)\ + if lfs.attributes(filepath, 'mode') then\ + error(\"facet file already exists!\")\ + end\ + local file = assert((io.open(filepath, 'wb')), \"couldn't open facet file '\" .. tostring(filepath) .. \"'\")\ + file:write(blob)\ + return file:close()\ + end,\ + remove_facet = function(self, path, name, type)\ + self:log(\"removing facet \" .. tostring(path) .. \" | \" .. tostring(name) .. \": \" .. tostring(type))\ + local filepath = self:locate(path, name, type)\ + assert(filepath, \"couldn't locate facet!\")\ + return assert(os.remove(filepath))\ + end,\ + rename_facet = function(self, path, name, type, next_name)\ + self:log(\"renaming facet \" .. tostring(path) .. \" | \" .. tostring(name) .. \": \" .. tostring(type) .. \" -> \" .. tostring(next_name))\ + local filepath = self:locate(path, name, type)\ + assert(filepath, \"couldn't locate facet!\")\ + return assert(os.rename(filepath, self:tofp(path, next_name, type)))\ + end,\ + update_facet = function(self, path, name, type, blob)\ + self:log(\"updating facet \" .. tostring(path) .. \" | \" .. tostring(name) .. \": \" .. tostring(type))\ + local filepath = self:locate(path, name, type)\ + assert(filepath, \"couldn't locate facet!\")\ + local file = assert((io.open(filepath, 'wb')), \"couldn't open facet file '\" .. tostring(filepath) .. \"'\")\ + file:write(blob)\ + return file:close()\ + end\ + }\ + _base_0.__index = _base_0\ + _class_0 = setmetatable({\ + __init = function(self, opts)\ + if opts == nil then\ + opts = { }\ + end\ + opts.root = opts.root or 'root'\ + opts.verbose = opts.verbose or false\ + if not opts.verbose then\ + self.log = function() end\ + end\ + self.root = opts.root:match('^(.-)/?$')\ + return self:log(\"opening '\" .. tostring(opts.root) .. \"'...\")\ + end,\ + __base = _base_0,\ + __name = \"FSStore\"\ + }, {\ + __index = _base_0,\ + __call = function(cls, ...)\ + local _self_0 = setmetatable({}, _base_0)\ + cls.__init(_self_0, ...)\ + return _self_0\ + end\ + })\ + _base_0.__class = _class_0\ + FSStore = _class_0\ +end\ +return {\ + FSStore = FSStore\ +}\ +", "mmm/mmmfs/stores/fs.lua") end +if not p["mmm.mmmfs.stores"] then p["mmm.mmmfs.stores"] = load("local require = relative(..., 0)\ +local get_store\ +get_store = function(args, opts)\ + if args == nil then\ + args = 'sql'\ + end\ + if opts == nil then\ + opts = {\ + verbose = true\ + }\ + end\ + local type, arg = args:match('(%w+):(.*)')\ + if not (type) then\ + type = args\ + end\ + local _exp_0 = type:lower()\ + if 'sql' == _exp_0 then\ + local SQLStore\ + SQLStore = require('.sql').SQLStore\ + if arg == 'MEMORY' then\ + opts.memory = true\ + else\ + opts.file = arg\ + end\ + return SQLStore(opts)\ + elseif 'fs' == _exp_0 then\ + local FSStore\ + FSStore = require('.fs').FSStore\ + opts.root = arg\ + return FSStore(opts)\ + else\ + warn(\"unknown or missing value for STORE: valid types values are sql, fs\")\ + return os.exit(1)\ + end\ +end\ +return {\ + get_store = get_store\ +}\ +", "mmm/mmmfs/stores/init.lua") end +if not p["mmm.mmmfs.stores.sql"] then p["mmm.mmmfs.stores.sql"] = load("local sqlite = require('sqlite3')\ +local root = os.tmpname()\ +local SQLStore\ +do\ + local _class_0\ + local _base_0 = {\ + log = function(self, ...)\ + return print(\"[DB]\", ...)\ + end,\ + close = function(self)\ + return self.db:close()\ + end,\ + fetch = function(self, q, ...)\ + local stmt = assert(self.db:prepare(q))\ + if 0 < select('#', ...) then\ + stmt:bind(...)\ + end\ + return stmt:irows()\ + end,\ + fetch_one = function(self, q, ...)\ + local stmt = assert(self.db:prepare(q))\ + if 0 < select('#', ...) then\ + stmt:bind(...)\ + end\ + return stmt:first_irow()\ + end,\ + exec = function(self, q, ...)\ + local stmt = assert(self.db:prepare(q))\ + if 0 < select('#', ...) then\ + stmt:bind(...)\ + end\ + local res = assert(stmt:exec())\ + end,\ + list_fileders_in = function(self, path)\ + if path == nil then\ + path = ''\ + end\ + return coroutine.wrap(function()\ + for _des_0 in self:fetch('SELECT path\\n FROM fileder WHERE parent IS ?', path) do\ + path = _des_0[1]\ + coroutine.yield(path)\ + end\ + end)\ + end,\ + list_all_fileders = function(self, path)\ + if path == nil then\ + path = ''\ + end\ + return coroutine.wrap(function()\ + for path in self:list_fileders_in(path) do\ + coroutine.yield(path)\ + for p in self:list_all_fileders(path) do\ + coroutine.yield(p)\ + end\ + end\ + end)\ + end,\ + create_fileder = function(self, parent, name)\ + local path = tostring(parent) .. \"/\" .. tostring(name)\ + self:log(\"creating fileder \" .. tostring(path))\ + self:exec('INSERT INTO fileder (path, parent)\\n VALUES (:path, :parent)', {\ + path = path,\ + parent = parent\ + })\ + local changes = self:fetch_one('SELECT changes()')\ + assert(changes[1] == 1, \"couldn't create fileder - parent missing?\")\ + return path\ + end,\ + remove_fileder = function(self, path)\ + self:log(\"removing fileder \" .. tostring(path))\ + return self:exec('DELETE FROM fileder\\n WHERE path LIKE :path || \"/%\"\\n OR path = :path', path)\ + end,\ + rename_fileder = function(self, path, next_name)\ + self:log(\"renaming fileder \" .. tostring(path) .. \" -> '\" .. tostring(next_name) .. \"'\")\ + error('not implemented')\ + return self:exec('UPDATE fileder\\n SET path = parent || \"/\" || :next_name\\n WHERE path = :path', {\ + path = path,\ + next_name = next_name\ + })\ + end,\ + move_fileder = function(self, path, next_parent)\ + self:log(\"moving fileder \" .. tostring(path) .. \" -> \" .. tostring(next_parent) .. \"/\")\ + return error('not implemented')\ + end,\ + list_facets = function(self, path)\ + return coroutine.wrap(function()\ + for _des_0 in self:fetch('SELECT facet.name, facet.type\\n FROM facet\\n INNER JOIN fileder ON facet.fileder_id = fileder.id\\n WHERE fileder.path = ?', path) do\ + local name, type\ + name, type = _des_0[1], _des_0[2]\ + coroutine.yield(name, type)\ + end\ + end)\ + end,\ + load_facet = function(self, path, name, type)\ + local v = self:fetch_one('SELECT facet.value\\n FROM facet\\n INNER JOIN fileder ON facet.fileder_id = fileder.id\\n WHERE fileder.path = :path\\n AND facet.name = :name\\n AND facet.type = :type', {\ + path = path,\ + name = name,\ + type = type\ + })\ + return v and v[1]\ + end,\ + create_facet = function(self, path, name, type, blob)\ + self:log(\"creating facet \" .. tostring(path) .. \" | \" .. tostring(name) .. \": \" .. tostring(type))\ + self:exec('INSERT INTO facet (fileder_id, name, type, value)\\n SELECT id, :name, :type, :blob\\n FROM fileder\\n WHERE fileder.path = :path', {\ + path = path,\ + name = name,\ + type = type,\ + blob = blob\ + })\ + local changes = self:fetch_one('SELECT changes()')\ + return assert(changes[1] == 1, \"couldn't create facet - fileder missing?\")\ + end,\ + remove_facet = function(self, path, name, type)\ + self:log(\"removing facet \" .. tostring(path) .. \" | \" .. tostring(name) .. \": \" .. tostring(type))\ + self:exec('DELETE FROM facet\\n WHERE name = :name\\n AND type = :type\\n AND fileder_id = (SELECT id FROM fileder WHERE path = :path)', {\ + path = path,\ + name = name,\ + type = type\ + })\ + local changes = self:fetch_one('SELECT changes()')\ + return assert(changes[1] == 1, \"no such facet\")\ + end,\ + rename_facet = function(self, path, name, type, next_name)\ + self:log(\"renaming facet \" .. tostring(path) .. \" | \" .. tostring(name) .. \": \" .. tostring(type) .. \" -> \" .. tostring(next_name))\ + self:exec('UPDATE facet\\n SET name = :next_name\\n WHERE name = :name\\n AND type = :type\\n AND fileder_id = (SELECT id FROM fileder WHERE path = :path)', {\ + path = path,\ + name = name,\ + next_name = next_name,\ + type = type\ + })\ + local changes = self:fetch_one('SELECT changes()')\ + return assert(changes[1] == 1, \"no such facet\")\ + end,\ + update_facet = function(self, path, name, type, blob)\ + self:log(\"updating facet \" .. tostring(path) .. \" | \" .. tostring(name) .. \": \" .. tostring(type))\ + self:exec('UPDATE facet\\n SET value = :blob\\n WHERE facet.name = :name\\n AND facet.type = :type\\n AND facet.fileder_id = (SELECT id FROM fileder WHERE path = :path)', {\ + path = path,\ + name = name,\ + type = type,\ + blob = blob\ + })\ + local changes = self:fetch_one('SELECT changes()')\ + return assert(changes[1] == 1, \"no such facet\")\ + end\ + }\ + _base_0.__index = _base_0\ + _class_0 = setmetatable({\ + __init = function(self, opts)\ + if opts == nil then\ + opts = { }\ + end\ + opts.file = opts.file or 'db.sqlite3'\ + opts.verbose = opts.verbose or false\ + opts.memory = opts.memory or false\ + if not opts.verbose then\ + self.log = function() end\ + end\ + if opts.memory then\ + self:log(\"opening in-memory DB...\")\ + self.db = sqlite.open_memory()\ + else\ + self:log(\"opening '\" .. tostring(opts.file) .. \"'...\")\ + self.db = sqlite.open(opts.file)\ + end\ + return assert(self.db:exec([[ PRAGMA foreign_keys = ON;\ + PRAGMA case_sensitive_like = ON;\ + CREATE TABLE IF NOT EXISTS fileder (\ + id INTEGER NOT NULL PRIMARY KEY,\ + path TEXT NOT NULL UNIQUE,\ + parent TEXT REFERENCES fileder(path)\ + ON DELETE CASCADE\ + ON UPDATE CASCADE\ + );\ + INSERT OR IGNORE INTO fileder (path, parent) VALUES (\"\", NULL);\ +\ + CREATE TABLE IF NOT EXISTS facet (\ + fileder_id INTEGER NOT NULL\ + REFERENCES fileder\ + ON UPDATE CASCADE\ + ON DELETE CASCADE,\ + name TEXT NOT NULL,\ + type TEXT NOT NULL,\ + value BLOB NOT NULL,\ + PRIMARY KEY (fileder_id, name, type)\ + );\ + CREATE INDEX IF NOT EXISTS facet_fileder_id ON facet(fileder_id);\ + CREATE INDEX IF NOT EXISTS facet_name ON facet(name);\ + ]]))\ + end,\ + __base = _base_0,\ + __name = \"SQLStore\"\ + }, {\ + __index = _base_0,\ + __call = function(cls, ...)\ + local _self_0 = setmetatable({}, _base_0)\ + cls.__init(_self_0, ...)\ + return _self_0\ + end\ + })\ + _base_0.__class = _class_0\ + SQLStore = _class_0\ +end\ +return {\ + SQLStore = SQLStore\ +}\ +", "mmm/mmmfs/stores/sql.lua") end +if not p["mmm.dom"] then p["mmm.dom"] = load("local element\ +element = function(element)\ + return function(...)\ + local children = {\ + ...\ + }\ + local attributes = children[#children]\ + if 'table' == (type(attributes)) and not attributes.node then\ + table.remove(children)\ + else\ + attributes = { }\ + end\ + do\ + local e = document:createElement(element)\ + for k, v in pairs(attributes) do\ + if k == 'class' then\ + k = 'className'\ + end\ + if k == 'style' and 'table' == type(v) then\ + for kk, vv in pairs(v) do\ + e.style[kk] = vv\ + end\ + elseif 'string' == type(k) then\ + e[k] = v\ + end\ + end\ + if #children == 0 then\ + children = attributes\ + end\ + for _index_0 = 1, #children do\ + local child = children[_index_0]\ + if 'string' == type(child) then\ + child = document:createTextNode(child)\ + end\ + e:appendChild(child)\ + end\ + return e\ + end\ + end\ +end\ +return setmetatable({ }, {\ + __index = function(self, name)\ + do\ + local val = element(name)\ + self[name] = val\ + return val\ + end\ + end\ +})\ +", "mmm/dom/init.client.lua") end +if not p["mmm.component"] then p["mmm.component"] = load("local tohtml\ +tohtml = function(val)\ + if 'string' == type(val) then\ + return document:createTextNode(val)\ + end\ + if 'table' == type(val) then\ + assert(val.node, \"Table doesn't have .node\")\ + val = val.node\ + end\ + if 'userdata' == type(val) then\ + assert((js.instanceof(val, js.global.Node)), \"userdata is not a Node\")\ + return val\ + else\ + return error(\"not a Node: \" .. tostring(val) .. \", \" .. tostring(type(val)))\ + end\ +end\ +local text\ +text = function(str)\ + return document:createTextNode(tostring(str))\ +end\ +local ReactiveVar\ +do\ + local _class_0\ + local _base_0 = {\ + set = function(self, value)\ + local old = self.value\ + self.value = value\ + for k, callback in pairs(self.listeners) do\ + callback(self.value, old)\ + end\ + end,\ + get = function(self)\ + return self.value\ + end,\ + transform = function(self, transform)\ + return self:set(transform(self:get()))\ + end,\ + subscribe = function(self, callback)\ + do\ + local _with_0\ + _with_0 = function()\ + self.listeners[callback] = nil\ + end\ + self.listeners[callback] = callback\ + return _with_0\ + end\ + end,\ + map = function(self, transform)\ + do\ + local _with_0 = ReactiveVar(transform(self.value))\ + _with_0.upstream = self:subscribe(function(...)\ + return _with_0:set(transform(...))\ + end)\ + return _with_0\ + end\ + end\ + }\ + _base_0.__index = _base_0\ + _class_0 = setmetatable({\ + __init = function(self, value)\ + self.value = value\ + self.listeners = setmetatable({ }, {\ + __mode = 'kv'\ + })\ + end,\ + __base = _base_0,\ + __name = \"ReactiveVar\"\ + }, {\ + __index = _base_0,\ + __call = function(cls, ...)\ + local _self_0 = setmetatable({}, _base_0)\ + cls.__init(_self_0, ...)\ + return _self_0\ + end\ + })\ + _base_0.__class = _class_0\ + local self = _class_0\ + self.isinstance = function(val)\ + return 'table' == (type(val)) and val.subscribe\ + end\ + ReactiveVar = _class_0\ +end\ +local ReactiveElement\ +do\ + local _class_0\ + local _base_0 = {\ + destroy = function(self)\ + local _list_0 = self._subscriptions\ + for _index_0 = 1, #_list_0 do\ + local unsub = _list_0[_index_0]\ + unsub()\ + end\ + end,\ + set = function(self, attr, value)\ + if attr == 'class' then\ + attr = 'className'\ + end\ + if 'table' == (type(value)) and ReactiveVar.isinstance(value) then\ + table.insert(self._subscriptions, value:subscribe(function(...)\ + return self:set(attr, ...)\ + end))\ + value = value:get()\ + end\ + if attr == 'style' and 'table' == type(value) then\ + for k, v in pairs(value) do\ + self.node.style[k] = v\ + end\ + return \ + end\ + self.node[attr] = value\ + end,\ + prepend = function(self, child, last)\ + return self:append(child, last, 'prepend')\ + end,\ + append = function(self, child, last, mode)\ + if mode == nil then\ + mode = 'append'\ + end\ + if ReactiveVar.isinstance(child) then\ + table.insert(self._subscriptions, child:subscribe(function(...)\ + return self:append(...)\ + end))\ + child = child:get()\ + end\ + if 'string' == type(last) then\ + error('cannot replace string node')\ + end\ + if child == nil then\ + if last then\ + self:remove(last)\ + end\ + return \ + end\ + child = tohtml(child)\ + if last then\ + return self.node:replaceChild(child, tohtml(last))\ + else\ + local _exp_0 = mode\ + if 'append' == _exp_0 then\ + return self.node:appendChild(child)\ + elseif 'prepend' == _exp_0 then\ + return self.node:insertBefore(child, self.node.firstChild)\ + end\ + end\ + end,\ + remove = function(self, child)\ + self.node:removeChild(tohtml(child))\ + if 'table' == (type(child)) and child.destroy then\ + return child:destroy()\ + end\ + end\ + }\ + _base_0.__index = _base_0\ + _class_0 = setmetatable({\ + __init = function(self, element, ...)\ + if 'userdata' == type(element) then\ + self.node = element\ + else\ + self.node = document:createElement(element)\ + end\ + self._subscriptions = { }\ + local children = {\ + ...\ + }\ + local attributes = children[#children]\ + if 'table' == (type(attributes)) and (not ReactiveElement.isinstance(attributes)) and (not ReactiveVar.isinstance(attributes)) then\ + table.remove(children)\ + else\ + attributes = { }\ + end\ + for k, v in pairs(attributes) do\ + if 'string' == type(k) then\ + self:set(k, v)\ + end\ + end\ + if #children == 0 then\ + children = attributes\ + end\ + for _index_0 = 1, #children do\ + local child = children[_index_0]\ + self:append(child)\ + end\ + end,\ + __base = _base_0,\ + __name = \"ReactiveElement\"\ + }, {\ + __index = _base_0,\ + __call = function(cls, ...)\ + local _self_0 = setmetatable({}, _base_0)\ + cls.__init(_self_0, ...)\ + return _self_0\ + end\ + })\ + _base_0.__class = _class_0\ + local self = _class_0\ + self.isinstance = function(val)\ + return 'table' == (type(val)) and val.node\ + end\ + ReactiveElement = _class_0\ +end\ +local get_or_create\ +get_or_create = function(elem, id, ...)\ + elem = (document:getElementById(id)) or elem\ + do\ + local _with_0 = ReactiveElement(elem, ...)\ + _with_0:set('id', id)\ + return _with_0\ + end\ +end\ +local elements = setmetatable({ }, {\ + __index = function(self, name)\ + do\ + local val\ + val = function(...)\ + return ReactiveElement(name, ...)\ + end\ + self[name] = val\ + return val\ + end\ + end\ +})\ +return {\ + ReactiveVar = ReactiveVar,\ + ReactiveElement = ReactiveElement,\ + get_or_create = get_or_create,\ + tohtml = tohtml,\ + text = text,\ + elements = elements\ +}\ +", "mmm/component/init.client.lua") end diff --git a/root/static/style/text$css.css b/root/static/style/text$css.css new file mode 100644 index 0000000..c0dc484 --- /dev/null +++ b/root/static/style/text$css.css @@ -0,0 +1,411 @@ +:root { + --gray-bright: #eeeeee; + --gray-dark: #303336; + --gray-darker: #1d1f21; + --gray-neutral: #b9bdc1; + --gray-success: #bdddc1; + --gray-fail: #ddbdc1; + --margin-wide: 2rem; } + @media only screen and (max-width: 425px) { + :root { + --margin-wide: 0.5rem; } } + +* { + font-weight: inherit; + font-style: inherit; + font-family: inherit; + color: inherit; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; } + +body, html, h1, h2, h3, h4, h5, h6 { + margin: 0; + padding: 0; } + +h1, h2, h3, h4, h5, h6 { + font-weight: bold; } + +input, select, button { + color: initial; } + +tt, code, kbd, samp { + font-family: monospace; } + +code { + font-size: 0.8em; } + +b, strong { + font-weight: bold; } + +em, i { + font-style: italic; } + +a { + font-size: 1em; + cursor: pointer; } + +hr { + clear: both; } + +ul { + margin: 0; } + +body { + display: flex; + flex-direction: column; + justify-content: space-between; + background: #ffffff; + font-family: sans-serif; + min-height: 100vh; } + +a { + display: inline-block; + font-weight: bold; + text-decoration: underline; + text-decoration-color: transparent; + transition: filter 500ms, text-decoration-color 500ms; } + a:hover { + filter: invert(40%); + text-decoration-color: currentColor; } + +::selection { + background: #303336; + color: #eeeeee; + padding: 1em; } + +/* + +vim-hybrid theme by w0ng (https://github.com/w0ng/vim-hybrid) + +*/ +/*background color*/ +.hljs { + background: #1d1f21; + border-radius: 6px; + padding: 0.2em 0.5em; + white-space: nowrap; } + +pre > .hljs { + display: block; + overflow-x: auto; + padding: 1em; + white-space: pre-wrap; + margin: 0 2em; } + +/*selection color*/ +.hljs::selection, +.hljs span::selection { + background: #373b41; } + +.hljs::-moz-selection, +.hljs span::-moz-selection { + background: #373b41; } + +/*foreground color*/ +.hljs { + color: #c5c8c6; } + +/*color: fg_yellow*/ +.hljs-title, +.hljs-name { + color: #f0c674; } + +/*color: fg_comment*/ +.hljs-comment, +.hljs-meta, +.hljs-meta .hljs-keyword { + color: #707880; } + +/*color: fg_red*/ +.hljs-number, +.hljs-symbol, +.hljs-literal, +.hljs-deletion, +.hljs-link { + color: #cc6666; } + +/*color: fg_green*/ +.hljs-string, +.hljs-doctag, +.hljs-addition, +.hljs-regexp, +.hljs-selector-attr, +.hljs-selector-pseudo { + color: #b5bd68; } + +/*color: fg_purple*/ +.hljs-attribute, +.hljs-code, +.hljs-selector-id { + color: #b294bb; } + +/*color: fg_blue*/ +.hljs-keyword, +.hljs-selector-tag, +.hljs-bullet, +.hljs-tag { + color: #81a2be; } + +/*color: fg_aqua*/ +.hljs-subst, +.hljs-variable, +.hljs-template-tag, +.hljs-template-variable { + color: #8abeb7; } + +/*color: fg_orange*/ +.hljs-type, +.hljs-built_in, +.hljs-builtin-name, +.hljs-quote, +.hljs-section, +.hljs-selector-class { + color: #de935f; } + +.hljs-emphasis { + font-style: italic; } + +.hljs-strong { + font-weight: bold; } + +header > div { + font-family: 'Source Sans Pro', sans-serif; + padding: 1rem 2rem; + text-align: right; + color: #eeeeee; + background: #303336; } + header > div h1 { + display: flex; + justify-content: flex-end; + align-items: center; + flex-wrap: wrap; + margin: 0; + line-height: 5rem; + font-size: 4rem; + font-weight: 200; } + header > div h1 > span { + margin-left: auto; } + header > div h1 .bold { + font-weight: 400; } + header > div h1 > a { + height: 5rem; } + header > div h1 .sun { + height: 100%; + stroke: currentColor; + fill: currentColor; } + header > div h1 .sun .out, header > div h1 .sun .in { + animation: spin 2s; + animation-iteration-count: 1; + transform: scale(1); } + header > div h1 .sun .in { + animation: size 2s; } + header > div h1 .sun.spin .circle { + animation: none; } + +header aside { + margin: 0; + padding: 0 2rem; + color: #eeeeee; + background: #1d1f21; + line-height: 3em; } + header aside > a { + margin-right: 2em; } + +@keyframes spin { + 0% { + transform: rotate3d(0.5, 1, 0, 0turn); } + 100% { + transform: rotate3d(0.5, 1, 0, 1turn); } } + +@keyframes size { + 0%, 100% { + transform: scale(1); } + 40%, 60% { + transform: scale(1.8); } } + +footer { + display: flex; + padding: 1rem 2rem; + position: sticky; + bottom: 0; + color: #eeeeee; + background: #303336; } + footer .icons { + flex: 1; + display: flex; + justify-content: flex-end; } + footer .icons .iconlink { + filter: invert(0.7); } + footer .icons .iconlink:hover { + filter: invert(1); } + footer .icons .iconlink > img { + height: 1em; + margin-left: 0.5em; + vertical-align: middle; } + +.browser { + display: flex; + flex: 1; + border-top: 0.2rem solid #eeeeee; } + +.view { + display: flex; + flex-direction: column; + flex: 1 1 0; + min-width: 0; } + .view.inspector { + top: 0; + position: sticky; + max-height: 100vh; + color: #c5c8c6; + border-style: solid; + border-width: 0 0 0 0.6rem; + border-radius: 0 6px 6px 0; + border-color: #303336; + background: #303336; } + .view.inspector nav { + background: inherit; } + .view.inspector .content { + margin: 0; + padding: 0; + display: flex; + background: #1d1f21; } + .view.inspector .content > * { + display: block; + margin: 0; + padding: 1em; + border-radius: 0; + border: 0; + flex: 1; } + .view nav { + position: sticky; + top: 0; + display: flex; + flex: 0 0 auto; + flex-wrap: wrap; + justify-content: space-between; + background: #eeeeee; + z-index: 1000; } + .view nav > span:first-of-type { + flex: 1 0 auto; } + .view nav > * { + margin: 1em; + white-space: nowrap; } + @media only screen and (max-width: 425px) { + .view nav > .inspect-btn { + display: none; } } + .view .error-wrap { + flex: 0 0 auto; + padding: 1em 2em; + overflow: hidden; + background: #ddbdc1; } + .view .error-wrap.empty { + display: none; } + .view .error-wrap > span { + display: inline-block; + margin-bottom: 0.5em; + font-weight: bold; + color: #f00; } + .view .error-wrap > pre { + margin: 0; } + .view .content { + flex: 1 1 auto; + overflow: auto; + padding: 1em 2em; + position: relative; } + +.content { + opacity: 1; + transition: opacity 150ms; } + body.loading .content { + opacity: 0; } + +.content img, .content video { + width: inherit; + height: inherit; } + +.content .markdown > img, .content .markdown > video, +.content .markdown > p > img, +.content .markdown > p > video, +.content .markdown > p > a > img, +.content .markdown > p > a > video, +.content .markdown > a > img, +.content .markdown > a > video { + display: block; + max-width: 100%; + max-height: 50vh; + padding: 0 2em; + box-sizing: border-box; } + +.content .embed { + width: inherit; + height: inherit; } + .content .embed .description { + text-align: center; } + .content .embed.inline { + display: inline-block; } + .content .embed.desc { + display: inline-block; + padding: 0.5em; + margin: 0.2em; + background: #eeeeee; } + +.content pre > code { + border-style: solid; + border-width: 0 0 0 0.6rem; + border-radius: 0 6px 6px 0; + border-color: #303336; + display: block; + border-radius: 6px; + margin: 0 var(--margin-wide); + padding: 1em; + white-space: pre-wrap; + overflow-x: auto; + background: #1d1f21; + color: #c5c8c6; } + +.content pre.dual-code { + display: flex; + justify-content: space-between; + padding: 0 2rem; } + .content pre.dual-code > code { + border-style: solid; + border-width: 0 0.3rem 0 0; + border-radius: 6px 0 0 6px; + flex: 1; + margin: 0; } + .content pre.dual-code > code + code { + border-style: solid; + border-width: 0 0 0 0.3rem; + border-radius: 0 6px 6px 0; } + +.content .example, .content .well { + border-style: solid; + border-width: 0 0 0 0.6rem; + border-radius: 0 6px 6px 0; + margin: 1rem var(--margin-wide); + padding: 1rem; + background: #eeeeee; + border-color: #b9bdc1; } + +.canvas_app { + position: relative; + display: inline-block; } + .canvas_app .overlay { + position: absolute; + padding: 1rem; + top: 0; + left: 0; + right: 0; + bottom: 0; + opacity: 0; + background: rgba(0, 0, 0, 0.7); + transition: opacity 300ms; } + .canvas_app .overlay:hover { + opacity: 1; } + .canvas_app .overlay > * { + margin: 0.5em; } + .canvas_app .overlay a { + display: block; + color: #eeeeee; + font-family: inherit; }