Browse Source

Starting with frames.

Manuel Gall 3 years ago
commit
fac7336f46
15 changed files with 913 additions and 0 deletions
  1. 1 0
      .vimrc
  2. 70 0
      LICENSE
  3. 0 0
      README.md
  4. 1 0
      data/.gitignore
  5. 226 0
      frames
  6. 1 0
      frames.conf
  7. 78 0
      frames.xml
  8. 20 0
      rngs/document.rng
  9. 13 0
      rngs/langs.rng
  10. BIN
      template/css/BabelStoneFlags.ttf
  11. 112 0
      template/css/ui.css
  12. 71 0
      template/js/language.js
  13. 195 0
      template/js/ui.js
  14. 44 0
      template/js/ui2.js
  15. 81 0
      template/template.html

+ 1 - 0
.vimrc

@@ -0,0 +1 @@
+map <F9> :!./wiz -v restart<CR>

File diff suppressed because it is too large
+ 70 - 0
LICENSE


+ 0 - 0
README.md


+ 1 - 0
data/.gitignore

@@ -0,0 +1 @@
+*/*

+ 226 - 0
frames

@@ -0,0 +1,226 @@
+#!/usr/bin/ruby
+#
+# This file is part of centurio.work/ing/commands.
+#
+# centurio.work/ing/commands is free software: you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or (at your
+# option) any later version.
+#
+# centurio.work/ing/commands is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
+# Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# centurio.work/ing/commands (file COPYING in the main directory). If not, see
+# <http://www.gnu.org/licenses/>.
+
+require 'rubygems'
+require 'json'
+require 'xml/smart'
+require 'riddl/server'
+require 'fileutils'
+require 'typhoeus'
+
+class Get < Riddl::Implementation
+  def response
+    Riddl::Parameter::Complex.new('ui','text/html',File.open(File.join(__dir__,'template','template.html')))
+  end
+end
+
+class Put < Riddl::Implementation
+  def response
+    Dir.mkdir(File.join('data',@r.last)) rescue nil
+    File.write(File.join('data',@r.last,'num'),@p[0].value)
+    File.write(File.join('data',@r.last,'total'),@p[1].value)
+    File.write(File.join('data',@r.last,'style.url'),@p[2].value)
+
+    image = Typhoeus.get(@p[3].value).response_body rescue '<image/>'
+    File.write(File.join('data',@r.last,'image.xml'),image)
+
+    if @p[4]&.name == 'errors_url'
+      errors = Typhoeus.get(@p[4].value).response_body rescue '<reasons/>'
+      File.write(File.join('data',@r.last,'errors.xml'),errors)
+    end
+    if @p[4]&.name == 'info'
+      File.write(File.join('data',@r.last,'info.json'),@p[4].value)
+    end
+    if @p[5]&.name == 'info'
+      File.write(File.join('data',@r.last,'info.json'),@p[5].value)
+    end
+
+    File.write(File.join('data',@r.last,'callback'),@h['CPEE_CALLBACK'])
+
+    @a[0].send('new')
+    nil
+  end
+
+  def headers
+    Riddl::Header.new('CPEE-CALLBACK', 'true')
+  end
+end
+class Delete < Riddl::Implementation
+  def response
+    if cbu = File.read(File.join('data',@r.last,'callback'))
+      send = { 'operation' => @p[0].value }
+      case send['operation']
+        when 'jump'
+          send['target'] = @p[1].value
+        when 'error'
+          send['reason'] = @p[1].value
+      end
+      cbu += '/' unless cbu[-1] == '/'
+
+      Typhoeus.put(cbu, body: JSON::generate(send), headers: { 'content-type' => 'application/json'})
+    end
+
+    File.unlink(File.join('data',@r.last,'callback')) rescue nil
+    File.unlink(File.join('data',@r.last,'num')) rescue nil
+    File.unlink(File.join('data',@r.last,'total')) rescue nil
+    File.unlink(File.join('data',@r.last,'style.url')) rescue nil
+    File.unlink(File.join('data',@r.last,'image.xml')) rescue nil
+    File.unlink(File.join('data',@r.last,'errors.xml')) rescue nil
+
+    if @p[0].value == 'finish' || @p[0].value == 'error'
+      @a[0].send('reset')
+    end
+    nil
+  end
+end
+
+class GetInfo < Riddl::Implementation #{{{
+  def response
+    fname = File.join('data',@r[-2],'info.json')
+    if File.exists? fname
+      Riddl::Parameter::Complex.new('value','application/json',File.read(fname))
+    else
+      @status = 404
+    end
+  end
+end #}}}
+class GetLangs < Riddl::Implementation #{{{
+  def response
+    fname = File.join('data',@r[-2],'image.xml')
+    if File.exists? fname
+      doc = XML::Smart.open_unprotected(fname)
+      ndoc = XML::Smart.string('<languages/>')
+      doc.find('//@lang').each do |e|
+        ndoc.root.add('language',e.value)
+      end
+      Riddl::Parameter::Complex.new('value','text/xml',ndoc.to_s)
+    else
+      @status = 404
+    end
+  end
+end #}}}
+class GetStyle < Riddl::Implementation #{{{
+  def response
+    fname = File.join('data',@r[-2],'style.url')
+    if File.exists? fname
+      Riddl::Parameter::Complex.new('url','text/plain',File.read(fname).strip)
+    else
+      @status = 404
+    end
+  end
+end #}}}
+class GetDocument < Riddl::Implementation #{{{
+  def response
+    fname = File.join('data',@r[-3],'image.xml')
+    if File.exists? fname
+      doc = XML::Smart.open_unprotected(fname)
+      val = nil
+      doc.find("//variant[@lang='#{@r[-1]}']").each do |e|
+        val = e.text
+      end
+      if val
+        Riddl::Parameter::Complex.new('url','text/plain',val.strip)
+      else
+        @status = 404
+      end
+    else
+      @status = 404
+    end
+  end
+end #}}}
+
+class SSE < Riddl::SSEImplementation #{{{
+  def onopen
+    signals = @a[0]
+    signals.add self
+    send 'started'
+  end
+
+  def onclose
+    signals = @a[0]
+    signals.remove self
+    nil
+  end
+end #}}}
+
+class Signaling # {{{
+  def initialize
+    @binding = []
+  end
+
+  def add(binding)
+    @binding << binding
+  end
+  def remove(binding)
+    @binding.delete(binding)
+  end
+  def length
+    @binding.length
+  end
+
+  def send(value)
+    @binding.each do |b|
+      b.send(value)
+    end
+  end
+end #}}}
+
+server = Riddl::Server.new(File.join(__dir__,'/frames.xml'), :host => 'localhost') do |opts|
+  accessible_description true
+  cross_site_xhr true
+
+  opts[:signals] = {}
+
+  parallel do
+    loop do
+      opts[:signals].each do |k,v|
+        v.send('keepalive')
+      end
+      sleep 5
+    end
+  end
+
+  on resource do
+    on resource do |r|
+      idx = r[:r][0]
+      opts[:signals][idx] ||= Signaling.new
+
+      run Get if get
+      run Put, opts[:signals][idx] if put 'input'
+      run Delete, opts[:signals][idx] if delete 'opa'
+      run Delete, opts[:signals][idx] if delete 'opb'
+      on resource 'sse' do
+        run SSE, opts[:signals][idx] if sse
+      end
+      on resource 'languages' do
+        run GetLangs if get
+      end
+      on resource 'style.url' do
+        run GetStyle if get
+      end
+      on resource 'info.json' do
+        run GetInfo if get
+      end
+      on resource 'documents' do
+        on resource '[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*' do
+          run GetDocument if get
+        end
+      end
+    end
+  end
+end.loop!

+ 1 - 0
frames.conf

@@ -0,0 +1 @@
+:port: 8305

+ 78 - 0
frames.xml

@@ -0,0 +1,78 @@
+<!--
+  This file is part of centurio.work/wiz.
+
+  centurio.work/wiz is free software: you can redistribute it and/or modify it
+  under the terms of the GNU General Public License as published by the Free
+  Software Foundation, either version 3 of the License, or (at your option) any
+  later version.
+
+  centurio.work/wiz is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+  more details.
+
+  You should have received a copy of the GNU General Public License along with
+  centurio.work/wiz (file LICENSE in the main directory). If not, see
+  <http://www.gnu.org/licenses/>.
+-->
+
+<description xmlns="http://riddl.org/ns/description/1.0" xmlns:ann="http://riddl.org/ns/annotation/1.0" xmlns:xi="http://www.w3.org/2001/XInclude" datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">
+  <message name="input">
+    <parameter name="style_url" type="string"/>
+    <parameter name="document_url" type="string"/>
+    <optional>
+      <parameter name="info" type="string"/>
+    </optional>
+  </message>
+  <message name="ui">
+    <parameter name="ui" mimetype="text/html"/>
+  </message>
+  <message name="callback">
+    <header name="CPEE-CALLBACK" type="boolean"/>
+  </message>
+  <message name="url">
+    <parameter name="url" mimetype="text/plain"/>
+  </message>
+  <message name="langs">
+    <parameter name="langs" mimetype="*/xml">
+      <xi:include href="rngs/langs.rng"/>
+    </parameter>
+  </message>
+  <message name="opa">
+    <parameter name="op" fixed="error"/>
+    <parameter name="reason" type="string"/>
+  </message>
+  <message name="opb">
+    <parameter name="op" fixed="result"/>
+    <parameter name="value" mimetype="application/json"/>
+  </message>
+  <message name="json">
+    <parameter name="value" mimetype="application/json"/>
+  </message>
+
+  <resource>
+    <resource>
+      <get out="ui"/>
+      <put in='input' out="callback"/>
+      <delete in='opa'/>
+      <delete in='opb'/>
+      <resource relative='sse'>
+        <sse/>
+      </resource>
+      <resource relative="languages">
+        <get out="langs"/>
+      </resource>
+      <resource relative="style.url">
+        <get out="url"/>
+      </resource>
+      <resource relative="documents">
+        <resource relative="[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*">
+            <get out="url"/>
+        </resource>
+      </resource>
+      <resource relative="info.json">
+        <get out="json"/>
+      </resource>
+    </resource>
+  </resource>
+</description>

