1
0
mirror of /repos/Prototyper.git synced 2025-12-30 06:31:32 +01:00
Prototyper/public/index.html
2013-04-28 13:07:31 +02:00

666 lines
25 KiB
HTML

<!DOCTYPE html><!-- @@import__app_config_json_ -->{
"navigation": {
"collection": "app",
"name": "navigation"
},
"user": {
"collection": "users"
},
"main": {
"collection": "app",
"name": "main"
},
"viewModel": {
"main":
{
"editorId": "editor",
"mapping" : {
"ignore": ["version"]
}
},
"navigation":
{
"mapping": {},
"elementId": "navigation"
}
}
}<!-- @@_end_import__app_config_json --><!-- @@import__app_main_json_ -->{
"_availableModes" : [
{ "name": "none", "ace":"none" },
{ "name": "HTML", "ace":"html" },
{ "name": "Style", "ace":"css" },
{ "name": "Less", "ace":"css" },
{ "name": "Behaviour", "ace":"javascript" },
{ "name": "Text", "ace":"markdown" },
{ "name": "JSON", "ace":"json" }
]
}<!-- @@_end_import__app_main_json -->
<!-- @@import_file__../README.md__into__app_main_readme -->
<!-- @@import_file__templates/navigation.json__into__app_navigation_json -->
<!-- @@import_leftovers__app_main_index --><html>
<head>
<!-- @@import_file__templates/head.html__into__app_templates_head -->
<!-- @@template__app_templates_head__context__app_main -->
<!-- @@remove_ -->
<title>app/main/index</title>
<!--<script src="/lib/modernizr/modernizr.min.js" type="text/javascript" charset="utf-8"></script>-->
<link href="/lib/bootstrap/bootstrap-combined.min.css" rel="stylesheet" type="text/css">
<!--<link href="/lib/font-awesome/font-awesome.css" rel="stylesheet" type="text/css">-->
<!-- @@import_file__style.less__into__app_main_style -->
<!-- @@style__app_main_style -->
<!-- @@_end_remove -->
<!-- @@remove_ -->
<link rel="stylesheet/less" type="text/css" href="./style.less" />
<script src="/lib/lessjs/less-1.3.3.min.js" type="text/javascript" charset="utf-8"></script>
<!-- @@_end_remove -->
</head>
<body>
<!-- @@template__app_main_body__context__app_main -->
<!-- @@import__app_main_body_ -->
<div id="header">
<div id="htext">
Editing <b data-bind="text: $root.name()+'/'+$root._chosenAttributeId()"> </b>
<strong data-bind="text: _attributes().length"></strong>
<div data-bind="visible: _getMode()">
mode: <strong data-bind="text: _getMode()"></strong>
</div>
<!--<select data-bind="options: _availableModes(), optionsText: 'name', value: _selectedMode"></select>-->
</div>
<!-- ko stopBinding: true --><!-- isolate navigation viewmodel, prevent main from interfering -->
<div id="navigation" class="navbar navbar-inverse">
<div class="navbar-inner">
<div class="container">
<div class="dropdown" data-bind="template: {name: 'links_nav', foreach: links }"></div>
<script type="text/html" id="links_nav_sub">
<li data-bind="css: { 'dropdown-submenu': $data.hasOwnProperty('links')}">
<!-- ko ifnot: $data.hasOwnProperty('links') -->
<a href="#" data-bind="attr: { href: $data.url(), title: $data.title() }, click: $root._openLink">
<span data-bind="attr: { class: $data.icon_class && $data.icon_class() || 'icon-eye-open' }"></span>
<span data-bind="text: $data.title()"></span>
</a>
<!-- /ko --><!-- ko if: $data.hasOwnProperty('links') -->
<a href="#" data-bind="text: $data.title()"></a>
<ul class="dropdown-menu" data-bind="template: {name: 'links_nav_sub', foreach: links }"></ul>
<!-- /ko -->
</li>
</script>
<script type="text/html" id="links_nav">
<div class="btn-group">
<a class="btn btn-inverse dropdown-toggle" data-toggle="dropdown" href="#">
<span data-bind="text: $data.title()"></span>
<span class="caret"></span>
</a>
<!-- ko if: $data.hasOwnProperty('links') -->
<ul class="dropdown-menu" data-bind="template: {name: 'links_nav_sub', foreach: links, }"></ul>
<!-- /ko -->
</div>
</script>
</div>
</div>
</div>
<!-- /ko -->
</div>
<div id="content">
<div id="tree" class="">
<ul>
<li>
<span data-bind="text: name"></span>
<!--<ul data-bind="template: { name: 'nodeTmpl', foreach: nodes }"></ul>-->
</li>
</ul>
</div>
<div id="main" class="">
<div id="tabs">
<ul class="nav nav-pills">
<li>
<a class="btn" title="preview" data-bind="css: { disabled: _chosenAttributeId() == null }, enable: _chosenAttributeId, click: _previewAttribute"><i class="icon-eye-open"></i></a>
</li>
<li class="dropdown" >
<a class="btn dropdown-toggle" data-bind="css: { disabled: _chosenAttributeId() == null }"
data-toggle="dropdown"
href="#"><span data-bind="text: _selectedMode().name()">
Type
</span>
<b class="caret"></b>
</a>
<ul class="dropdown-menu">
<!-- ko foreach: _availableModes() -->
<li><a data-bind="click: $root._selectedMode, text: $data.name"></a></li>
<!-- /ko -->
</ul>
</li>
<li class="divider-vertical"></li>
<!-- ko foreach: _attributes() -->
<li data-bind="css: { active: $data == $root._chosenAttributeId() }">
<a data-bind="text: $data,
click: $root._goToAttribute,
css: { active: $data == $root._chosenAttributeId() }"></a>
</li>
<!-- /ko -->
<li>
<div class="nav navbar-form pull-right">
<label>
<input type="text" class="span2"
data-bind="value: _newAttribute"/>
</label>
</div>
</li>
</ul>
</div>
<div id="editor"></div>
</div>
</div>
<!-- @@import_file__templates/scripts.html__into__app_templates_scripts -->
<!-- @@template__app_templates_scripts__context__app_main -->
<!-- @@remove_ -->
<script src="/lib/jquery/jquery.min.js" type="text/javascript" charset="utf-8"></script>
<script src="/lib/knockout/knockout-2.2.1.debug.js" type="text/javascript" charset="utf-8"></script>
<script src="/lib/underscore/underscore-min.js" type="text/javascript" charset="utf-8"></script>
<script src="/lib/knockout/knockout.mapping-latest.debug.js" type="text/javascript" charset="utf-8"></script>
<script src="/lib/bootstrap/bootstrap.min.js" type="text/javascript" charset="utf-8"></script>
<script src="/lib/knockout/knockout-bootstrap.min.js" type="text/javascript" charset="utf-8"></script>
<script src="/lib/async/async.js" type="text/javascript" charset="utf-8"></script>
<script src="/lib/markdown/markdown.js" type="text/javascript" charset="utf-8"></script>
<script src="/lib/ace/ace.js" type="text/javascript" charset="utf-8"></script>
<script src="/lib/ace/mode-html.js" type="text/javascript" charset="utf-8"></script>
<script src="/lib/ace/mode-javascript.js" type="text/javascript" charset="utf-8"></script>
<script src="/lib/ace/mode-css.js" type="text/javascript" charset="utf-8"></script>
<script src="/lib/ace/mode-markdown.js" type="text/javascript" charset="utf-8"></script>
<script src="/lib/ace/mode-json.js" type="text/javascript" charset="utf-8"></script>
<!--<script src="/lib/ace/mode-less.js" type="text/javascript" charset="utf-8"></script>-->
<script src="/lib/sockjs/sockjs-0.3.min.js" type="text/javascript" charset="utf-8"></script>
<script src="/lib/share/share.uncompressed.js" type="text/javascript" charset="utf-8"></script>
<script src="/lib/share/json.uncompressed.js" type="text/javascript" charset="utf-8"></script>
<!--<script src="/lib/share/json.js" type="text/javascript" charset="utf-8"></script>-->
<script src="/lib/share/share-ace.js" type="text/javascript" charset="utf-8"></script>
<!-- @@_end_remove -->
<!-- @@template__app_main_behaviour__context__app_main -->
<!-- @@import__app_main_behaviour_ -->
<script>
var debug = false;
function main_functions(app, viewModel, vm_config) {
var editor = null;
var cursorPositions = {};
if (!app.state.editor || (app.state.editor.editorId |= vm_config.editorId)) {
var aceInstance = ace.edit(vm_config.editorId || "editor");
editor = {
editorId: vm_config.editorId,
ace: aceInstance,
session: aceInstance.getSession(),
doc: null
};
aceInstance.setReadOnly(true);
} else {
editor = app.state.editor;
}
return {
setMode: function (attribute, mode) {
debug && console.log('setmode', attribute, mode);
var ace_mode = mode;
if (!mode || mode == 'none') {
ace_mode = 'markdown';
}
if (app.doc.main.snapshot
&& app.doc.main.snapshot[attribute]) {
var aceMode = require("ace/mode/" + ace_mode).Mode;
editor.session.setMode(new aceMode());
var currentMode = app.doc.main.snapshot[attribute].mode;
if (!currentMode) {
currentMode = 'none';
}
debug && console.log('modes', attribute, currentMode, mode);
if (currentMode != mode) {
debug && console.log('mode differs', attribute, currentMode, mode);
var op = [];
if (app.doc.main.snapshot[attribute].mode) {
debug && console.log('// remove', attribute, mode);
op.push({
p: [attribute, 'mode'],
od: app.doc.main.snapshot[attribute].mode
});
}
if (mode && mode != 'none') {
debug && console.log('//insert', attribute, mode);
op.push({
p: [attribute, 'mode'],
oi: mode
});
}
app.doc.main.submitOp(op, function (err, result) {
debug && console.log('err', err, 'result', result);
});
}
}
},
setDoc: function (attribute) {
//noinspection JSUnresolvedFunction
editor.ace.setReadOnly(true);
var mode = (app.doc.main.snapshot
&& app.doc.main.snapshot[attribute]
&& app.doc.main.snapshot[attribute].mode
) || 'none';
debug && console.log('setDoc attribute, mode', attribute, mode);
this.setMode(attribute, mode);
//document.title = attribute;
var docName = 'text:app:' + viewModel.name() + ':' + attribute;
sharejs.open(docName, 'text', function (error, newDoc) {
if (editor.doc != null) {
debug && console.log('current cursor position', editor.ace.getCursorPosition());
cursorPositions[editor.doc.name] = editor.ace.getCursorPosition();
editor.doc.close();
editor.doc.detach_ace();
}
editor.doc = newDoc;
if (error) {
console.error(error);
return;
}
//noinspection JSUnresolvedFunction
editor.doc.attach_ace(editor.ace);
//noinspection JSUnresolvedFunction
var moveTo = cursorPositions[editor.doc.name];
if (!moveTo) {
moveTo = {
row: 0,
column: 0
}
}
debug && console.log('moveTo',moveTo);
editor.ace.moveCursorToPosition(moveTo);
editor.ace.centerSelection();
editor.ace.setReadOnly(false);
editor.ace.focus(); //To focus the ace editor
});
},
getExtensionForAttribute: function (attribute) {
var mode = this.getModeForAttribute(attribute);
if (mode && mode.ace() == 'markdown') {
return '.md';
} else {
return '.html';
}
},
getModeForChosenAttribute: function () {
var attribute = viewModel._chosenAttributeId();
debug && console.log('getModeForChosenAttribute._chosenAttributeId', attribute);
return this.getModeForAttribute(attribute)
},
noneAceMode: null, /* does not get invalidated */
noneMode: function () {
if (!this.noneAceMode) {
this.noneAceMode = _.find(viewModel._availableModes(),
function (mode) {
return mode.ace() == 'none';
});
}
return this.noneAceMode;
},
getModeForAttribute: function (attribute) {
if (!attribute) {
return this.noneMode();
}
var mode = _.find(viewModel._availableModes(),
function (mode) {
return viewModel[attribute].mode && (mode.ace() == viewModel[attribute].mode());
});
if (mode) {
debug && console.log('getModeForAttribute', mode);
return mode;
} else {
return this.noneMode();
}
},
updateSelectedMode: function () {
var selectedMode = this.getModeForChosenAttribute();
debug && console.log('updateSelectedMode.getModeForChosenAttribute', selectedMode);
if (selectedMode) {
debug && console.log('setSelectedMode:', selectedMode.ace());
viewModel._selectedMode(selectedMode);
}
}
}
}
function addComputed(snapshot) {
snapshot._attributes = _.filter(
Object.keys(snapshot),
function (key) {
return snapshot[key].hasOwnProperty('guid');
}
);
}
function open_in_new_tab(url) {
var win = window.open(url, '_blank');
win.focus();
}
function initViewModelMain(app, doc, vm_config) {
var mapping = vm_config.mapping;
var snapshot = doc.snapshot;
addComputed(snapshot);
var viewModel = ko.mapping.fromJS(snapshot, mapping);
viewModel._chosenAttributeId = ko.observable();
viewModel._newAttribute = ko.observable();
viewModel._selectedMode = ko.observable();
app.fn['main'] = main_functions(app, viewModel, vm_config);
viewModel._selectedMode(app.fn.main.noneMode());
// Behaviours
viewModel._goToAttribute = function (attribute) {
viewModel._chosenAttributeId(attribute);
app.fn.main.updateSelectedMode();
app.fn.main.setDoc(attribute);
};
viewModel._newAttribute.subscribe(function (newValue) {
if (newValue) {
app.doc.main.submitOp({
p: [newValue],
oi: {}
}, function (err, result) {
//viewModel._newAttribute("");
});
viewModel._goToAttribute(newValue);
}
});
viewModel._previewAttribute = function () {
var id = viewModel._chosenAttributeId();
if (id) {
var ext = app.fn.main.getExtensionForAttribute(id);
var url = '/page/app/main.' + id + ext + '#!watch';
open_in_new_tab(url);
}
};
viewModel._getMode = ko.computed(function () {
var id = viewModel._chosenAttributeId();
return id && viewModel[id].mode;
});
viewModel._selectedMode.subscribe(function (newValue) {
if (mainViewModelUpdating) {
console.error('// fires during ko.mapping.fromJS');
}
if (newValue && !mainViewModelUpdating) {
debug && console.log('_selectedMode, newValue', newValue.ace());
app.fn.main.setMode(viewModel._chosenAttributeId(), newValue.ace());
}
});
viewModel._closeDoc = function (attribute) {
console.log('closing ', attribute);
};
debug && console.log('mainViewModel', viewModel);
return viewModel;
}
var mainViewModelUpdating = false;
function updateViewModelMain(app, viewModel, doc, vm_config) {
mainViewModelUpdating = true;
var snapshot = doc.snapshot;
addComputed(snapshot);
debug && console.log('updating viewModel', snapshot, viewModel, vm_config);
ko.mapping.fromJS(snapshot, vm_config.mapping, viewModel);
viewModel._newAttribute("");
debug && console.log('updated viewModel', viewModel);
mainViewModelUpdating = false;
}
/* crazy expensive caching tree traverse */
function traverse(current, field, property, depth, pos) {
if (current == field) {
field._depth = depth;
field._pos = pos;
return depth;
} else if (current.hasOwnProperty(property)) {
var links = current.links();
depth++;
var found = 0;
for (var x = 0; x < links.length; x++) {
if (!found) {
found = traverse(links[x], field, property, depth, x);
} else {
links[x]._pos = x;
}
}
return found;
}
return 0;
}
function initViewModel(app, viewModels, key, doc, vm_config) {
debug && console.log('initViewModel', viewModels, key, doc, vm_config);
if (key == 'main') {
viewModels[key] = initViewModelMain(app, doc, vm_config);
}
if (key == 'navigation') {
var mapping = vm_config.mapping;
var snapshot = doc.snapshot;
var viewModel = ko.mapping.fromJS(snapshot, mapping);
viewModel._getDepth = function (field) {
if (field.hasOwnProperty('_depth')) {
return field._depth;
}
return traverse(viewModel, field, 'links', 0, 0);
};
viewModel._getPos = function (field) {
if (field.hasOwnProperty('_pos')) {
return field._pos;
}
return traverse(viewModel, field, 'links', 0, 0);
};
viewModel._openLink = function (link) {
open_in_new_tab(link.url());
};
viewModels[key] = viewModel;
}
}
function updateViewModel(app, viewModels, key, doc, vm_config) {
if (key == 'main') {
updateViewModelMain(app, viewModels[key], doc, vm_config);
}
if (key == 'navigation') {
// updateViewModelMain(app, viewModels[key], doc, mapping);
}
}
function initializeViewModel(app, doc, key, viewModels, vm_config) {
initViewModel(app, viewModels, key, doc, vm_config);
doc.on('change', function onChange() {
debug && console.log(key, ' viewModel changed!! running:', app.state.running);
if (app.state.running) {
debug && console.log(key, ' viewModel changed!! updating', vm_config);
if (viewModels[key]) {
updateViewModel(app, viewModels, key, doc, vm_config);
} else {
console.error('HUH!!');
initViewModel(app, viewModels, key, doc, vm_config);
}
}
});
}
function loadLocation(location, callback) {
debug && console.log('opening location', location.collection, location.name);
return sharejs.open('json:' + location.collection + ':' + location.name, 'json', function (err, doc) {
if (err) {
console.error('error loadLocation', location, err, doc);
return callback(err);
}
doc.on('error', function (err) {
console.error('event error loadLocation', location, err, doc.state, doc);
});
doc.on('closed', function (err, data) {
debug && console.log('doc', doc, 'closing', err,'data', data);
});
debug && console.log('opened location', location.collection, location.name, doc, doc.snapshot, doc.state);
if (doc.snapshot == null) {
debug && console.log('wait for first change', location, doc.version);
return doc.once('change', function onceChange() {
debug && console.log('received first change', location, doc.snapshot);
callback(null, doc);
});
} else {
return callback(null, doc);
}
});
}
function getCurrentUser() {
return 'aiko';
}
function loadConfigKey(key, location, viewModels, vm_config, callback) {
debug && console.log('loadConfigKey', key, location);
if (key == 'user') {
var user = getCurrentUser();
if (user) {
location.name = user;
debug && console.log('loadConfig user', location);
loadLocation(location, callback);
}
else callback(new Error('User not logged in.'), null);
} else {
loadLocation(location, callback);
}
}
function handleConfigKey(app, key, snapshot, viewModels, callback) {
debug && console.log('handleConfigKey app', app);
if (key != 'name' && key != '_id' && key != 'viewModel' && !app.hasOwnProperty(key)) {
var location = snapshot[key];
var vm_config = snapshot.viewModel && snapshot.viewModel[key];
return loadConfigKey(key, location, viewModels, vm_config, function handleLoadedKey(err, key_doc) {
if (err) {
console.error('Error loading key', key, err);
debug && console.log('- handleConfigKey', key);
return callback(err);
} else {
app.doc[key] = key_doc;
if (vm_config && !app.state.vm.bound.hasOwnProperty(key)) {
initializeViewModel(app, key_doc, key, viewModels, vm_config);
}
}
debug && console.log('- handleConfigKey', key);
return callback();
});
} else {
debug && console.log('- handleConfigKey', key);
return callback();
}
}
function initializeConfig(doc, app, viewModels, callback) {
var snapshot = doc.snapshot;
return async.each(_.keys(snapshot), function handleConfigKeys(key, iterator_callback) {
debug && console.log('+ handleConfigKey', key);
return handleConfigKey(app, key, snapshot, viewModels, iterator_callback);
}, callback);
}
function loadConfig(app, location, viewModels, callback) {
loadLocation(location, function handleConfig(err, doc) {
if (err) {
console.error('error loadConfig', location, err);
return callback(err);
}
app['config'] = doc;
initializeConfig(doc, app, viewModels, callback);
return doc.on('change', function handleConfigChange() {
debug && console.log('Config changed!!');
initializeConfig(doc, app, viewModels, callback);
});
});
}
var config_location = {
collection: "app",
name: "config"
};
var app = {
state: {
running: false,
vm: {bound: {}}
},
doc: {},
fn: {},
vm: {}
};
function initKnockout() {
/* http://www.knockmeout.net/2012/05/quick-tip-skip-binding.html */
ko.bindingHandlers.stopBinding = {
init: function () {
return { controlsDescendantBindings: true };
}
};
ko.virtualElements.allowedBindings.stopBinding = true;
}
function bindViewModels(app) {
_.forEach(_.keys(app.vm), function (key) {
if (key in app.state.vm.bound) {
debug && console.log('bindViewModels bindings already applied for', key);
} else {
var viewModel = app.vm[key];
var viewModelConfig = app.config.snapshot.viewModel[key];
var elementId = viewModelConfig.elementId;
var element = document.getElementById(elementId);
debug && console.log('bindViewModels applyBindings', key, viewModel, viewModelConfig, elementId);
ko.applyBindings(viewModel, element);
app.state.vm.bound[key] = elementId;
}
});
debug && console.log('Viewmodels bound', 'ok');
}
function configLoaded(err) {
if (err) {
console.error('Error loading config', err);
}
console.log(app, 'Loaded', err || 'ok');
if (app.state.running == false) {
initKnockout();
}
bindViewModels(app);
app.state.running = true;
}
loadConfig(app, config_location, app.vm, configLoaded);
</script>
<!-- @@_end_import__app_main_behaviour -->
<!-- @@_end_import__app_main_body -->
</body>
</html>