diff --git a/package.json b/package.json index 691a53e5..44a36b73 100644 --- a/package.json +++ b/package.json @@ -38,8 +38,10 @@ "webpack-dev-server": "1.15.0" }, "dependencies": { + "sprintf": "0.1.5", "diff-match-patch": "1.0.0", "numeric": "1.2.6", - "jwerty": "0.3.2" + "jwerty": "0.3.2", + "mustache-loader": "0.3.3" } } diff --git a/web/app/3d/actions/actions.js b/web/app/3d/actions/actions.js index 20fc3c18..f65cbf0d 100644 --- a/web/app/3d/actions/actions.js +++ b/web/app/3d/actions/actions.js @@ -66,7 +66,7 @@ ActionManager.prototype.run = function(actionId, event) { if (action.state.enabled) { action.__handler(this.app, event); } else { - this.app.inputManager.info("action '"+actionId+"' is disabled and can't be launched
" + action.state.hint); + this.app.inputManager.messageSink.info("action '"+actionId+"' is disabled and can't be launched
" + action.state.hint); } }; diff --git a/web/app/3d/ui/bind.js b/web/app/3d/ui/bind.js new file mode 100644 index 00000000..af409091 --- /dev/null +++ b/web/app/3d/ui/bind.js @@ -0,0 +1,26 @@ +import {sprintf} from 'sprintf' + +export function Bind(dom, data, policy) { + if (!policy) policy = DEFAULT_POLICY; + const props = Object.getOwnPropertyNames(data); + for (let prop of props) { + const node = $(dom).find('[data-bind="'+prop+'"]'); + if (node.length == 0) continue; + let value = data[prop]; + if (!policy.hideEmptyValue || value || value === 0) { + var format = node.attr('bind-format'); + if (format) { + value = sprintf(format, value); + } + node.text(value); + node.show(); + } else { + node.text(''); + node.hide(); + } + } +} + +const DEFAULT_POLICY = { + hideEmptyValue: true +}; \ No newline at end of file diff --git a/web/app/3d/ui/input-manager.js b/web/app/3d/ui/input-manager.js index 0040e981..77415e0c 100644 --- a/web/app/3d/ui/input-manager.js +++ b/web/app/3d/ui/input-manager.js @@ -1,6 +1,8 @@ import {jwerty} from 'jwerty' import {keymap} from './keymaps/default' -import {DefaultMouseEvent, EventData, fit} from './utils' +import {Bind} from './bind' +import {MessageSink} from './message-sink' +import {LoadTemplate, DefaultMouseEvent, EventData, fit} from './utils' export function InputManager(app) { this.app = app; @@ -8,19 +10,20 @@ export function InputManager(app) { this.keymap = keymap; this.mouseInfo = new DefaultMouseEvent(); this.requestedActionInfo = null; + this.actionInfoDom = $(LoadTemplate('action-info')({})); + this.messageSink = new MessageSink(this); $(() => { $(document) .on('keydown', (e) => this.handleKeyPress(e)) .on('mousedown', (e) => this.clear(e)) .on('mouseenter', '.action-item', (e) => this.showActionInfo($(e.target))) - .on('mouseleave', '.action-item', (e) => this.emptyInfo()) + .on('mouseleave', '.action-item', (e) => this.hideActionInfo()) .on('mousemove', (e) => this.mouseInfo = e) .on('click', '.action-item', (e) => this.handleActionClick(e)); }); } InputManager.prototype.handleKeyPress = function(e) { - console.log(e.keyCode); switch (e.keyCode) { case 27 : this.clear(); break; } @@ -44,7 +47,7 @@ InputManager.prototype.clear = function(e) { this.openMenus = []; } this.requestedActionInfo = null; - $('#message-sink').hide(); + this.messageSink.hide(); }; InputManager.prototype.handleActionClick = function(event) { @@ -62,39 +65,21 @@ InputManager.prototype.registerOpenMenu = function(menu) { this.openMenus.push(menu); }; -InputManager.messageSink = function() { - return $('#message-sink'); -}; - -InputManager.prototype.emptyInfo = function() { +InputManager.prototype.hideActionInfo = function() { this.requestedActionInfo = null; - var messageSink = InputManager.messageSink(); - messageSink.empty(); - messageSink.hide(); + this.messageSink.hide(); }; InputManager.prototype.showActionInfo = function(el) { //show hint immediately and deffer showing the full info - var hint = el.data('actionHint'); - if (hint) { - InputManager.messageSink().text(hint); - this.showMessageSinkAround(); - } + //var hint = el.data('actionHint'); + //if (hint) { + // InputManager.messageSink().text(hint); + // this.showMessageSinkAround(); + //} this.requestInfo(el.data('action')); }; -InputManager.prototype.info = function(text) { - InputManager.messageSink().html(text); - this.showMessageSinkAround(); -}; - -InputManager.prototype.showMessageSinkAround = function() { - var messageSink = InputManager.messageSink(); - messageSink.show(); - messageSink.offset({left: this.mouseInfo.pageX + 10, top: this.mouseInfo.pageY + 10}); - fit(messageSink, $('body')); -}; - InputManager.prototype.requestInfo = function(action) { this.requestedActionInfo = action; setTimeout(() => { @@ -103,13 +88,14 @@ InputManager.prototype.requestInfo = function(action) { if (actionId != null) { const action = this.app.actionManager.actions[actionId]; if (action) { - var hotkey = this.keymap[actionId]; - InputManager.messageSink().html( - (action.state.hint ? action.state.hint : '') + - ('
' + action.info + '
') + - (hotkey ? '
hotkey: ' + hotkey + '
' : '')); - this.showMessageSinkAround(); + var hotKey = this.keymap[actionId]; + Bind(this.actionInfoDom, { + hint: action.state.hint, + info: action.info, + hotKey: hotKey + }); + this.messageSink.showContent(this.actionInfoDom); } } - }, 1000); + }, 500); }; \ No newline at end of file diff --git a/web/app/3d/ui/message-sink.js b/web/app/3d/ui/message-sink.js new file mode 100644 index 00000000..8d8b1b50 --- /dev/null +++ b/web/app/3d/ui/message-sink.js @@ -0,0 +1,29 @@ +import {fit} from './utils' + +export function MessageSink(inputManager) { + this.inputManager = inputManager; + this.node = $('
', {'class': 'message-sink'}); + $('body').append(this.node); +} + +MessageSink.prototype.show = function() { + this.node.show(); + this.node.offset({left: this.inputManager.mouseInfo.pageX + 10, top: this.inputManager.mouseInfo.pageY + 10}); + fit(this.node, $('body')); +}; + +MessageSink.prototype.hide = function() { + this.node.hide(); +}; + +MessageSink.prototype.showContent = function(dom) { + this.node.children().detach(); + this.node.append(dom); + this.show(); +}; + +MessageSink.prototype.info = function(text) { + this.node.children().detach(); + this.node.html(text); + this.show(); +}; diff --git a/web/app/3d/ui/tmpl/action-info.html b/web/app/3d/ui/tmpl/action-info.html new file mode 100644 index 00000000..9ad94cf5 --- /dev/null +++ b/web/app/3d/ui/tmpl/action-info.html @@ -0,0 +1,5 @@ +
+
+
+
+
\ No newline at end of file diff --git a/web/app/3d/ui/utils.js b/web/app/3d/ui/utils.js index faa6a80e..d237343b 100644 --- a/web/app/3d/ui/utils.js +++ b/web/app/3d/ui/utils.js @@ -70,5 +70,8 @@ export function fit(el, relativeEl) { top: off.top + 'px' }); } +} -} \ No newline at end of file +export function LoadTemplate(name) { + return require('./tmpl/' + name + '.html'); +} \ No newline at end of file diff --git a/web/css/app3d.less b/web/css/app3d.less index c7c1b75c..2e6078a8 100644 --- a/web/css/app3d.less +++ b/web/css/app3d.less @@ -9,6 +9,8 @@ @control-button-border: 1px solid #2c2c2c; @menu-border-radius: 3px; +@suppressed-color: #888; + .no-selection { user-select: none; -webkit-user-select: none; @@ -187,13 +189,13 @@ body { } .menu-item.action-disabled { - color: #888; + color: @suppressed-color; } .menu-item .action-hotkey-info { float: right; padding-left: 15px; - color: #888; + color: @suppressed-color; font-size: 9px; margin-top: 1px; } @@ -206,16 +208,31 @@ body { padding-left: 25px; } -#message-sink { +.message-sink { display: none; position: absolute; max-width: 400px; padding: 5px; + padding: 2px 5px 2px 5px; .aux-win; color: #ccc; white-space: nowrap; z-index: 999; } +.action-info > div { + padding: 3px 0 3px 0; +} +.action-info-hotkey { + text-align: right; + font-style: italic; + color: @suppressed-color; +} + +.action-info-hint { + text-align: right; + font-style: italic; + color: #E1A4A4; +} diff --git a/web/index.html b/web/index.html index 15e9084b..33e94251 100644 --- a/web/index.html +++ b/web/index.html @@ -37,7 +37,6 @@
-
diff --git a/webpack.config.js b/webpack.config.js index e240075b..f4193b7d 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -28,6 +28,10 @@ module.exports = { { test: /\.less$/, loader: "style!css!less" + }, + { + test: /\.html$/, + loader: 'mustache' }] } };