+ 20 - 0
rngs/document.rng

@@ -0,0 +1,20 @@
+<grammar xmlns="http://relaxng.org/ns/structure/1.0" datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">
+  <start>
+    <element name="document">
+      <attribute name="id">
+        <data type="nonNegativeInteger"/>
+      </attribute>
+      <element name="label">
+        <data type="string"/>
+      </element>
+      <oneOrMore>
+        <element name="variant">
+          <attribute name="lang">
+            <data type="language"/>
+          </attribute>
+          <data type="string"/>
+        </element>
+      </oneOrMore>
+    </element>
+  </start>
+</grammar>

+ 13 - 0
rngs/langs.rng

@@ -0,0 +1,13 @@
+<grammar xmlns="http://relaxng.org/ns/structure/1.0" datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">
+  <start>
+    <element name="languages">
+      <oneOrMore>
+        <element name="language">
+          <attribute name="lang">
+            <data type="language"/>
+          </attribute>
+        </element>
+      </oneOrMore>
+    </element>
+  </start>
+</grammar>

BIN
template/css/BabelStoneFlags.ttf


+ 112 - 0
template/css/ui.css

@@ -0,0 +1,112 @@
+:root {
+  --content-height: 100vh;
+  --language-height: 10vh;
+  --bottom-height: 10vh;
+  --nav-top: 25vh;
+}
+
+[is="x-ui"] {
+  font-family: sans-serif;
+  font-size: 1.5em;
+}
+
+body {
+  margin: 0;
+  padding: 0;
+}
+.hidden {
+  display: none !important;
+}
+
+#content {
+  display: table;
+  padding: 0;
+  margin: 0;
+  width: 100%;
+  height: var(--content-height);
+}
+#content .added {
+  background-repeat: no-repeat;
+  background-position: center;
+  background-size: contain;
+  width: 100vw;
+  height: 100vh;
+}
+
+#control {
+  position: absolute;
+  padding: 0;
+  margin: 0;
+  height: var(--bottom-height);
+  bottom: 0;
+  width: 100%;
+}
+#control > button {
+  position: absolute;
+  bottom: 0.5em;
+  outline: 0;
+}
+#control > button::-moz-focus-inner {
+  border: 0;
+}
+#control button[name=previous] {
+  left: 4em;
+}
+#control button[name=first] {
+  left: 1em;
+}
+#control button[name=error] {
+  left: 50%;
+  transform: translate(-50%, 0);
+}
+#control button[name=next] {
+  right: 4em;
+}
+#control button[name=last] {
+  right: 1em;
+}
+
+#reasons {
+  position: absolute;
+  bottom: var(--bottom-height);
+  left: 50%;
+  transform: translate(-50%, 0);
+}
+#reasons button {
+  width: 100%;
+}
+
+#nope {
+  display: table-cell;
+  font-weight: bold;
+  vertical-align: middle;
+  text-align: center;
+  height: 100%;
+  width: 100%;
+  font-size: 3em;
+}
+
+#nav {
+  position: absolute;
+  right: 0;
+  top: var(--nav-top);
+  left: 0;
+}
+
+#languages {
+  position: absolute;
+  right: 0;
+  top: 0;
+
+  flex: 0 1 auto;
+  text-align: right;
+  font-size: var(--language-height);
+}
+@font-face {
+  font-family: flag-font;
+  src: local("BabelStoneFlags.ttf") format("truetype");
+}
+.lang-select-item {
+  padding-right: 0.1em;
+  font-family: flag-font;
+}

