mirror of
/repos/Prototyper.git
synced 2025-12-30 06:31:32 +01:00
1523 lines
54 KiB
HTML
1523 lines
54 KiB
HTML
<!DOCTYPE html><!-- @@import__app_projects_json_ -->{
|
|
"projects" : [
|
|
{
|
|
"title" : "Prototyper",
|
|
"docs" : [
|
|
{
|
|
"collection" : "app",
|
|
"name" : "main"
|
|
},
|
|
{
|
|
"collection" : "app",
|
|
"name" : "config"
|
|
},
|
|
{
|
|
"collection" : "app",
|
|
"name" : "templates"
|
|
},
|
|
{
|
|
"collection" : "app",
|
|
"name" : "navigation"
|
|
},
|
|
{
|
|
"collection" : "app",
|
|
"name" : "projects"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"title" : "Test One",
|
|
"docs" : [
|
|
{
|
|
"collection" : "tests",
|
|
"name" : "test1"
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"title" : "Test Two",
|
|
"docs" : [
|
|
{
|
|
"collection" : "tests",
|
|
"name" : "test2"
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}<!-- @@_end_import__app_projects_json --><!-- @@import__app_config_json_ -->{
|
|
"navigation": {
|
|
"collection": "app",
|
|
"name": "navigation"
|
|
},
|
|
"user": {
|
|
"collection": "users"
|
|
},
|
|
"main": {
|
|
"collection": "app",
|
|
"name": "main"
|
|
},
|
|
"projects": {
|
|
"collection": "app",
|
|
"name": "projects"
|
|
},
|
|
"viewModel": {
|
|
"main": {
|
|
"editorId": "editor",
|
|
"mapping" : {
|
|
"ignore": ["version"]
|
|
}
|
|
},
|
|
"navigation": {
|
|
"mapping": {},
|
|
"elementId": "navigation"
|
|
},
|
|
"user": {
|
|
"mapping": {},
|
|
"elementId": "user"
|
|
},
|
|
"projects": {
|
|
"mapping": {},
|
|
"elementId": "project_tree"
|
|
}
|
|
}
|
|
}<!-- @@_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>
|
|
<link rel="shortcut icon" type="image/x-icon" href="/favicon.ico">
|
|
<!--<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>
|
|
<!-- Modal -->
|
|
<div id="popup" class="modal hide fade" tabindex="-1" role="dialog" aria-hidden="true">
|
|
<div class="modal-body"></div>
|
|
</div>
|
|
<!-- @@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._chosenAttribute && $root._chosenAttribute() && $root._chosenAttribute().attribute)"> </b>
|
|
|
|
<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">
|
|
<img class="brand" src="/img/uillogo-inverse.png"/>
|
|
|
|
<div class="container">
|
|
<div class="nav 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') -->
|
|
<!-- ko ifnot: $data.hasOwnProperty('modal') -->
|
|
<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() || $root.default_icon() }"></span>
|
|
<span data-bind="text: $data.title()"></span>
|
|
</a>
|
|
<!-- /ko --><!-- ko if: $data.hasOwnProperty('modal') -->
|
|
<a href="#" data-toggle="modal" data-bind="attr: { href: $data.url(), title: $data.title(), 'data-target': '#' + $data.modal() }">
|
|
<span data-bind="attr: { class: $data.icon_class && $data.icon_class() || $root.default_icon() }"></span>
|
|
<span data-bind="text: $data.title()"></span>
|
|
</a>
|
|
<!-- /ko --><!-- /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>
|
|
<!-- ko stopBinding: true --><!-- isolate user viewmodel, prevent main and navigation from interfering -->
|
|
<div class="nav navbar-form pull-right" id="user">
|
|
<div class="input-prepend">
|
|
<span class="add-on btn-inverse"><i class="icon-white icon-user"></i></span>
|
|
<input type="text" class="span2" data-bind="value: name"/>
|
|
</div>
|
|
</div>
|
|
<!-- /ko -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- /ko -->
|
|
</div>
|
|
<div id="content">
|
|
<!-- ko stopBinding: true --><!-- isolate project viewModel, prevent main from interfering -->
|
|
<div id="project_tree">
|
|
<div class="controls">
|
|
<select data-bind="options: projects(), optionsText: 'title', value: _selectedProject"></select>
|
|
</div>
|
|
<!--<div class="controls">-->
|
|
<!--<input type="text" autocomplete="off">-->
|
|
<!--</div>-->
|
|
<div class="accordion" id="accordion"
|
|
data-bind="sortable: { data:_selectedProject().docs(), afterMove:$root._moveContent}">
|
|
<div class="accordion-group">
|
|
<a class="nav-pills nav-stacked" data-toggle="collapse"
|
|
data-parent="#accordion"
|
|
data-bind="attr: { href: '#collapse' + $index() }">
|
|
<span data-bind="text: name, click: $root._loadAttributes($index(), $data)"></span>
|
|
</a>
|
|
|
|
<div data-bind="attr: { id: 'collapse' +$index() }"
|
|
class="accordion-body collapse">
|
|
<div>
|
|
<ul class="nav nav-pills nav-stacked" data-bind="sortable: { data:_attributeDocNames(), afterMove:$root._moveAttribute}">
|
|
<li data-bind="css: { active: $root._isActiveAttribute($data) }">
|
|
<a data-bind="text: $data.attribute,
|
|
click: $root._openAttribute"></a>
|
|
</li>
|
|
</ul>
|
|
<label>
|
|
<div class="input-append">
|
|
<input type="text"
|
|
data-bind="value: _newAttribute"/>
|
|
<span class="add-on btn"><i class="icon-plus"></i></span>
|
|
</div>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- /ko -->
|
|
<div id="main">
|
|
<div id="tabs">
|
|
<ul class="nav nav-pills">
|
|
<li>
|
|
<a class="btn" title="preview" data-bind="css: { disabled: _chosenAttribute() == null }, enable: _chosenAttribute, click: _previewAttribute"><i class="icon-eye-open"></i></a>
|
|
</li>
|
|
<li class="dropdown" >
|
|
<a class="btn dropdown-toggle selectedMode" data-bind="css: { disabled: _chosenAttribute() == 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>
|
|
<!-- ko foreach: _activeAttributes() -->
|
|
<li data-bind="css: { active: $root._isChosenAttribute($data) }">
|
|
<a class="nav-pill" href="javascript:;">
|
|
<div class="btn-group">
|
|
<button class="btn-pill" data-bind="text: $data.attribute, click: $root._goToAttribute"></button>
|
|
<button class="close" data-bind="click: $root._closeDoc"><i class="icon-remove-sign"></i></button>
|
|
</div>
|
|
</a>
|
|
</li>
|
|
<!-- /ko -->
|
|
</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/jquery/jquery-ui.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/knockout/knockout-sortable.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/share-ace.js" type="text/javascript" charset="utf-8"></script>
|
|
<!-- @@_end_remove -->
|
|
<!-- @@template__app_main_behaviour__context__app_main -->
|
|
<!-- @@import__app_main_behaviour_ -->
|
|
<script>
|
|
|
|
function main_functions(app, vmName) {
|
|
var vm_config = getViewModelConfig(app, vmName);
|
|
var editor = null;
|
|
|
|
if (app.state.editor && app.state.editor[vm_config.editorId]) {
|
|
editor = app.state.editor[vm_config.editorId];
|
|
} else {
|
|
var aceInstance = app.ace.edit(vm_config.editorId || "editor");
|
|
editor = {
|
|
editorId: vm_config.editorId,
|
|
ace: aceInstance,
|
|
session: aceInstance.getSession(),
|
|
doc: null
|
|
};
|
|
if (!app.state.editor) {
|
|
app.state.editor = {};
|
|
}
|
|
app.state.editor[vm_config.editorId] = editor;
|
|
|
|
aceInstance.setReadOnly(true);
|
|
}
|
|
|
|
return {
|
|
setMode: function (attribute, mode) {
|
|
app.debug && console.log('setmode', attribute.attribute, mode);
|
|
var ace_mode = mode;
|
|
if (!mode || mode == 'none') {
|
|
ace_mode = 'markdown';
|
|
}
|
|
var attributeDoc = getAttributeFromDocSnapshot(app, attribute);
|
|
if (attributeDoc) {
|
|
var aceMode = require("ace/mode/" + ace_mode).Mode;
|
|
editor.session.setMode(new aceMode());
|
|
var currentMode = attributeDoc.mode;
|
|
if (!currentMode) {
|
|
currentMode = 'none';
|
|
}
|
|
app.debug && console.log('modes', attribute, currentMode, mode);
|
|
|
|
if (currentMode != mode) {
|
|
app.debug && console.log('mode differs', attribute, currentMode, mode);
|
|
function doInsert(mode) {
|
|
return mode && mode != 'none';
|
|
}
|
|
|
|
replaceAttributeDocKey(app, attribute, 'mode', mode, doInsert, function (err, result) {
|
|
app.debug && console.log('err', err, 'result', result);
|
|
});
|
|
}
|
|
}
|
|
},
|
|
|
|
setReadOnly: function (value) {
|
|
editor.ace.setReadOnly(value);
|
|
},
|
|
|
|
setDoc: function (attribute, callback) {
|
|
//noinspection JSUnresolvedFunction
|
|
editor.ace.setReadOnly(true);
|
|
var attributeDoc = getAttributeFromDocSnapshot(app, attribute);
|
|
var mode = (attributeDoc && attributeDoc.mode) || 'none';
|
|
app.debug && console.log('setDoc attribute, mode', attribute.title, mode);
|
|
|
|
this.setMode(attribute, mode);
|
|
|
|
if (editor.doc != null) {
|
|
app.debug && console.log('current cursor position', editor.ace.getCursorPosition());
|
|
callFunction(app, 'user').storeCurrentPosition(editor.doc.name, editor.ace.getCursorPosition());
|
|
editor.doc.close();
|
|
editor.doc.detach_ace();
|
|
}
|
|
loadAttributeTextDoc(app, attribute, function (error, newDoc) {
|
|
if (error) {
|
|
editor.ace.setReadOnly(true);
|
|
console.error(error);
|
|
return callback && callback(error);
|
|
}
|
|
newDoc.on('error', function (err) {
|
|
editor.ace.setReadOnly(true);
|
|
return callback && callback(err);
|
|
});
|
|
|
|
editor.doc = newDoc;
|
|
|
|
//noinspection JSUnresolvedFunction
|
|
editor.doc.attach_ace(editor.ace);
|
|
//noinspection JSUnresolvedFunction
|
|
|
|
var moveTo = callFunction(app, 'user').getCurrentPosition(editor.doc.name);
|
|
if (!(moveTo && moveTo.row && moveTo.column)) {
|
|
moveTo = {
|
|
row: 0,
|
|
column: 0
|
|
}
|
|
}
|
|
app.debug && console.log('moveTo', moveTo);
|
|
editor.ace.moveCursorToPosition(moveTo);
|
|
editor.ace.centerSelection();
|
|
|
|
editor.ace.setReadOnly(false);
|
|
|
|
editor.ace.focus(); //To focus the ace editor
|
|
return callback && callback(null);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
function addComputed(snapshot) {
|
|
snapshot._attributes = _.filter(
|
|
Object.keys(snapshot),
|
|
function (key) {
|
|
return snapshot[key].hasOwnProperty('guid');
|
|
}
|
|
);
|
|
}
|
|
|
|
function getAttributeDocNames(snapshot, parentLocation) {
|
|
if (!snapshot._attributes) {
|
|
addComputed(snapshot);
|
|
}
|
|
var loc = normalizeLocation(parentLocation);
|
|
var pairs = [];
|
|
_.forEach(snapshot._attributes, function (key) {
|
|
pairs.push({
|
|
index: snapshot[key].index,
|
|
collection: loc.collection,
|
|
name: loc.name,
|
|
attribute: key
|
|
})
|
|
});
|
|
var sortedPairs = _.sortBy(pairs, function (pair) {
|
|
return pair.index;
|
|
});
|
|
var i;
|
|
for(i=0; i< sortedPairs.length; i+=1){
|
|
sortedPairs[i].index=i;
|
|
}
|
|
return sortedPairs;
|
|
}
|
|
|
|
// uniquely identifying an attribute
|
|
function getAttributeId(attribute) {
|
|
return getDocId(attribute) + ':' + attribute.attribute;
|
|
}
|
|
|
|
function getAttributeLocation(attribute) {
|
|
return {
|
|
collection: attribute.collection,
|
|
name: attribute.name,
|
|
attribute: attribute.attribute
|
|
}
|
|
}
|
|
|
|
// identifying attributes parent doc
|
|
function getDocId(attribute) {
|
|
return attribute
|
|
&& attribute.collection && attribute.name
|
|
&& (attribute.collection + ':' + attribute.name);
|
|
}
|
|
|
|
function getAttributeDoc(app, attribute) {
|
|
var docId = getDocId(attribute);
|
|
return docId && app.doc[docId];
|
|
}
|
|
|
|
function getAttributeFromDocSnapshot(app, attribute) {
|
|
var doc = getAttributeDoc(app, attribute);
|
|
return doc && doc.snapshot && doc.snapshot[attribute.attribute];
|
|
}
|
|
|
|
function getAttributeUrl(attribute) {
|
|
return attribute.collection + '/' + attribute.name + '.' + attribute.attribute;
|
|
}
|
|
|
|
function replaceAttributeDocKey(app, attribute, key, value, doInsert, callback) {
|
|
var op = [];
|
|
var attributeDoc = getAttributeDoc(app, attribute);
|
|
if (attributeDoc && attributeDoc[key]) {
|
|
app.debug && console.log('// remove', key);
|
|
op.push({
|
|
p: [attribute.attribute, key],
|
|
od: attributeDoc[key]
|
|
});
|
|
}
|
|
if (typeof doInsert == 'function' && doInsert(value) || doInsert) {
|
|
app.debug && console.log('//insert', key);
|
|
op.push({
|
|
p: [attribute.attribute, key],
|
|
oi: value
|
|
});
|
|
}
|
|
attributeDoc.submitOp(op, function (err, result) {
|
|
app.debug && console.log('err', err, 'result', result);
|
|
return callback && callback(err, result);
|
|
});
|
|
}
|
|
|
|
function loadAttributeTextDoc(app, attribute, callback) {
|
|
loadLocation(app, getAttributeLocation(attribute), 'text', function (err, doc) {
|
|
if (err) {
|
|
console.error('error loading attribute doc', err)
|
|
return callback && callback(err);
|
|
}
|
|
return callback && callback(err, doc);
|
|
});
|
|
}
|
|
|
|
function equalAttributes(first, second) {
|
|
return first && second &&
|
|
(typeof first == 'object') && (typeof second == 'object') &&
|
|
first.collection == second.collection &&
|
|
first.name == second.name &&
|
|
first.attribute == second.attribute;
|
|
}
|
|
|
|
function open_in_new_tab(url) {
|
|
var win = window.open(url, '_blank');
|
|
win.focus();
|
|
}
|
|
|
|
function initViewModel_main(app, vmName) {
|
|
app.debug && console.log('initViewModel_main');
|
|
var vm_config = getViewModelConfig(app, vmName);
|
|
var mapping = vm_config.mapping;
|
|
var snapshot = getDocSnapshot(app, vmName);
|
|
|
|
var viewModel = app.ko.mapping.fromJS(snapshot, mapping);
|
|
|
|
viewModel._selectedMode = app.ko.observable();
|
|
|
|
var noneAceMode = null;
|
|
/* does not get invalidated */
|
|
viewModel._noneMode = function () {
|
|
if (!this.noneAceMode) {
|
|
this.noneAceMode = _.find(viewModel._availableModes(),
|
|
function (mode) {
|
|
return mode.ace() == 'none';
|
|
});
|
|
}
|
|
return this.noneAceMode;
|
|
};
|
|
|
|
viewModel._selectedMode(viewModel._noneMode());
|
|
|
|
viewModel._chosenAttribute = app.ko.observable();
|
|
|
|
viewModel._isChosenAttribute = function (attribute) {
|
|
var active = viewModel._chosenAttribute();
|
|
if (!active || !attribute) {
|
|
return false;
|
|
}
|
|
return equalAttributes(active, attribute);
|
|
};
|
|
|
|
viewModel._getModeForAttribute = function (attribute) {
|
|
if (!attribute) {
|
|
return viewModel._noneMode();
|
|
}
|
|
var attributeDoc = getAttributeFromDocSnapshot(app, attribute);
|
|
var docMode = (attributeDoc && attributeDoc.mode);
|
|
if (!docMode) {
|
|
return viewModel._noneMode();
|
|
}
|
|
var mode = _.find(viewModel._availableModes(),
|
|
function (mode) {
|
|
return docMode == mode.ace();
|
|
});
|
|
if (mode) {
|
|
app.debug && console.log('getModeForAttribute', mode);
|
|
return mode;
|
|
} else {
|
|
return viewModel._noneMode();
|
|
}
|
|
};
|
|
|
|
viewModel._getExtensionForAttribute = function (attribute) {
|
|
var mode = viewModel._getModeForAttribute(attribute);
|
|
if (mode && mode.ace() == 'markdown') {
|
|
return '.md';
|
|
} else {
|
|
return '.html';
|
|
}
|
|
};
|
|
|
|
viewModel._getModeForChosenAttribute = function () {
|
|
var attribute = viewModel._chosenAttribute();
|
|
app.debug && console.log('getModeForChosenAttribute._chosenAttribute', attribute);
|
|
return viewModel._getModeForAttribute(attribute)
|
|
};
|
|
|
|
|
|
viewModel._updateSelectedMode = function () {
|
|
var selectedMode = viewModel._getModeForChosenAttribute();
|
|
app.debug && console.log('updateSelectedMode.getModeForChosenAttribute', selectedMode);
|
|
if (selectedMode) {
|
|
app.debug && console.log('setSelectedMode:', selectedMode.ace());
|
|
viewModel._selectedMode(selectedMode);
|
|
}
|
|
};
|
|
var activeAttribute = callFunction(app, 'user').getActiveAttributes();
|
|
|
|
viewModel._activeAttributes = app.ko.observableArray(activeAttribute);
|
|
|
|
viewModel._goToAttribute = function (attribute) {
|
|
app.debug && console.log('_goToAttribute',attribute);
|
|
viewModel._chosenAttribute(attribute);
|
|
viewModel._updateSelectedMode();
|
|
callFunction(app, 'main').setDoc(attribute, function (err) {
|
|
if (!_.find(
|
|
viewModel._activeAttributes(),
|
|
function (active) {
|
|
return equalAttributes(active, attribute);
|
|
}
|
|
)) { // TODO: do not update viewmodel, instead update sharedoc.
|
|
callFunction(app, 'user').addActiveAttribute(attribute);
|
|
//viewModel._activeAttributes.push(attribute);
|
|
}
|
|
});
|
|
};
|
|
|
|
|
|
viewModel._previewAttribute = function () {
|
|
var attribute = viewModel._chosenAttribute();
|
|
if (attribute) {
|
|
var ext = viewModel._getExtensionForAttribute(attribute);
|
|
var url = '/page/' + getAttributeUrl(attribute) + ext + '#!watch';
|
|
open_in_new_tab(url);
|
|
}
|
|
|
|
};
|
|
|
|
viewModel._getMode = app.ko.computed(function () {
|
|
var attribute = viewModel._chosenAttribute();
|
|
if (!attribute) return attribute;
|
|
|
|
var attributeDoc = getAttributeFromDocSnapshot(app, attribute);
|
|
|
|
return attribute && attributeDoc && attributeDoc.mode;
|
|
});
|
|
|
|
viewModel._selectedMode.subscribe(function (newValue) {
|
|
var attribute = viewModel._chosenAttribute();
|
|
if (attribute) {
|
|
var docId = getDocId(attribute);
|
|
if (app.state.vm.updating[docId]) {
|
|
console.error('// fires during ko.mapping.fromJS');
|
|
}
|
|
if (newValue && !app.state.vm.updating[docId]) {
|
|
app.debug && console.log('_selectedMode, newValue', newValue.ace());
|
|
callFunction(app, 'main').setMode(attribute, newValue.ace());
|
|
}
|
|
}
|
|
});
|
|
|
|
viewModel._closeDoc = function (attribute) {
|
|
console.log('closing ', attribute);
|
|
var mainViewModel = getViewModel(app, 'main');
|
|
if(mainViewModel._isChosenAttribute(attribute)) {
|
|
callFunction(app, 'main').setReadOnly(true);
|
|
}
|
|
callFunction(app, 'user').removeActiveAttribute(attribute);
|
|
};
|
|
|
|
app.debug && console.log('mainViewModel', viewModel);
|
|
|
|
return viewModel;
|
|
}
|
|
|
|
function updateViewModel_main(app, vmName) {
|
|
var vm_config = getViewModelConfig(app, vmName);
|
|
var mapping = vm_config.mapping;
|
|
var snapshot = getDocSnapshot(app, vmName);
|
|
var viewModel = getViewModel(app, vmName);
|
|
|
|
addComputed(snapshot);
|
|
app.debug && console.log('updating main viewModel', snapshot, viewModel, vm_config);
|
|
app.ko.mapping.fromJS(snapshot, mapping, viewModel);
|
|
app.debug && console.log('updated main viewModel', viewModel);
|
|
}
|
|
|
|
function post_updateViewModel_main(app, vmName) {
|
|
app.debug && console.log('post_update main viewModel', vmName);
|
|
var viewModel = getViewModel(app, vmName);
|
|
viewModel._updateSelectedMode();
|
|
}
|
|
|
|
/* 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 ensurePath(doc, path, lastIsList, callback) {
|
|
var ops = [];
|
|
var sub_doc = doc.snapshot;
|
|
var current = {};
|
|
for (var x = 0; x < path.length; x++) {
|
|
var check = sub_doc[path[x]];
|
|
if (check) {
|
|
sub_doc = check;
|
|
current = check;
|
|
} else {
|
|
if (((x + 1) == path.length) && lastIsList) {
|
|
ops.push({
|
|
p: path.slice(0, x + 1),
|
|
oi: []
|
|
});
|
|
current = [];
|
|
} else {
|
|
ops.push({
|
|
p: path.slice(0, x + 1),
|
|
oi: {}
|
|
});
|
|
sub_doc = {};
|
|
current = null;
|
|
}
|
|
}
|
|
}
|
|
return callback(null, ops, current);
|
|
}
|
|
|
|
function user_functions(app, vmName) {
|
|
var cursorPositions = {};
|
|
|
|
return {
|
|
storeCurrentPosition: function (name, position) {
|
|
cursorPositions[name] = position;
|
|
|
|
var user_doc = getDoc(app, 'user');
|
|
var path = ['positions', name];
|
|
ensurePath(user_doc, path, false, function (err, ops, current) {
|
|
ops.push({
|
|
p: path,
|
|
od: current,
|
|
oi: position
|
|
});
|
|
app.debug && console.log('storeCurrentPosition ops', ops);
|
|
user_doc.submitOp(ops, function (err) {
|
|
app.debug && console.log('set position', position, err);
|
|
});
|
|
});
|
|
},
|
|
|
|
getCurrentPosition: function (name) {
|
|
var position_doc = getDoc(app, 'user').at(['positions', name]);
|
|
var position = null;
|
|
try {
|
|
position = position_doc.get();
|
|
} catch (e) {
|
|
// ignore 'bad path' error
|
|
}
|
|
if (!position) {
|
|
position = cursorPositions[name];
|
|
}
|
|
return position
|
|
},
|
|
|
|
addActiveAttribute: function (attribute, callback) {
|
|
var path = ['active'];
|
|
var user_doc = getDoc(app, 'user');
|
|
ensurePath(user_doc, path, true, function (err, ops, current) {
|
|
if (err) {
|
|
console.error('addActiveAttribute.ensurePath', err);
|
|
return callback && callback(err);
|
|
}
|
|
if (!_.find(
|
|
current,
|
|
function (active) {
|
|
return equalAttributes(active, attribute);
|
|
}
|
|
)) { // new
|
|
path.push(current.length); // add at end of the list
|
|
ops.push({
|
|
p: path,
|
|
li: normalizeLocation(attribute)
|
|
});
|
|
app.debug && console.log('addActiveAttribute ops', ops);
|
|
return user_doc.submitOp(ops, function (err) {
|
|
if (err) {
|
|
console.error('user_doc.submitOp', err);
|
|
return callback && callback(err);
|
|
}
|
|
return callback && callback(err);
|
|
});
|
|
|
|
} else {
|
|
return callback && callback(null);
|
|
}
|
|
});
|
|
},
|
|
|
|
removeActiveAttribute: function (attribute, callback) {
|
|
var path = ['active'];
|
|
var user_doc = getDoc(app, 'user');
|
|
ensurePath(user_doc, path, true, function (err, ops, current) {
|
|
if (err) {
|
|
console.error('removeActiveAttribute.ensurePath', err);
|
|
return callback && callback(err);
|
|
}
|
|
var currentIndex=null;
|
|
_.any(current, function(value, index, list) {
|
|
if (equalAttributes(value, attribute)) {
|
|
currentIndex=index;
|
|
return true;
|
|
}
|
|
});
|
|
if (currentIndex != null){
|
|
path.push(currentIndex);
|
|
ops.push({
|
|
p: path,
|
|
ld: normalizeLocation(attribute)
|
|
});
|
|
app.debug && console.log('removeActiveAttribute ops', ops);
|
|
return user_doc.submitOp(ops, function (err) {
|
|
if (err) {
|
|
console.error('user_doc.submitOp', err);
|
|
return callback && callback(err);
|
|
}
|
|
return callback && callback(err);
|
|
});
|
|
|
|
} else {
|
|
return callback && callback(null);
|
|
}
|
|
});
|
|
},
|
|
|
|
getActiveAttributes: function () {
|
|
var active_list = getDoc(app, 'user').at(['active']);
|
|
var attributes = null;
|
|
try {
|
|
attributes = active_list.get();
|
|
} catch (e) {
|
|
// ignore 'bad path' error
|
|
}
|
|
if (!attributes) {
|
|
attributes = [];
|
|
}
|
|
return attributes
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
function post_updateViewModel_user(app, vmName) {
|
|
app.debug && console.log('post_update main viewModel', vmName);
|
|
var main_viewModel = getViewModel(app, 'main');
|
|
main_viewModel._activeAttributes(
|
|
callFunction(app, 'user').getActiveAttributes()
|
|
);
|
|
}
|
|
|
|
function initViewModel_user(app, vmName) {
|
|
var vm_config = getViewModelConfig(app, vmName);
|
|
var mapping = vm_config.mapping;
|
|
var snapshot = getDocSnapshot(app, vmName);
|
|
|
|
app.debug && console.log('init', vmName, 'viewModel', snapshot, vm_config);
|
|
var viewModel = app.ko.mapping.fromJS(snapshot, mapping);
|
|
app.debug && console.log('init', vmName, 'viewModel', viewModel);
|
|
if (!viewModel.name) {
|
|
viewModel.name = app.ko.observable('guest');
|
|
}
|
|
viewModel.name.subscribe(function (newValue) {
|
|
console.log('change to new user', newValue);
|
|
if (newValue != 'guest') {
|
|
var location = {
|
|
collection: getConfigDocSnapshot(app)[vmName].collection,
|
|
name: newValue
|
|
};
|
|
loadRawDoc(app, location, function (err, user_doc) {
|
|
setDoc(app, vmName, user_doc);
|
|
updateViewModel(app, vmName);
|
|
post_updateViewModel(app, vmName);
|
|
user_doc.on('change', onDocChange(app, vmName));
|
|
});
|
|
}
|
|
});
|
|
return viewModel;
|
|
}
|
|
|
|
function normalizeLocation(loc) {
|
|
var collection = loc.collection;
|
|
if (typeof loc.collection == 'function') {
|
|
collection = loc.collection();
|
|
}
|
|
var name = loc.name;
|
|
if (typeof loc.name == 'function') {
|
|
name = loc.name();
|
|
}
|
|
var normal = {
|
|
collection: collection,
|
|
name: name
|
|
};
|
|
if (loc.attribute) {
|
|
var attribute = loc.attribute;
|
|
if (typeof loc.attribute == 'function') {
|
|
attribute = loc.attribute();
|
|
}
|
|
normal.attribute = attribute;
|
|
|
|
}
|
|
return normal;
|
|
}
|
|
|
|
function locationFromKey(key) {
|
|
var parts = key.split(':');
|
|
var loc = {
|
|
collection: parts[0],
|
|
name: parts[1]
|
|
};
|
|
var attribute = parts[2];
|
|
if (attribute) {
|
|
loc.attribute = attribute;
|
|
}
|
|
return loc;
|
|
}
|
|
|
|
function keyFromLocation(loc) {
|
|
var _loc = normalizeLocation(loc);
|
|
var key = _loc.collection + ':' + _loc.name;
|
|
if (_loc.attribute) {
|
|
key += ':' + _loc.attribute;
|
|
}
|
|
return key;
|
|
}
|
|
|
|
function setDoc(app, docId, doc) {
|
|
if (!app.doc) {
|
|
app['doc'] = {};
|
|
}
|
|
app.doc[docId] = doc;
|
|
}
|
|
|
|
function getDoc(app, docId) {
|
|
return app && app.doc && app.doc[docId];
|
|
}
|
|
|
|
function getDocSnapshot(app, docId) {
|
|
return getDoc(app, docId) && app.doc[docId].snapshot;
|
|
}
|
|
|
|
function initViewModel_projects(app, vmName) {
|
|
var vm_config = getViewModelConfig(app, vmName);
|
|
var mapping = vm_config.mapping;
|
|
var snapshot = getDocSnapshot(app, vmName);
|
|
app.debug && console.log('init', vmName, 'viewModel', snapshot, vm_config);
|
|
var viewModel = app.ko.mapping.fromJS(snapshot, mapping);
|
|
app.debug && console.log('init', vmName, 'viewModel', viewModel);
|
|
// TODO: grab current project from user.
|
|
viewModel._selectedProject = app.ko.observable();
|
|
viewModel._selectedProject.subscribe(function (project) {
|
|
_.forEach(project.docs(), function (loc, index) {
|
|
var docId = keyFromLocation(loc);
|
|
if (getDoc(app, docId)) {
|
|
var docSnapshot = getDocSnapshot(app, docId);
|
|
var attributes = getAttributeDocNames(docSnapshot, loc);
|
|
project.docs()[index]._attributeDocNames = app.ko.observableArray(attributes);
|
|
} else {
|
|
project.docs()[index]._attributeDocNames = app.ko.observableArray();
|
|
}
|
|
project.docs()[index]._newAttribute = app.ko.observable();
|
|
project.docs()[index]._newAttribute.subscribe(function (newValue, aaaa) {
|
|
if (newValue) {
|
|
var docId = keyFromLocation(loc);
|
|
var doc = getDoc(app, docId);
|
|
|
|
doc.submitOp({
|
|
p: [newValue],
|
|
oi: {}
|
|
}, function (err, result) {
|
|
project.docs()[index]._newAttribute("");
|
|
});
|
|
var _loc = normalizeLocation(loc);
|
|
var attribute = {
|
|
collection: _loc.collection,
|
|
name: _loc.name,
|
|
attribute: newValue
|
|
};
|
|
// TODO: a callback would be nice so we can clear the
|
|
// _newAttribute box after the document has been written to.
|
|
// currently it just wierd in 'guest' mode as you can not
|
|
// have any tabs open.
|
|
viewModel._openAttribute(attribute);
|
|
}
|
|
});
|
|
})
|
|
});
|
|
|
|
viewModel._moveAttribute = function(args) {
|
|
app.debug && console.log('moveAttribute', args);
|
|
var index = _.indexOf(viewModel.projects(),viewModel._selectedProject());
|
|
if (index >= 0) {
|
|
callFunction(app,'projects').moveAttribute(index, args);
|
|
}
|
|
};
|
|
|
|
viewModel._moveContent = function(arg) {
|
|
var index = _.indexOf(viewModel.projects(),viewModel._selectedProject());
|
|
if (index >= 0) {
|
|
callFunction(app,'projects').moveContent(index, arg);
|
|
}
|
|
};
|
|
|
|
viewModel._loadAttributes = function (index, loc) {
|
|
return function (data, event) {
|
|
var docId = keyFromLocation(loc);
|
|
if (!getDoc(app, docId)) {
|
|
loadLocation(app, loc, 'json', function (err, key_doc) {
|
|
setDoc(app, docId, key_doc);
|
|
function onChange() {
|
|
var docSnapshot = getDocSnapshot(app, docId);
|
|
addComputed(docSnapshot);
|
|
var attributes = getAttributeDocNames(docSnapshot, loc);
|
|
viewModel._selectedProject().docs()[index]._attributeDocNames(
|
|
attributes
|
|
);
|
|
}
|
|
key_doc.on('change', onChange);
|
|
onChange();
|
|
});
|
|
}
|
|
}
|
|
};
|
|
|
|
viewModel._openAttribute = function (data) {
|
|
app.debug && console.log('_openAttribute data', data);
|
|
var vmMain = getViewModel(app, 'main');
|
|
vmMain && vmMain._goToAttribute(data);
|
|
};
|
|
|
|
viewModel._isActiveAttribute = function (attribute) {
|
|
var vmMain = getViewModel(app, 'main');
|
|
if (!vmMain) return false;
|
|
return _.find(
|
|
vmMain._activeAttributes(),
|
|
function (active) {
|
|
return equalAttributes(active, attribute);
|
|
}
|
|
);
|
|
};
|
|
|
|
return viewModel;
|
|
}
|
|
|
|
function ensureIndexesOnAttributes(snapshot) {
|
|
var ops = [];
|
|
if (!snapshot._attributes) {
|
|
addComputed(snapshot);
|
|
}
|
|
var needs_indexing = _.find(snapshot._attributes, function (attributeName) {
|
|
return (!snapshot[attributeName].hasOwnProperty('index'));
|
|
});
|
|
app.debug && console.log('ensureIndexesOnAttributes needs_indexing', needs_indexing);
|
|
if (needs_indexing) {
|
|
var maxAttribute = _.max(snapshot._attributes, function (attributeName) {
|
|
return snapshot[attributeName].index;
|
|
});
|
|
var max = maxAttribute && snapshot[maxAttribute].index || 0;
|
|
_.forEach(snapshot._attributes, function (attributeName,index) {
|
|
if (!snapshot[attributeName].hasOwnProperty('index')) {
|
|
max += 1;
|
|
app.debug && console.log('ensureIndexesOnAttributes correct_index', attributeName, index);
|
|
var path = [attributeName, 'index'];
|
|
ops.push({
|
|
p: path,
|
|
oi: max
|
|
});
|
|
snapshot[attributeName].index=max;
|
|
}
|
|
});
|
|
}
|
|
// defrag
|
|
var used = [];
|
|
var dups = [];
|
|
var max = snapshot._attributes.length;
|
|
_.forEach(snapshot._attributes, function (attributeName) {
|
|
var od = snapshot[attributeName].index;
|
|
var path = [attributeName, 'index'];
|
|
if (_.find(used, function (dup) {
|
|
return od == dup.od;
|
|
})){
|
|
app.debug && console.log('// duplicate move to end.', attributeName, od, dups);
|
|
ops.push({
|
|
p: path,
|
|
od: od,
|
|
oi: max
|
|
});
|
|
od = max;
|
|
max += 1;
|
|
}
|
|
app.debug && dups.push(od);
|
|
used.push({p: path, od: od})
|
|
});
|
|
var sortedUsed = _.sortBy(used, function (item) {
|
|
return item.od;
|
|
});
|
|
if (app.debug) {
|
|
var justindex = _.pluck(sortedUsed, 'od');
|
|
console.log('// sorted indexes',justindex);
|
|
}
|
|
_.forEach(sortedUsed, function (item,index) {
|
|
if (item.od != index) {
|
|
app.debug && console.log('// hole found', item.od, index);
|
|
ops.push({
|
|
p: item.p,
|
|
od: item.od,
|
|
oi: index
|
|
});
|
|
}
|
|
});
|
|
return ops;
|
|
}
|
|
|
|
function projects_functions(app, vmName) {
|
|
return {
|
|
moveContent: function (index, args) {
|
|
var doc = getDoc(app, 'projects');
|
|
var path = ['projects',index,'docs',args.sourceIndex];
|
|
var ops = [];
|
|
ops.push({
|
|
p: path,
|
|
lm: args.targetIndex
|
|
});
|
|
app.debug && console.log('moveContent ops', ops);
|
|
doc.submitOp(ops, function (err) {
|
|
app.debug && console.log('moved content', err);
|
|
});
|
|
},
|
|
|
|
moveAttribute: function (index, args) {
|
|
var docId = getDocId(args.item);
|
|
var doc = getDoc(app, docId);
|
|
var snapshot = doc.snapshot;
|
|
var ops = ensureIndexesOnAttributes(snapshot);
|
|
if (ops.length < 2) {
|
|
var min = Math.min(args.sourceIndex,args.targetIndex);
|
|
var max = Math.max(args.sourceIndex,args.targetIndex);
|
|
var inc = ((args.sourceIndex > args.targetIndex) ? 1 : -1);
|
|
_.forEach(snapshot._attributes, function (attributeName,index) {
|
|
var od = snapshot[attributeName].index;
|
|
var path = [attributeName, 'index'];
|
|
if ((od > min && od < max) || (od == args.targetIndex)) {
|
|
ops.push({
|
|
p: path,
|
|
od: od,
|
|
oi: od + inc
|
|
});
|
|
}
|
|
if (od == args.sourceIndex) {
|
|
ops.push({
|
|
p: path,
|
|
od: od,
|
|
oi: args.targetIndex
|
|
});
|
|
}
|
|
if (typeof od != 'number') {
|
|
ops.push({
|
|
p: path,
|
|
od: od,
|
|
oi: index
|
|
});
|
|
}
|
|
});
|
|
} else {
|
|
app.debug && console.log('not moving reorganizing', ops);
|
|
args.cancelDrop=true;
|
|
}
|
|
app.debug && console.log('moveAttribute ops', ops);
|
|
doc.submitOp(ops, function (err) {
|
|
app.debug && console.log('moved attribute', err);
|
|
});
|
|
}
|
|
};
|
|
}
|
|
|
|
function initViewModel_navigation(app, vmName) {
|
|
app.debug && console.log('initViewModel_navigation');
|
|
var vm_config = getViewModelConfig(app, vmName);
|
|
var mapping = vm_config.mapping;
|
|
var snapshot = getDocSnapshot(app, vmName);
|
|
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());
|
|
};
|
|
return viewModel;
|
|
}
|
|
|
|
function updateViewModel_default(app, vmName) {
|
|
var vm_config = getViewModelConfig(app, vmName);
|
|
var mapping = vm_config.mapping;
|
|
var snapshot = getDocSnapshot(app, vmName);
|
|
var viewModel = getViewModel(app, vmName);
|
|
app.debug && console.log('updating', vmName, 'viewModel', snapshot, viewModel, vm_config);
|
|
app.ko.mapping.fromJS(snapshot, mapping, viewModel);
|
|
app.debug && console.log('updated', vmName, 'viewModel', viewModel);
|
|
}
|
|
|
|
function updateViewModel(app, vmName) {
|
|
var viewModelMethod = updateViewModel_default;
|
|
var methodName = 'updateViewModel_' + vmName;
|
|
if (this.hasOwnProperty(methodName)) {
|
|
viewModelMethod = this[methodName];
|
|
}
|
|
app.state.vm.updating[vmName] = true;
|
|
viewModelMethod(app, vmName);
|
|
app.state.vm.updating[vmName] = false;
|
|
}
|
|
|
|
function post_updateViewModel_default(app, vmName) {
|
|
app.debug && console.log('post_updating', vmName, 'viewModel');
|
|
}
|
|
|
|
function post_updateViewModel(app, vmName) {
|
|
var viewModelMethod = post_updateViewModel_default;
|
|
var methodName = 'post_updateViewModel_' + vmName;
|
|
if (this.hasOwnProperty(methodName)) {
|
|
viewModelMethod = this[methodName];
|
|
}
|
|
viewModelMethod(app, vmName);
|
|
}
|
|
|
|
function setViewModel(app, vmName, vm) {
|
|
app.vm[vmName] = vm;
|
|
}
|
|
|
|
function initViewModel_default(app, vmName) {
|
|
var vm_config = getViewModelConfig(app, vmName);
|
|
var mapping = vm_config.mapping;
|
|
var snapshot = getDocSnapshot(app, vmName);
|
|
app.debug && console.log('init', vmName, 'viewModel', snapshot, vm_config);
|
|
var viewModel = app.ko.mapping.fromJS(snapshot, mapping);
|
|
app.debug && console.log('init', vmName, 'viewModel', viewModel);
|
|
return viewModel;
|
|
}
|
|
|
|
function initViewModel(app, vmName) {
|
|
app.debug && console.log('initViewModel', vmName);
|
|
var viewModelMethod = initViewModel_default;
|
|
var methodName = 'initViewModel_' + vmName;
|
|
if (this.hasOwnProperty(methodName)) {
|
|
viewModelMethod = this[methodName];
|
|
}
|
|
app.state.vm.updating[vmName] = true;
|
|
setViewModel(app, vmName, viewModelMethod(app, vmName));
|
|
app.state.vm.updating[vmName] = false;
|
|
}
|
|
|
|
function getViewModel(app, vmName) {
|
|
return app.vm && app.vm[vmName];
|
|
}
|
|
|
|
function onDocChange(app, vmName) {
|
|
return function onChange() {
|
|
app.debug && console.log(vmName, ' viewModel changed!! running:', app.state.running);
|
|
if (app.state.running) {
|
|
app.debug && console.log(vmName, ' viewModel changed!! updating');
|
|
if (getViewModel(app, vmName)) {
|
|
updateViewModel(app, vmName);
|
|
post_updateViewModel(app, vmName);
|
|
} else {
|
|
console.error('HUH!!');
|
|
initViewModel(app, vmName);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function callFunction(app, vmName) {
|
|
return app.fn[vmName];
|
|
}
|
|
|
|
function initializeViewModel(app, vmName) {
|
|
app.debug && console.log('initializeViewModel', vmName);
|
|
// set viewModel functions
|
|
var methodName = vmName + '_functions';
|
|
if (this.hasOwnProperty(methodName)) {
|
|
app.fn[vmName] = this[methodName](app, vmName);
|
|
}
|
|
|
|
initViewModel(app, vmName);
|
|
var module_doc = getDoc(app, vmName);
|
|
module_doc.on('change', onDocChange(app, vmName));
|
|
}
|
|
|
|
function loadLocation(app, loc, type, callback) {
|
|
var doc_key = keyFromLocation(loc);
|
|
app.debug && console.log('opening location', doc_key);
|
|
return sharejs.open(type + ':' + doc_key, type, function (err, doc) {
|
|
if (err) {
|
|
console.error('error loadLocation', doc_key, err, doc);
|
|
return callback(err);
|
|
}
|
|
doc.on('error', function (err) {
|
|
console.error('event error loadLocation', doc_key, err, doc.state, doc);
|
|
});
|
|
doc.on('closed', function (err, data) {
|
|
app.debug && console.log('doc', doc, 'closing', err, 'data', data);
|
|
});
|
|
app.debug && console.log('opened location', doc_key, doc, doc.state, doc.version);
|
|
if (doc.snapshot == null) {
|
|
app.debug && console.log('wait for first change', doc_key, doc.version);
|
|
return doc.once('change', function onceChange() {
|
|
app.debug && console.log('received first change', doc_key, doc.snapshot);
|
|
callback(null, doc);
|
|
});
|
|
} else {
|
|
return callback(null, doc);
|
|
}
|
|
});
|
|
}
|
|
|
|
function getCurrentUser(app) {
|
|
return '';
|
|
// maybe pull from cookie?
|
|
// return 'aiko';
|
|
}
|
|
|
|
function loadConfigModule(app, loc, appModule, callback) {
|
|
app.debug && console.log('loadConfigModule', appModule, loc);
|
|
if (appModule == 'user') {
|
|
var user = getCurrentUser(app);
|
|
if (user) {
|
|
loc.name = user;
|
|
app.debug && console.log('loadConfig user', loc);
|
|
loadRawDoc(app, loc, callback);
|
|
} else {
|
|
// dummy user doc
|
|
callback(null, {
|
|
snapshot: {},
|
|
on: function (event, handler) {
|
|
console.log('blank config', appModule, 'event', event, 'handler', handler);
|
|
},
|
|
submitOp: function (ops, callback) {
|
|
return callback(null);
|
|
},
|
|
at: function (path) {
|
|
return {
|
|
get: function (path) {
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|
|
} else {
|
|
loadRawDoc(app, loc, callback);
|
|
}
|
|
}
|
|
|
|
function getViewModelConfig(app, appModule) {
|
|
var config_snapshot = getConfigDocSnapshot(app);
|
|
return config_snapshot && config_snapshot.viewModel && config_snapshot.viewModel[appModule]
|
|
}
|
|
|
|
function handleConfigModule(app, appModule, callback) {
|
|
app.debug && console.log('handleConfigModule app', app);
|
|
var config_snapshot = getConfigDocSnapshot(app);
|
|
if (!app.hasOwnProperty(appModule)) {
|
|
var loc = config_snapshot[appModule];
|
|
return loadConfigModule(app, loc, appModule, function handleLoadedConfigModule(err, module_doc) {
|
|
if (err) {
|
|
console.error('Error loading key', appModule, err);
|
|
app.debug && console.log('- handleConfigKey', appModule);
|
|
return callback(err);
|
|
} else {
|
|
setDoc(app, appModule, module_doc);
|
|
var vm_config = getViewModelConfig(app, appModule);
|
|
if (vm_config && !app.state.vm.bound.hasOwnProperty(appModule)) {
|
|
initializeViewModel(app, appModule);
|
|
}
|
|
}
|
|
app.debug && console.log('- handleConfigKey', appModule);
|
|
return callback();
|
|
});
|
|
} else {
|
|
app.debug && console.log('- handleConfigKey', appModule);
|
|
return callback();
|
|
}
|
|
}
|
|
|
|
function moduleIsLoadable(module) {
|
|
return module.hasOwnProperty('collection');
|
|
}
|
|
|
|
function initializeConfig(app, callback) {
|
|
var config_snapshot = getConfigDocSnapshot(app);
|
|
return app.async.each(_.keys(config_snapshot), function handleConfigModules(module, iterator_callback) {
|
|
app.debug && console.log('+ handleConfigModules', module);
|
|
if (moduleIsLoadable(config_snapshot[module])) {
|
|
return handleConfigModule(app, module, iterator_callback);
|
|
} else {
|
|
return iterator_callback();
|
|
}
|
|
}, callback);
|
|
}
|
|
|
|
function setConfigDoc(app, doc) {
|
|
app.config = doc;
|
|
}
|
|
|
|
function getConfigDoc(app) {
|
|
return app && app.config;
|
|
}
|
|
|
|
function getConfigDocSnapshot(app) {
|
|
return getConfigDoc(app) && app.config.snapshot;
|
|
}
|
|
|
|
function loadRawDoc(app, loc, callback) {
|
|
loadLocation(app, loc, 'json', callback);
|
|
}
|
|
|
|
function loadConfig(app, config_loc, callback) {
|
|
loadRawDoc(app, config_loc, function handleConfig(err, doc) {
|
|
if (err) {
|
|
console.error('error loadConfig', loc, err);
|
|
return callback(err);
|
|
}
|
|
setConfigDoc(app, doc);
|
|
initializeConfig(app, callback);
|
|
return doc.on('change', function handleConfigChange() {
|
|
app.debug && console.log('Config changed!!');
|
|
initializeConfig(app, callback);
|
|
});
|
|
});
|
|
}
|
|
|
|
function bindViewModels(app) {
|
|
var config_snapshot = getConfigDocSnapshot(app);
|
|
_.forEach(_.keys(app.vm), function (module) {
|
|
if (module in app.state.vm.bound) {
|
|
app.debug && console.log('bindViewModels bindings already applied for', module);
|
|
} else {
|
|
var viewModel = app.vm[module];
|
|
var viewModelConfig = config_snapshot.viewModel[module];
|
|
var elementId = viewModelConfig.elementId;
|
|
var element = app.document.getElementById(elementId);
|
|
app.debug && console.log('bindViewModels applyBindings', module, viewModel, viewModelConfig, elementId);
|
|
app.ko.applyBindings(viewModel, element);
|
|
app.state.vm.bound[module] = elementId;
|
|
}
|
|
});
|
|
app.debug && console.log('Viewmodels bound', 'ok');
|
|
}
|
|
|
|
var app = {
|
|
debug: true,
|
|
ko: ko,
|
|
async: async,
|
|
ace: ace,
|
|
document: document,
|
|
state: {
|
|
running: false,
|
|
vm: {
|
|
bound: {},
|
|
updating: {}
|
|
}
|
|
},
|
|
doc: {},
|
|
fn: {},
|
|
vm: {}
|
|
};
|
|
|
|
var config_location = {
|
|
collection: "app",
|
|
name: "config"
|
|
};
|
|
|
|
function initKnockout(app) {
|
|
/* http://www.knockmeout.net/2012/05/quick-tip-skip-binding.html */
|
|
app.ko.bindingHandlers.stopBinding = {
|
|
init: function () {
|
|
return { controlsDescendantBindings: true };
|
|
}
|
|
};
|
|
app.ko.virtualElements.allowedBindings.stopBinding = true;
|
|
}
|
|
|
|
function initializeApp(app) {
|
|
|
|
if (app.state.running == false) {
|
|
initKnockout(app);
|
|
}
|
|
bindViewModels(app);
|
|
app.state.running = true;
|
|
|
|
console.log(app, 'Running', 'ok');
|
|
}
|
|
|
|
loadConfig(app, config_location, function configLoaded(err) {
|
|
if (err) {
|
|
console.error('Error loading config', err);
|
|
}
|
|
console.log(app, 'Loaded', err || 'ok');
|
|
|
|
initializeApp(app)
|
|
});
|
|
|
|
</script>
|
|
<!-- @@_end_import__app_main_behaviour -->
|
|
<!-- @@_end_import__app_main_body -->
|
|
</body>
|
|
</html>
|
|
|