+ 71 - 0
template/js/language.js

@@ -0,0 +1,71 @@
+function lang_init(obj,target){
+  if (obj == null) {
+    obj = $(document);
+  } else {
+    obj = $(obj);
+  }
+  $(target).empty();
+  let langSelect = new Set();
+  $("[lang]", obj).each(function(){
+    if(langSelect.has($(this).attr("lang"))){ return true; }
+    $(target).append("<span class=\"lang-select-item\" data-lang="+$(this).attr("lang")+">"+countryCodeToUTF8Flag($(this).attr("lang"))+"</span>");
+    langSelect.add($(this).attr("lang"));
+  });
+  if(langSelect.size <= 1){
+    $(target).hide();
+    return;
+  }
+  $(target).show();
+  displayLang($("html").attr("lang"), obj);
+  $(".lang-select-item").click(function(){
+    $("html").attr("lang", $(this).data("lang"));
+    let lang = $("html").attr("lang");
+    displayLang(lang);
+  });
+}
+
+function displayLang(lang, obj){
+  if(obj == null) obj = $(document);
+  let elementsWithLangAttr = new Set();
+  let elementIdsLangTrue = new Set();
+  $("[lang]").each(function(){
+    if($(this).is("html")){
+      return true;
+    }
+    elementsWithLangAttr.add($(this).attr("id"));
+    if($(this).attr("lang") != lang){
+      $(this).hide();
+    }else{
+      $(this).show();
+      elementIdsLangTrue.add($(this).attr("id"));
+    }
+  });
+  let elemWithLangAttrArr = Array.from(elementsWithLangAttr);
+  let elemIdsLangFalse = elemWithLangAttrArr.filter(x => !elementIdsLangTrue.has(x));
+  elemIdsLangFalse.forEach(function(x){
+    $("#"+x).first().show();
+  });
+}
+
+function countryCodeToUTF8Flag(code){
+  let charCodeA = "A".charCodeAt(0);
+  let utf8CountryIndicatorA = 127462;
+  let langCode = code;
+  let langFlagMap = new Map([["en", "gb"]]);
+  if(langFlagMap.has(code.toLowerCase())){
+    langCode = langFlagMap.get(code);
+  }
+  const utf8CountryIndicator = function(letter) {return (letter.toUpperCase().charCodeAt(0) - charCodeA) + utf8CountryIndicatorA;}
+  let firstIndicator = utf8CountryIndicator(langCode.charAt(0));
+  let secondIndicator = utf8CountryIndicator(langCode.charAt(1));
+  return "&#" + firstIndicator + ";&#" + secondIndicator + ";";
+}
+
+function fitTextToDiv(divTextElem, divFitToHeight, divFitToWidth){
+  let measureDiv = $('<div/>').text(divTextElem.text()).css("font-size", divTextElem.css("font-size"));
+  console.log($(this));
+  divTextElem.css("font-size", parseInt(divTextElem.css("font-size"))*divFitToWidth/divTextElem.width());
+  if(divTextElem.height() > divFitToHeight){
+    divTextElem.css("font-size", parseInt(divTextElem.css("font-size"))*divFitToHeight/divTextElem.height());
+  }
+}

+ 195 - 0
template/js/ui.js

@@ -0,0 +1,195 @@
+var reason ="";
+
+function showImage() {
+  $.ajax({
+    type: "GET",
+    url: 'languages',
+    success: function(ret) {
+      $('#content .added').remove();
+      var template = $('#content template')[0];
+      var promises = [];
+      $('language',ret).each(function(i,e){
+        var clone = document.importNode(template.content, true);
+        promises.push(new Promise((resolve, reject) => {
+          $('> *',clone).each(function(j,c){
+            $(c).addClass('added');
+            $(c).attr('lang', e.textContent);
+            $.ajax({
+              type: "GET",
+              url: 'images/' + e.textContent,
+              success: function(img) {
+                $(c).attr('style','background-image: url("' + img +'")');
+                $('#content').append(c);
+                resolve(true);
+              },
+              error: function() {
+                reject(false);
+                setTimeout(function(){ showImage(); }, 500);
+              }
+            });
+          });
+        }));
+      });
+      Promise.all(promises).then((values) => {
+        $.ajax({
+          type: "GET",
+          url: 'style.url',
+          success: function(ret) {
+            $('head link.custom').attr('href',ret);
+          }
+        });
+        $.ajax({
+          type: "GET",
+          url: 'num',
+          success: function(num) {
+            if (num == 1) {
+              $("button[name=first]").addClass('hidden');
+              $("button[name=previous]").addClass('hidden');
+            } else {
+              $("button[name=first]").removeClass('hidden');
+              $("button[name=previous]").removeClass('hidden');
+            }
+            $.ajax({
+              type: "GET",
+              url: 'total',
+              success: function(total) {
+                if (num == total) {
+                  $("button[name=last]").addClass('hidden');
+                  $("button[name=next]").addClass('hidden');
+                } else {
+                  $("button[name=last]").removeClass('hidden');
+                  $("button[name=next]").removeClass('hidden');
+                }
+              }
+            });
+          }
+        });
+        $.ajax({
+          type: "GET",
+          url: 'errors.xml',
+          success: function(ret) {
+            $("button[name=error]").removeClass('hidden');
+          }
+        });
+        lang_init('#content','#languages');
+        $('#languages').removeClass('hidden');
+        $('#nav').removeClass('hidden');
+        $('#nope').addClass('hidden');
+      });
+    },
+    error: function() {
+      reason = '';
+      clearImage();
+    }
+  });
+}
+function clearImage() {
+  $('#languages').addClass('hidden');
+  $('#reasons').addClass('hidden');
+  $('#nav').addClass('hidden');
+  $('#nav button').addClass('hidden');
+  $('#nope').removeClass('hidden');
+  $("button").addClass('hidden');
+  $('#content .added').remove();
+  $('#reason').text(reason);
+}
+
+function init() {
+  es = new EventSource('sse/');
+  es.onopen = function() {
+    showImage();
+    // load
+  };
+  es.onmessage = function(e) {
+    if (e.data == 'new') {
+      reason = '';
+      showImage();
+    }
+    if (e.data == 'reset') {
+      reason = '';
+      clearImage();
+    }
+  };
+  es.onerror = function() {
+    reason = 'Server down.';
+    clearImage();
+    setTimeout(init, 10000);
+  };
+}
+
+$(document).ready(function() {
+  init();
+  $("button[name=next]").click(b_next);
+  $("button[name=previous]").click(b_previous);
+  $("button[name=error]").click(b_error);
+  $("button[name=first]").click(b_first);
+  $("button[name=last]").click(b_last);
+  $("#reasons").on('click','button',b_reason);
+  $('body').keypress(function(e){
+    if (e.originalEvent.key == 'a' && !$("button[name=previous]").hasClass('hidden')) {
+      b_previous();
+    }
+    if (e.originalEvent.key == 'c' && !$("button[name=next]").hasClass('hidden')) {
+      b_next();
+    }
+  });
+});
+
+function b_previous() {
+  $.ajax({
+    type: "DELETE",
+    data: { op: "prev" },
+    url: location.href
+  });
+}
+
+function b_next() {
+  $.ajax({
+    type: "DELETE",
+    data: { op: "next" },
+    url: location.href
+  });
+}
+function b_first() {
+  $.ajax({
+    type: "DELETE",
+    data: { op: "jump", target: 1 },
+    url: location.href
+  });
+}
+function b_last() {
+  $.ajax({
+    type: "DELETE",
+    data: { op: "jump", target: -1 },
+    url: location.href
+  });
+}
+
+function b_reason() {
+  var reason = $(this).text();
+  $('#reasons').toggleClass('hidden');
+  $.ajax({
+    type: "DELETE",
+    data: { op: "error", reason: reason },
+    url: location.href
+  });
+}
+function b_error() {
+  $.ajax({
+    type: "get",
+    url: "errors.xml",
+    success: function(x) {
+      $('#reasons .added').remove();
+      var template = $('#reasons template')[0];
+      $('reason',x).each((k,v) => {
+        var clone = document.importNode(template.content, true);
+        $('> *',clone).each((j,c) => {
+          $(c).addClass('error added');
+          $('button',c).text(v.textContent);
+        });
+        $('#reasons').append(clone);
+      });
+      $('#reasons').toggleClass('hidden');
+    }
+  });
+}

+ 44 - 0
template/js/ui2.js

@@ -0,0 +1,44 @@
+
+
+function removeElementsByClass(className){
+   alert(className)
+    var elements = document.getElementsByClassName(className);
+    while(elements.length > 0){
+        elements[0].parentNode.removeChild(elements[0]);
+    }
+}
+
+
+function makeFrame(col, row, colamount = 1, rowamount = 1) {
+  
+  var style = document.createElement('style');
+  style.innerHTML = "   .item" + col + "-" + row + " {          border-style: solid;          border-color: blue;          grid-column: " + col + " / span " + colamount + ";         grid-row: "+ row + " / span " + rowamount + ";        }          ";
+  document.head.appendChild(style);
+  
+  var k = 1;
+  for(var i = 1; i < colamount || k < rowamount; ++i){
+    removeElementsByClass("item" + (col + i) + "-" + (row + k -1))
+    
+    if(i == colamount && k < rowamount){
+      i = 0;
+      ++k;
+    }
+  }
+  
+  
+};
+
+
+document.addEventListener('keyup', (event) => {
+    if (event.key == 'ArrowUp') {
+        alert("ArrowUp");
+        makeFrame(1,1,2,1)
+    }
+    if (event.key == 'ArrowDown') {
+        alert("ArrowDown");
+        
+        makeFrame(2,1,1,2)
+    }
+});
+  
+  

+ 81 - 0
template/template.html

@@ -0,0 +1,81 @@
+<!--
+  This file is part of centurio.work/out/frame.
+
+  centurio.work/out/frame is free software: you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by the Free
+  Software Foundation, either version 3 of the License, or (at your option) any
+  later version.
+
+  centurio.work/out/frame is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+  more details.
+
+  You should have received a copy of the GNU General Public License along with
+  centurio.work/out/frame (file LICENSE in the main directory). If not, see
+  <http://www.gnu.org/licenses/>.
+-->
+
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml" lang="de" xml:lang="de">
+  <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+    <title>work frame</title>
+
+    <!-- libs, do not modify. When local than load local libs. -->
+    <script type="text/javascript" src="/js_libs/jquery.min.js"></script>
+    <script type="text/javascript" src="/js_libs/jquery.browser.js"></script>
+    <script type="text/javascript" src="/js_libs/jquery.svg.min.js"></script>
+    <script type="text/javascript" src="/js_libs/jquery.svgdom.min.js"></script>
+    <script type="text/javascript" src="/js_libs/vkbeautify.js"></script>
+    <script type="text/javascript" src="/js_libs/util.js"></script>
+    <script type="text/javascript" src="/js_libs/printf.js"></script>
+    <script type="text/javascript" src="/js_libs/strftime.min.js"></script>
+    <script type="text/javascript" src="/js_libs/parsequery.js"></script>
+    <script type="text/javascript" src="/js_libs/underscore.min.js"></script>
+    <script type="text/javascript" src="/js_libs/jquery.caret.min.js"></script>
+    <script type="text/javascript" src="/js_libs/jquery.cookie.js"></script>
+
+    <!-- custom stuff, play arround  -->
+    <script type="text/javascript" src="../js/ui.js"></script>
+    <script type="text/javascript" src="../js/ui2.js"></script>
+    <script type="text/javascript" src="../js/language.js"></script>
+    <link rel="stylesheet" href="../css/ui.css" type="text/css"/>
+    <link class='custom' rel="stylesheet" href="" type="text/css"/>
+    <script>
+      if (location.href.match(/\/$/) == null) {
+        location.href = location.href + '/';
+      }
+    </script>
+  </head>
+  <body is="x-ui">
+    <div id="container">
+    </div>
+
+<!--
+    <div id="languages" class="hidden"></div>
+
+    <div id="nav" class="hidden">
+      <template>
+        <span class="item"/>
+      </template>
+    </div>
+
+    <div id="content">
+      <div id="nope">
+        <div onclick="document.location.reload()">🚫</div>
+        <div id="reason"></div>
+      </div>
+      <template>
+        <div/>
+      </template>
+    </div>
+
+    <div id="control">
+      <template>
+        <div><button name="" class="center"/></div>
+      </template>
+    </div>
+-->
+  </body>
+</html>